Как выполнить загрузку файла HTTP с помощью экспресс-функции в облачных функциях для Firebase (multer, busboy)

21
задан Doug Stevenson 25 August 2018 в 13:31
поделиться

5 ответов

Чтобы добавить к официальному ответу команды Cloud Function, вы можете эмулировать это поведение локально, выполнив следующие действия (очевидно, добавьте это промежуточное ПО выше, чем код busboy, который они опубликовали)

const getRawBody = require('raw-body');
const contentType = require('content-type');

app.use(function(req, res, next){
    if(req.rawBody === undefined && req.method === 'POST' && req.headers['content-type'] !== undefined && req.headers['content-type'].startsWith('multipart/form-data')){
        getRawBody(req, {
            length: req.headers['content-length'],
            limit: '10mb',
            encoding: contentType.parse(req).parameters.charset
        }, function(err, string){
            if (err) return next(err);
            req.rawBody = string;
            next();
        });
    }
    else{
        next();
    }
});
5
ответ дан Brian Rosamilia 25 August 2018 в 13:31
поделиться

Благодаря ответам выше я создал для этого модуль npm ( github )

Он работает с облачными функциями Google, просто установите его (npm install --save express-multipart-file-parser) и используйте его так:

const fileMiddleware = require('express-multipart-file-parser')

...
app.use(fileMiddleware)
...

app.post('/file', (req, res) => {
  const {
    fieldname,
    filename,
    encoding,
    mimetype,
    buffer,
  } = req.files[0]
  ...
})
9
ответ дан Cristóvão Trevisan 25 August 2018 в 13:31
поделиться

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

Объединение некоторых из приведенных выше решений для создания сценария, поддерживающего TypeScript и промежуточное программное обеспечение, здесь:

https://gist.github.com/jasonbyrne/8dcd15701f686a4703a72f13e3f800c0

3
ответ дан Jason Byrne 25 August 2018 в 13:31
поделиться

Я исправил некоторые ошибки в ответе Г. Родригеса. Я добавляю событие 'field' и 'finish' для Busboy и делаю next () в событии 'finish'. Это работа для меня. Как следует:

    module.exports = (path, app) => {
    app.use(bodyParser.json())
    app.use(bodyParser.urlencoded({ extended: true }))
    app.use((req, res, next) => {
        if(req.rawBody === undefined && req.method === 'POST' && req.headers['content-type'].startsWith('multipart/form-data')){
            getRawBody(req, {
                length: req.headers['content-length'],
                limit: '10mb',
                encoding: contentType.parse(req).parameters.charset
            }, function(err, string){
                if (err) return next(err)
                req.rawBody = string
                next()
            })
        } else {
            next()
        }
    })

    app.use((req, res, next) => {
        if (req.method === 'POST' && req.headers['content-type'].startsWith('multipart/form-data')) {
            const busboy = new Busboy({ headers: req.headers })
            let fileBuffer = new Buffer('')
            req.files = {
                file: []
            }

            busboy.on('file', (fieldname, file, filename, encoding, mimetype) => {
                file.on('data', (data) => {
                    fileBuffer = Buffer.concat([fileBuffer, data])
                })

                file.on('end', () => {
                    const file_object = {
                        fieldname,
                        'originalname': filename,
                        encoding,
                        mimetype,
                        buffer: fileBuffer
                    }

                    req.files.file.push(file_object)
                })
            })

            busboy.on('field', function(fieldname, val, fieldnameTruncated, valTruncated) {
              console.log('Field [' + fieldname + ']: value: ' + inspect(val));
            });

            busboy.on('finish', function() {
              next()
            });

            busboy.end(req.rawBody)
            req.pipe(busboy);
        } else {
            next()
        }
    })}
3
ответ дан Shuangquan Wei 25 August 2018 в 13:31
поделиться

В настройке Cloud Functions действительно произошли серьезные изменения, которые вызвали эту проблему. Это связано с тем, как работает промежуточное ПО, которое применяется ко всем приложениям Express (включая приложение по умолчанию), используемым для обслуживания функций HTTPS. По сути, облачные функции будут анализировать тело запроса и решать, что с ним делать, оставляя необработанное содержимое тела в буфере в req.rawBody. Вы можете использовать это для непосредственного анализа вашего многокомпонентного контента, но вы не можете сделать это с помощью промежуточного программного обеспечения (например, multer).

Вместо этого вы можете использовать модуль под названием busboy для непосредственной работы с необработанным содержимым тела. Он может принять буфер rawBody и перезвонит вам с найденными файлами. Вот пример кода, который будет перебирать весь загруженный контент, сохранять его как файлы, а затем удалять их. Вы, очевидно, захотите сделать что-то более полезное.

const path = require('path');
const os = require('os');
const fs = require('fs');
const Busboy = require('busboy');

exports.upload = functions.https.onRequest((req, res) => {
    if (req.method === 'POST') {
        const busboy = new Busboy({ headers: req.headers });
        // This object will accumulate all the uploaded files, keyed by their name
        const uploads = {}

        // This callback will be invoked for each file uploaded
        busboy.on('file', (fieldname, file, filename, encoding, mimetype) => {
            console.log(`File [${fieldname}] filename: ${filename}, encoding: ${encoding}, mimetype: ${mimetype}`);
            // Note that os.tmpdir() is an in-memory file system, so should only 
            // be used for files small enough to fit in memory.
            const filepath = path.join(os.tmpdir(), fieldname);
            uploads[fieldname] = { file: filepath }
            console.log(`Saving '${fieldname}' to ${filepath}`);
            file.pipe(fs.createWriteStream(filepath));
        });

        // This callback will be invoked after all uploaded files are saved.
        busboy.on('finish', () => {
            for (const name in uploads) {
                const upload = uploads[name];
                const file = upload.file;
                res.write(`${file}\n`);
                fs.unlinkSync(file);
            }
            res.end();
        });

        // The raw bytes of the upload will be in req.rawBody.  Send it to busboy, and get
        // a callback when it's finished.
        busboy.end(req.rawBody);
    } else {
        // Client error - only support POST
        res.status(405).end();
    }
})

Помните, что файлы, сохраненные во временном пространстве, занимают память, поэтому их размеры должны быть ограничены до 10 МБ. Для более крупных файлов вы должны загрузить их в облачное хранилище и обработать их с помощью триггера хранилища.

Также имейте в виду, что выбор промежуточного программного обеспечения по умолчанию, добавляемый Cloud Functions, в настоящее время не добавляется в локальный эмулятор через firebase serve. Так что этот пример не будет работать (rawBody не будет доступен) в этом случае.

Команда работает над обновлением документации, чтобы лучше понять, что происходит во время запросов HTTPS, отличающихся от стандартного приложения Express.

31
ответ дан Doug Stevenson 25 August 2018 в 13:31
поделиться
Другие вопросы по тегам:

Похожие вопросы: