Node.js 中的图片上传大小限制
直接从手机传出的照片,往往比您预想的限制还要大。在 Node.js 环境中,最多有五个地方可能拒绝它,而每个地方的响应都不同。本页按请求经过的顺序逐一介绍这些地方,说明 Express 示例如何把 multer 的限制转换为干净的 JSON 413,并介绍 multer 的一个行为——它决定了服务器对于超大上传到底还是会读取多少数据。
各层的顺序
- 图片库对话框。在编辑器选项中设置
maxUploadFileSize: 4000000后,对话框会在发送之前拒绝更大的文件。一个 4.5 MB 的文件得到了 "big.jpg is 4.3 MB. The maximum allowed size is 3.8 MB." 的提示(对话框按二进制兆字节计算),并且没有发送任何请求。这只是为了方便用户。任何人都可以绕过对话框直接调用该 URL。 - 反向代理(如果有的话)。根据 nginx 文档,
client_max_body_size的默认值为 1m,因此在默认配置的 nginx 之后,2 MB 的照片根本到不了 Node。nginx 会返回它自己的 HTML 413 页面,对话框将其报告为上传失败。 - multer 的
limits,由您自己设置。它对fileSize、files、fields和parts的默认值都是Infinity,在使用内存存储时,这意味着整个文件都在内存中。 - 错误处理中间件,它把 multer 的错误转换为浏览器期望的 JSON。
- 示例自己的检查,位于
saveImage()中,同样是 4,000,000 字节,只有在 multer 的限制被调高时才会起作用。
要让用户看到的是示例的消息,前面的每一层都必须允许略多于 4,000,000 字节的数据。multipart 编码会在文件周围增加几百个字节。
multer 的限制
// multer is the multipart parser most Express apps already use. Memory storage keeps the
// file in req.file.buffer, so its bytes can be checked before anything touches the disk;
// the limits keep that buffer small. multer allows exactly fileSize bytes and fails with
// LIMIT_FILE_SIZE above that (turned into a 413 in the error handler below).
const parseForm = multer({
storage: multer.memoryStorage(),
limits: { fileSize: MAX_BYTES, files: 1, fields: 10, fieldSize: 1024, parts: 12 },
defParamCharset: "utf8", // file names are sent as UTF-8; multer's default is latin1
});fileSize 是允许的最大大小,而不是第一个被拒绝的大小。我们发送了一个恰好 4,000,000 字节的文件,它被保存了。4,000,001 字节则被拒绝。设置其他限制是因为内存存储会保留所有内容:一个文件、最多十个且每个不超过 1,024 字节的文本字段,总共十二个部分。multer 默认的 fieldSize 是每个文本字段 1 MB。
从 MulterError 到 413
当文件超过 fileSize 时,multer 会以一个 MulterError 调用 next(err)。它的 code 为 "LIMIT_FILE_SIZE",message 为 "File too large",field 为 "file"。没有错误处理程序时,我们得到的是 Express 的默认行为:状态码 500 加一个 HTML 页面,并且除非设置了 NODE_ENV=production,该页面还会包含堆栈跟踪。示例的错误处理中间件会检查错误代码:
// Express 5 sends errors thrown in async handlers here, so the routes need no try/catch.
app.use((err, req, res, next) => {
if (err instanceof GalleryError) {
return res.status(err.status).json({ error: err.message });
}
if (err instanceof multer.MulterError) {
const tooBig = err.code === "LIMIT_FILE_SIZE";
return res
.status(tooBig ? 413 : 400)
.json({ error: tooBig ? "The image is larger than 4,000,000 bytes." : "Invalid upload: " + err.message });
}
console.error(err);
res.status(500).json({ error: "Server error." });
});其他所有 multer 错误(第二个文件对应 LIMIT_FILE_COUNT,以其他字段名发送的文件对应 LIMIT_UNEXPECTED_FILE)都会变成带有 multer 消息的 400。请检查 err.code,而不是消息文本。
multer 会先读完剩余的请求体
multer 并不会在超出限制的那一刻就发送 413。出错时,它会先读取并丢弃请求的剩余部分,然后才调用 next(err)。multer 源代码中的一条注释说明了原因:在客户端仍在发送数据时关闭连接,会导致客户端出现 EPIPE 错误。我们实测过:对于一个不带 Content-Length 发送的 20 MB 上传,客户端发送完全部 20,000,000 字节之后,413 才返回。而在达到限制时就停止读取的 Next.js 示例,大约在 4.9 MB 之后就作出了响应。
内存是安全的,因为多余的字节会被丢弃。但带宽和时间并非如此:一个 1 GB 的上传会被一直读到结尾。如果这一点很重要,请在 Node 之前(在反向代理上)拒绝过大的请求体,或者在 multer 之前用一个中间件检查 req.headers["content-length"]。仅检查 Content-Length 无法覆盖分块传输的请求,因为它们不带长度。
测试
用 npm start 启动本指南的示例项目,生成一个超过限制的文件并发送它:
head -c 4500000 /dev/zero > big.jpg
curl -F "file=@big.jpg" http://localhost:5101/rte/upload
{"error":"The image is larger than 4,000,000 bytes."}状态码为 413。这个文件全是零字节,因此也无法通过 JPEG 签名检查,但 multer 的限制先生效。要观察对话框自己的检查,请通过图片库的 Upload 按钮选择一个超过 4,000,000 字节的文件。对话框会显示大小提示,并且不发送任何内容。
其他平台的同一指南
Next.js · PHP · ASP.NET Core · MVC 5 · Web Forms