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

Handling multipart/form-data POST with Express in Cloud Functions

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

除了搞砸了请求对象之外,它似乎还搞砸了服务生的工作方式。

我已经尝试了 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
}

请阅读documentation for handling multipart form uploads

... if you want your Cloud Function to process multipart/form-data, you can use the rawBody property of the request.

由于 Cloud Functions pre-processes some requests 的方式,您可以预料某些 Express 中间件将无法工作,而这正是您 运行 喜欢的。

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

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

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

我已将前两个答案组合成一个 easy-to-use 异步函数。

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);
      }
    }
  });
};