我正尝试使用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
路由,而不是中间件)?
客户端可能还会上传多个文件。因此,我需要任何解决方案来处理多个文件。
答案 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.someTemporaryDirectory
和res.locals.files
来管理其业务(这需要清理阶段)。
此解决方案看似次优,但HTTP确实如此。您可能想为每个文件发出一个单独的HTTP请求,但是我不建议您这样做,因为您会遇到很多其他问题(例如所有请求的同步+内存管理)。
无论采用什么解决方案,都需要弄脏您的手。