使用Express in Cloud Functions处理multipart / form-data POST

时间:2018-01-16 21:05:56

标签: firebase express google-cloud-functions busboy

我一直在尝试使用Firebase功能和Express处理POST(multipart / form-data),但它只是不起作用。在本地服务器上试过这个,它运行得很好。一切都是一样的,除了它没有包含在Firebase功能中。

除了搞砸了请求对象之外,它似乎也搞砸了busboy的工作方式。

我尝试了here提供的不同解决方案,但它们无法正常工作。正如一位用户所提到的那样,传递给busboy的回调(在找到'field'时或在完成数据传递时调用)从不被调用,函数只是挂起。

有什么想法吗?

这是我的函数代码供参考:

const functions = require('firebase-functions');
const express = require('express');
const getRawBody = require('raw-body');
const contentType = require('content-type')
const Busboy = require('busboy');

const app = express();

const logging = (req, res, next) => {
  console.log(`> request body: ${req.body}`);
  next();
}

const toRawBody = (req, res, next) => {
  const options = {
      length: req.headers['content-length'],
      limit: '1mb',
      encoding: contentType.parse(req).parameters.charset
  };
  getRawBody(req, options)
      .then(rawBody => {
          req.rawBody = rawBody
          next();
      })
      .catch(error => {
          return next(error);
      });
};

const handlePostWithBusboy = (req, res) => {
  const busboy = new Busboy({ headers: req.headers });
  const formData = {};

  busboy.on('field', (fieldname, value) => {
      formData[fieldname] = value;
  });

  busboy.on('finish', () => {
      console.log(`> form data: ${JSON.stringify(formData)}`);
      res.status(200).send(formData);
  });

  busboy.end(req.rawBody);
}

app.post('/', logging, toRawBody, handlePostWithBusboy);

const exchange = functions.https.onRequest((req, res) => {
  if (!req.path) {
    req.url = `/${req.url}`
  }
  return app(req, res)
})
module.exports = {
  exchange
}

3 个答案:

答案 0 :(得分:7)

Doug在他的回答中提到的文件很好。但一个重要的警告是rawBody在模拟器中不起作用。解决方法是:

if (req.rawBody) {
    busboy.end(req.rawBody);
}
else {
    req.pipe(busboy);
}

如本期所述: https://github.com/GoogleCloudPlatform/cloud-functions-emulator/issues/161#issuecomment-376563784

答案 1 :(得分:6)

请阅读documentation for handling multipart form uploads

  

...如果您希望Cloud Function处理multipart / form-data,您可以使用请求的rawBody属性。

由于Cloud Functions pre-processes some requests的方式,您可以预期某些Express中间件将无法运行,而这正是您遇到的问题。

答案 2 :(得分:1)

我将前面的两个答案合并为一个易于使用的异步函数。

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

module.exports = function extractMultipartFormData(req) {
  return new Promise((resolve, reject) => {
    if (req.method != 'POST') {
      return reject(405);
    } else {
      const busboy = new Busboy({ headers: req.headers });
      const tmpdir = os.tmpdir();
      const fields = {};
      const fileWrites = [];
      const uploads = {};

      busboy.on('field', (fieldname, val) => (fields[fieldname] = val));

      busboy.on('file', (fieldname, file, filename) => {
        const filepath = path.join(tmpdir, filename);
        const writeStream = fs.createWriteStream(filepath);

        uploads[fieldname] = filepath;

        file.pipe(writeStream);

        const promise = new Promise((resolve, reject) => {
          file.on('end', () => {
            writeStream.end();
          });
          writeStream.on('finish', resolve);
          writeStream.on('error', reject);
        });

        fileWrites.push(promise);
      });

      busboy.on('finish', async () => {
        const result = { fields, uploads: {} };

        await Promise.all(fileWrites);

        for (const file in uploads) {
          const filename = uploads[file];

          result.uploads[file] = fs.readFileSync(filename);
          fs.unlinkSync(filename);
        }

        resolve(result);
      });

      busboy.on('error', reject);

      if (req.rawBody) {
        busboy.end(req.rawBody);
      } else {
        req.pipe(busboy);
      }
    }
  });
};