ASP.NET Core 中的上传大小限制:Kestrel、IIS 和 MVC
在 ASP.NET Core 中,一张大照片可能在六个地方被拒绝,而只有最后一个是您自己的代码。其他几层各有各的响应方式:不带响应体的 413、MVC 返回的 ProblemDetails 400、IIS 错误页面,或者浏览器报告为网络错误的连接中断。本页按请求经过的顺序列出这些层,并附上我们测试时每一层的实际响应。
各层的顺序
- 编辑器。在选项中设置
maxUploadFileSize: 4000000后,图片库会在发送之前拒绝更大的文件。一个 4.5 MB 的文件得到了 "big.jpg is 4.3 MB. The maximum allowed size is 3.8 MB." 的提示。对话框按二进制兆字节计算。服务器不能依赖这一层。 - IIS(如果应用以进程内方式运行在 IIS 之后)。根据 Microsoft 文档,请求筛选会在 ASP.NET Core 运行之前,以 IIS 自己的 404.13 页面拒绝超过
maxAllowedContentLength(以字节为单位,默认 30,000,000)的请求体。本页没有测试 IIS。 - Kestrel 的
MaxRequestBodySize,默认 30,000,000 字节。一次 31,000,000 字节的上传得到了 413。 - 路由自己的限制。
[RequestSizeLimit]为单个操作方法替换 Kestrel 的限制。在 Minimal API 中,同一个特性作为端点元数据生效。 - multipart 解析器。
FormOptions.MultipartBodyLengthLimit默认为 134,217,728 字节。可以用[RequestFormLimits]为单个操作方法设置,或用.WithFormOptions为单个端点设置。 - 您自己的检查:示例中为 4,000,000 字节,超出时返回 JSON 格式的 413。
代码
public const long MaxFileBytes = 4_000_000;
// The request is a little bigger than the file: multipart boundaries, headers, the "folder" field.
public const long MaxRequestBytes = MaxFileBytes + 64 * 1024;请求限制等于文件限制加上 64 KB,用于容纳 multipart 边界和 folder 字段。这个差额是有意留出的:略超过 4,000,000 字节的文件仍然能到达代码,并得到一个可读的响应。
// POST /rte/upload file, folder
[HttpPost("upload")]
[RequestSizeLimit(RteFiles.MaxRequestBytes)] // the whole request body
[RequestFormLimits(MultipartBodyLengthLimit = RteFiles.MaxRequestBytes)] // the multipart parser
public async Task<IActionResult> Upload(IFormFile? file, [FromForm] string? folder) =>
Send(await RteFiles.Save(HttpContext, file, folder));rte.MapPost("/upload", async (HttpContext http, IFormFile? file, [FromForm] string? folder) =>
Json(await RteFiles.Save(http, file, folder)))
.DisableAntiforgery()
// Kestrel accepts 30,000,000-byte bodies by default; this route needs ~4 MB.
.WithMetadata(new RequestSizeLimitAttribute(RteFiles.MaxRequestBytes))
.WithFormOptions(multipartBodyLengthLimit: RteFiles.MaxRequestBytes);浏览器看到什么
当请求超过路由限制时,Kestrel 会在浏览器仍在发送数据时返回 413 并关闭连接。我们在全部四个 ASP.NET Core 示例中,从 Chromium 用 XHR 发送了 10 MB 数据(绕过编辑器自身的检查)。每一次 XHR 都以状态 0 进入 onerror,从未得到 413。此时 rte-files.js 会报告 "The server could not be reached."。而会等待响应的 curl 确实看到了 413。因此,对于大文件,能给用户有用提示的是编辑器的 maxUploadFileSize。请让它与服务器的限制保持一致。
MVC 返回 400 而不是 413
在 Minimal API 中,超过路由限制的请求体会在参数绑定时失败,端点返回不带响应体的 413。而在 [ApiController] 中,它会在模型绑定时失败,并由自动模型验证返回 ProblemDetails 400:
{"type":"https://tools.ietf.org/html/rfc9110#section-15.5.1","title":"One or more validation errors occurred.","status":400,
"errors":{"":["Failed to read the request form. Request body too large. The max request body size is 4065536 bytes."]}}一个 4,100,000 字节的文件也得到了同样的响应,因为加上 multipart 封装后,请求刚好超过 4,065,536 字节。要得到示例自身检查返回的 {"error": ...} 413,整个请求必须在路由限制以内,4,050,000 字节的文件就满足这一点。
关闭请求限制后,超过 MultipartBodyLengthLimit 时,Minimal API 端点同样返回 400(以默认值 134,217,728 测试,上传 135,000,000 字节)。
提高限制
同时修改 RteFiles.cs 中的 MaxFileBytes 和页面上的 maxUploadFileSize。MaxRequestBytes 跟随 MaxFileBytes 变化,因此路由限制和 multipart 限制也会随之调整。超过 30,000,000 字节时,前面的每一层也都必须允许:Kestrel 的全局限制仍然适用于其他路由,而根据 Microsoft 文档,IIS 需要在 web.config 中调高 maxAllowedContentLength,因为 [RequestSizeLimit] 不能超过它。
测试
用 dotnet run 运行本指南的示例项目(端口 5302),并同时运行 Minimal API 示例(端口 5301):
head -c 4050000 /dev/zero > big.jpg
curl -F "file=@big.jpg" http://localhost:5302/rte/upload
{"error":"The image is larger than 4 MB."}
head -c 10000000 /dev/zero > huge.jpg
curl -s -w "\nHTTP %{http_code}\n" -F "file=@huge.jpg" http://localhost:5301/rte/upload
HTTP 413第一个响应是示例检查返回的 413,两个端口都一样。第二个是 Kestrel 返回的不带响应体的 413。把 huge.jpg 发送到端口 5302,就能看到上文所述 MVC 返回的 400。
其他平台的同一指南
JavaScript · Next.js · PHP · MVC 5 · Web Forms