Отправка node.js дуплексного потока по HTTP на внешний сервер - PullRequest
0 голосов
/ 06 мая 2020

Мне нужно руководство или объяснение, как использовать дуплексные потоки загрузки в node.js. Или какое-то подтверждение правильности моего подхода.

Общая идея состоит в том, чтобы загрузить файл в приложении angular на PHP сервер через graphql:

[Angular app] --> [GraphQL] --> [PHP Storage - some image manipulation] --> cloud or other storage

В конце концов, я был успешно, но я не могу устоять перед чувством, что это можно было бы сделать лучше. Вот некоторые подробности того, что я обнаружил и чего добился.

В качестве сервера GraphQL я использую Apollo . Общая идея заключается в том, что Apollo предоставляет Upload скаляр, который предоставляет поток для чтения загруженного файла в преобразователе мутаций.

Согласно документации Мне удалось отправить файл из angular в GQL server и создать там поток чтения, но я не смог отправить его через HTTP во внешнюю службу. Одно замечание: я использую ax ios для HTTP-запроса, но я пробовал также с другими инструментами с тем же результатом.

Подход 1. Отправить поток как есть

Во многих потоках stackoverflow я нашел один общий подход: упаковывать поток непосредственно в объект FormData и просто передавать его как тело запроса, поэтому мой код выглядел так:

exports.upload = async function(stream, token) {
    const { createReadStream, filename, mimetype } = await stream;
    const imageIncomingStream = createReadStream();
    const formData = new FormData();

    formData.append('file', imageIncomingStream, {
        contentType: mimetype,
        filename: filename
    });

    const headers = formData.getHeaders({
        'Authorization' : token
    });

    axios.post('MY ENDPOINT HERE', formData, { headers });
};

, но Проблема заключалась в том, что поток был отправлен не полностью, например, ax ios или данные формы не могли прочитать его перед отправкой. PHP Сервер получал загрузку, но файл был поврежден. Поэтому я подумал, что, может быть, мне следует сначала прочитать этот поток, а затем отправить его. Итак, возьмите номер два

Подход 2: Прочтите поток перед отправкой

exports.upload = async function(stream, token) {
    const { createReadStream, filename, mimetype } = await stream;
    const imageIncomingStream = createReadStream();
    const formData = new FormData();

    imageIncomingStream.on('end', () => {
        formData.append('file', imageIncomingStream, {
            contentType: mimetype,
            filename: filename
        });

        const headers = formData.getHeaders({
            'Authorization' : token
        });

        axios.post('MY ENDPOINT HERE', formData, { headers });
    });

    imageIncomingStream.resume();
};

с этим подходом я мог видеть, что отправляется весь файл, но по какой-то причине запрос не выполнялся . Сервер некоторое время зависал, а затем вылетал с ошибкой Error: socket hang up. Я попытался добавить длину содержимого, например,

formData.getLength((err, len) => {
                const headers = formData.getHeaders({
                    'content-length' : len,
                    'Authorization' : token
                });

                axios.post('MY ENDPOINT HERE', formData, { headers });
            });

, но это тоже не помогло. Также пробовал с imageIncomingStream.destroy();, потому что я подумал, что, возможно, поток записи каким-то образом блокирует его, но снова безуспешно. Одно важное замечание: запрос никогда не выполнялся или, по крайней мере, он никогда не доходил до сервера PHP.

На этом этапе я не был уверен, использую ли я правильные инструменты, или, может быть, что-то не так с конфигурации серверов, поэтому я попытался отправить локальный файл вот так. Вместо использования потока, предоставляемого Apollo, я создал обычный поток чтения для одного из локальных файлов, например const imageIncomingStream = fs.createReadStream(__dirname + '/image.png');, и он работал как шарм с первым подходом, поэтому нет необходимости вручную читать весь поток. Ax ios или FormData смогли прочитать поток и отправить его на мой сервер. Итак, это привело меня к созданию окончательного и рабочего подхода.

Подход 3: создать поток чтения из файла tmp

Я заметил, что как только входящий поток полностью прочитан, к нему прикреплен путь, указывающий на tmp файл на сервере. Итак, я понял, что могу прочитать этот файл, поскольку работают обычные потоки чтения. Вот как это выглядит сейчас

exports.upload = async function(stream, shopId, productId, token) {
    const { createReadStream, filename, mimetype } = await stream;
    const imageIncomingStream = createReadStream();

    finished(imageIncomingStream, (err) => {
        if (err) {
            console.error('Stream failed.', err);
        } else {
            const tmpImageStream = fs.createReadStream(imageIncomingStream.path);
            const formData = new FormData();

            imageIncomingStream.destroy();

            formData.append('file', tmpImageStream, {
                contentType: mimetype,
                filename: filename
            });

            const headers = formData.getHeaders({
                'Authorization' : token
            });

            axios.post('MY ENDPOINT HERE', formData, { headers });
        }
    });

    imageIncomingStream.resume();
};

Как я сказал в начале, это работает, но я чувствую, что это можно было бы сделать лучше. Итак, вот мой вопрос: есть ли способ использовать входящий поток (который является дуплексным, если это имеет значение) вместо создания нового?

...