Загрузка изображений с использованием multipart / form-data с помощью node.js с использованием встроенного http-модуля - PullRequest
0 голосов
/ 05 октября 2018

Так что мне нужно отправить два изображения и ключ API в рамках HTTP-запроса multipart / form-data к API.Я получаю изображения из корзины aws s3, и это отлично работает, но всякий раз, когда я пытаюсь отправить изображение как часть данных формы, я получаю HTTP-ошибку EPIPE.Почему-то запрос отменяется до того, как все данные будут получены API.Я попробовал то же самое, используя почтальон, и все работает хорошо, просто моя нодовая программа, кажется, не в состоянии этого достичь.Ниже приведен фрагмент кода:

const http = require('http')
const https = require('https')
const AWS = require('aws-sdk')
const s3 = new AWS.S3({apiVersion: '2006-03-01'});

//simple http post request, there doesn't seem to be anything wrong with it 
const httpPromise = (protocol, params, postData) => {
return new Promise((resolve, reject) => {
    const requestModule = protocol === 'http' ? http : https;
    const req = requestModule.request(params, res => {
        // grab request status
        const statusCode = res.statusCode;
        if(statusCode < 200 || statusCode > 299) {
            throw new Error(`Request Failed with Status Code: ${status}`);
        }

        let body = '';
        // continuosly update data with incoming data
        res.setEncoding('utf8');
        res.on('data', data => body += data);

        // once all data was received
        res.on('end', () => {
            console.log(body)
            resolve(body)
        });
    })

    // write data to a post request
    if(typeof(params.method) === 'string' && params.method === 'POST' && postData) {
        req.write(postData)
    }

    // bind to the error event
    req.on('error', err => reject(err));

    // end the request
    req.end();
})
}

const handler = async (event) => {

// requestOption parameters
const apiKey = '000000';
const protocol = 'http';
const path = '/verify';
// set to the defined port, if the port is not defined set to default for either http or https
const port = Port ? Port : protocol === 'http' ? 80 : 443;
const hostname ='www.example.com';
const method = "POST";
const boundary = '__X_PAW_BOUNDARY__';

// get correct keys for the relevant images
const image1Key = 'image1Key';
const image2Key = 'image2Key';
const imageKeys = [image1, image2];



try {
    // get the images, this works all as intended
    const s3GetObjectPromises = [];
    imageKeys.forEach(key => s3GetObjectPromises.push(
        s3.getObject({Bucket: BucketName, Key: key})
        .promise()
        .then(res => res.Body)
    ))
    const [image1, image2] = await Promise.all(s3GetObjectPromises);
    //========== ALL GOOD TILL HERE ============



    // THIS IS WHERE IT GETS PROBLEMATIC:
    // create the postData formData string
    const postData = "--" + boundary + "\r\nContent-Disposition: form-data; name=\"key\"\r\n\r\n" + apiKey + "\r\n--" + boundary + "Content-Disposition: form-data; name=\"image1\"; filename=\"IMG_7264.JPG\"\r\nContent-Type: image/jpeg \r\n\r\n" + image1 + "\r\n--" + boundary + "\r\nContent-Disposition: form-data; name=\"image1\"; filename=\"IMG_7264.JPG\"\r\nContent-Type: image/jpeg\r\n\r\n" + image2 + "\r\n--" + boundary + "--";

    // the formData headers
    const headers = {
        "Content-Type":`multipart/form-data; charset=utf-8; boundary=${boundary}`,
        "Content-Length": `${postData.length}`,
        "User-Agent": "Paw/3.1.7 (Macintosh; OS X/10.14.0) GCDHTTPRequest"
    }
    // the options object
    const options = {hostname, port, path, method, headers};

    let result = await httpPromise(protocol, options, postData)
    console.log(result)
    return result;
} catch(err) {
    console.log(err)
    //this either throws an EPIPE error or it simply states that no key was available
    throw err;
}

//execute the handler
handler()
.then(res => console.log(res))
.catch(err => console.log(err))

1 Ответ

0 голосов
/ 07 октября 2018

Хорошо, поэтому после долгих попыток и экспериментов я выяснил, почему вышеприведенный код не работает.Прежде всего, тип содержимого в строке postdata должен быть установлен на image /, но это маленький, на самом деле это не было причиной, почему это не сработало.

Ошибка EPIPE или сети произошла из-за того, что я установил заголовок Content-Length неправильной длины.Вместо того, чтобы просто устанавливать его в длину строки, он должен быть установлен в ByteLength строки.Так что просто замените 'Content-Length': postData.length.toString() на 'Content-Length': Buffer.byteLength(postData).toString().Это должно решить ошибку EPIPE.

Но есть еще одна проблема: я, по сути, преобразую все данные в одну большую строку данных (postData) и отправляю всю строку за одну req.write(postData) операцию.Таким образом, очевидно, что это не то, как нужно это делать (опять же после долгих экспериментов), вместо этого нужно отправить массив, содержащий отдельные строки данных, а затем записать каждый элемент массива в запрос http.Итак, по существу:

// instead of this string: 
const postData = "--" + boundary + "\r\nContent-Disposition: form-data; name=\"key\"\r\n\r\n" + apiKey + "\r\n--" + boundary + "Content-Disposition: form-data; name=\"image1\"; filename=\"IMG_7264.JPG\"\r\nContent-Type: image/jpeg \r\n\r\n" + image1 + "\r\n--" + boundary + "\r\nContent-Disposition: form-data; name=\"image1\"; filename=\"IMG_7264.JPG\"\r\nContent-Type: image/jpeg\r\n\r\n" + image2 + "\r\n--" + boundary + "--";

// use this array: 
const postData = [`--${boundary}`, `\r\nContent-Disposition: form-data; name=\"key\"\r\n\r\n`, apiKey, `\r\n--${boundary}\r\n`, `Content-Disposition: form-data; name=\"image1\"; filename=\"IMG_7264.JPG\"\r\n`, `Content-Type: image/jpeg \r\n\r\n`, image1, `\r\n--${boundary}\r\n`, `Content-Disposition: form-data; name=\"image1\"; filename=\"IMG_7264.JPG\"\r\n`, `Content-Type: image/jpeg\r\n\r\n`, image2, `\r\n--${boundary}--`];

, а затем в фактическом запросе вы должны записать этот массив в элемент http-запроса по элементу:

// instead of simply
req.write(postData)
// do:
for(let data of postData) {
    req.write(data);
} 

также обязательно добавьте некоторые функции длявычисление длины заголовка содержимого, которое учитывает, что тело теперь хранится в массиве, что-то вроде этого должно сделать работу:

const postDataLength = postData.reduce((acc, curr) => acc += Buffer.byteLength(curr), 0)

А затем просто установить атрибут заголовка Content-Length равным postDataLength.

Надеюсь, это поможет любому, кто пытается создать запрос на публикацию данных формы с нуля, вместо использования сторонней библиотеки, такой как request, которая также разберется с вами.

...