Next.js 中的图片上传大小限制
在 Next.js 路由处理程序中,request.formData() 是文档推荐的读取上传的方式,而 Next.js 中没有任何机制限制上传的大小。本页介绍我们的实测结果、示例改用的小函数 readForm(),以及可能在您的代码之前就拒绝大图片的其他各层。
formData() 会读取所有内容
我们添加了一个临时路由处理程序,它只执行 await request.formData(),然后向它发送了一个 50 MB 的文件。它返回了 200,fileSize: 50000000,而在这一个请求期间,开发服务器的内存(RSS)增长了约 175 MB。路由处理程序文档介绍了 formData(),但没有提到任何大小限制。文档指出,与 Pages Router 的 API 路由不同,路由处理程序不需要配置 bodyParser。因此没有可以设置的配置值,任何限制都必须写在您的代码中。
在解析之前计算字节数
lib/gallery.ts 中的 readForm() 会拒绝声明的 Content-Length 超过限制的请求,否则就读取请求体流,并在超过限制时立即停止。只有不超过限制的请求体才会通过一个 Response 交给标准解析器:
// request.formData() reads the whole body into memory, however large. This reads at most
// MAX_BYTES plus room for the other fields, answers 413 above that, then parses it.
export async function readForm(request: Request): Promise<FormData> {
const limit = MAX_BYTES + 64 * 1024;
if (Number(request.headers.get("content-length")) > limit) {
throw new GalleryError(413, TOO_BIG);
}
const chunks: Uint8Array[] = [];
let size = 0;
const reader = request.body?.getReader();
while (reader) {
const { done, value } = await reader.read();
if (done) break;
size += value.length;
if (size > limit) {
await reader.cancel();
throw new GalleryError(413, TOO_BIG);
}
chunks.push(value);
}
const body = new Response(Buffer.concat(chunks), {
headers: { "content-type": request.headers.get("content-type") ?? "" },
});
try {
return await body.formData();
} catch {
throw new GalleryError(400, "Expected a multipart/form-data request.");
}
}限制值为 4,000,000 字节的文件大小,再加上 64 KB,用于容纳表单字段及其周围的 multipart 标头。仅检查 Content-Length 是不够的:分块传输的请求不带长度,我们就发送过这样的请求。流式计数拦住了它:在 next dev 和 next start 中,客户端发送了 20 MB 请求体中的约 4.9 MB 后,413 就返回了,客户端也没有出现连接错误。reader.cancel() 会停止读取,因此剩余部分永远不会被缓冲。作为对比,使用 multer 的 Express 读完了全部 20 MB 才作出响应。
解析之后,saveImage() 会检查文件本身。正是这项检查以精确的 4,000,000 字节为界:这个大小的文件被保存了,而 4,000,001 字节的文件以 413 被拒绝。
其他各层
- 图片库对话框。编辑器选项中的
maxUploadFileSize: 4000000让对话框在发送任何内容之前就拒绝更大的文件,并显示 "big.jpg is 4.3 MB. The maximum allowed size is 3.8 MB."。服务器不能依赖这一层。 - 反向代理。根据 nginx 文档,
client_max_body_size的默认值为 1m。在默认配置的 nginx 之后,2 MB 的照片会得到 nginx 自己的 413 页面,根本到不了 Next.js。 proxy文件(以前称为 middleware)。Next.js 文档说明,使用它时,Next.js 会缓冲请求体,以便代理和路由都能读取,上限为experimental.proxyClientMaxBodySize,默认 10 MB。超过这个值时,请求体会在限制处被截断,并记录一条警告。示例没有 proxy 文件,因此我们没有测试这种情况。按照这一描述,如果您添加了 proxy 文件,超过 10 MB 的请求体会被截断后送到路由处理程序,而不是被拒绝。- Server Actions 有自己的
serverActions.bodySizeLimit,根据文档默认为 1 MB。这个设置只针对 Server Actions,对路由处理程序不起作用,而编辑器的上传发送到的是路由处理程序。
顶部的示意图展示了这些层的顺序。要让用户得到的是示例返回的 413,前面的每一层都必须允许略多于 4,000,000 字节的数据。
浏览器看到什么
示例的每一次拒绝都是带有 {"error":"The image is larger than 4,000,000 bytes."} 的 HTTP 413。对于任何失败的上传,图片库对话框都显示自己的文本 "Upload failed for <name>.",因此在启用对话框自身的大小检查时,用户通常看到的是对话框的消息。
测试
用 npm run build 和 npm start 启动本指南的示例项目,然后执行:
head -c 4500000 /dev/zero > big.jpg
curl -F "file=@big.jpg" http://localhost:5102/rte/upload
{"error":"The image is larger than 4,000,000 bytes."}状态码为 413。要尝试分块传输的情况,请发送不带长度的请求体,例如使用 fetch 和 ReadableStream 请求体(duplex: "half")。响应同样是 413,而且会在上传完成之前到达。
其他平台的同一指南
JavaScript · PHP · ASP.NET Core · MVC 5 · Web Forms