Busboy保存Steam供以后使用

时间:2019-01-18 22:51:02

标签: javascript node.js express busboy

我正尝试使用busboy允许客户端将文件上传到我的Express Web服务器。

我为Express运行以下中间件功能。

module.exports = (req, res, next) => {
    req.files = {};

    let busboy;
    try {
        busboy = new Busboy({
            headers: req.headers
        });
    } catch (e) {
        return next();
    }

    busboy.on("file", (fieldname, file, filename, encoding, mimetype) => {
        req.files[fieldname] = {
            file,
            filename,
            encoding,
            mimetype
        };

        // Need to call `file.resume` to consume the stream somehow (https://stackoverflow.com/a/24588458/894067)
        file.resume();
    });
    busboy.on("finish", next);

    req.pipe(busboy);
};

如您所见,我必须添加file.resume();才能触发“结束”事件,并为中间件(https://stackoverflow.com/a/24588458/894067)调用next函数。

问题是,稍后,当我要使用流时,它说readable: false。因此,我假设file.resume();会丢弃该流,并且不允许将来使用它。

我基本上想获取所有上传的文件和与这些文件相关的信息,将它们存储在req.files对象上,然后稍后使用流,或者如果我不想使用它们,则不使用它们。这样,它们将保留在流中,并且不会占用太多内存,直到我准备使用流并实际对其进行处理(或选择丢弃它)为止。

我可以代替file.resume();使用什么来确保“完成”事件得到触发,同时允许我稍后在请求的生命周期中使用流(实际的app.post路由,而不是中间件)?

客户端可能还会上传多个文件。因此,我需要任何解决方案来处理多个文件。

2 个答案:

答案 0 :(得分:1)

像这样将输入流通过管道传递到PassThrough流有意义吗?

const Busboy = require('busboy')
const { PassThrough } = require('stream')

const multipart = (req, res, next) => {
  req.files = new Map()
  req.fields = new Map()

  const busboy = new Busboy({ headers: req.headers })

  busboy.on('file', (fieldname, file, filename, encoding, mimetype) => {
    const stream = new PassThrough()
    file.pipe(stream)
    req.files.set(fieldname, { stream, filename, encoding, mimetype })
  })

  busboy.on(
    'field',
    (fieldname, val, fieldnameTruncated, valTruncated, encoding, mimetype) => {
      req.fields.set(fieldname, { val, encoding, mimetype })
    }
  )

  busboy.on('error', (error) => {
    next(error)
  })

  busboy.on('finish', () => {
    next()
  })

  busboy.end(req.rawBody)
}

答案 1 :(得分:-1)

如果您想在一个请求中处理多个文件,该过程将有些棘手。

Busboy通过单个流,并在文件到达时(按顺序)触发事件。您无法使用Busboy同时获得所有文件的单独流。这不是库的限制,而是HTTP的工作方式。

您最好的选择是将所有文件存储在一个临时存储器中,并使用res.locals保留下一个中间件的信息:

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

module.exports = (req, res, next) => {
  res.locals.files = {};
  // You need to ensure the directory exists
  res.locals.someTemporaryDirectory = '/some/temp/dir/with/randomString/in/it';

  let busboy;
  try {
    busboy = new Busboy({
      headers: req.headers
    });
  } catch (e) {
    return next(e);
  }

  busboy.on("file", (fieldname, file, filename, encoding, mimetype) => {
    res.locals.files[fieldname + '_' + filename] = {
      filename,
      encoding,
      mimetype
    };
    // I skipped error handling for the sake of simplicity. Cleanup phase will be required as well 
    const tempFilePath = path.join(res.locals.someTemporaryDirectory, fieldname + '_' + filename);
    file.pipe(fs.createWriteStream(tempFilePath));
  });

  busboy.on("finish", next);

  req.pipe(busboy);
};

下一个中间件必须使用res.locals.someTemporaryDirectoryres.locals.files来管理其业务(这需要清理阶段)。

此解决方案看似次优,但HTTP确实如此。您可能想为每个文件发出一个单独的HTTP请求,但是我不建议您这样做,因为您会遇到很多其他问题(例如所有请求的同步+内存管​​理)。

无论采用什么解决方案,都需要弄脏您的手。