IIS 上 ASP.NET Web Forms 的上传大小限制

Web Forms 中可能拒绝大文件上传的各层示意图:图片库对话框、IIS 请求筛选、ASP.NET httpRuntime 和上传处理程序

在 IIS 上,ASP.NET Web Forms 的上传在到达您的代码之前,要先经过两道大小限制。它们使用不同的单位,各自产生不同的错误页面,而且都不是 JSON。本页列出可能拒绝大图片的每一层、示例使用的设置,以及让每次拒绝都以 JSON 返回的两项额外配置(一个 Global.asax 钩子和一行 httpErrors 配置)。第二项只对远程访问者有影响。

各层的顺序

  1. 图片库对话框。设置 maxUploadFileSize: 4000000 后,它会在发送任何内容之前拒绝更大的文件。一个 4,500,008 字节的文件得到了 "big.png is 4.3 MB. The maximum allowed size is 3.8 MB." 的提示。对话框按二进制兆字节计算,并且没有请求到达服务器。这只是为了方便用户,服务器不能依赖它。
  2. IIS 请求筛选:maxAllowedContentLength,单位为字节。默认值为 30,000,000。IIS 在 ASP.NET 运行之前根据 Content-Length 进行检查。
  3. ASP.NET:<httpRuntime maxRequestLength>,单位为千字节。默认值为 4096,即 4,194,304 字节(在我们的机器上,new HttpRuntimeSection().MaxRequestLength 输出了 4096)。它在首次读取请求体时生效。
  4. 处理程序自己的检查:每个文件 4,000,000 字节,超出时返回 JSON 格式的 413。
XML · web.config
<!-- maxRequestLength is in KILOBYTES (default 4096 = 4,194,304 bytes). 5120 KB = 5,242,880
     bytes: comfortably above the 4,000,000-byte image limit plus multipart overhead, so a
     file that is slightly too big still reaches the handler and gets its JSON 413. -->
<httpRuntime targetFramework="4.8" maxRequestLength="5120" />
XML · web.config
<requestFiltering>
  <!-- maxAllowedContentLength is in BYTES (default 30,000,000). Same 5,242,880 bytes as
       maxRequestLength above. IIS checks this one first, before ASP.NET runs. -->
  <requestLimits maxAllowedContentLength="5242880" />
</requestFiltering>

两者都设置为相同的 5,242,880 字节,一处写成 5120 KB,另一处写成字节数。默认的 4096 KB 其实也能容纳一张 4,000,000 字节的图片加上几百字节的 multipart 开销。留出额外的空间,是为了让稍微超大的文件仍然能到达处理程序,并得到相应的具体消息。

每一层返回什么

上传拒绝者没有额外配置时的响应
4,500,008 字节处理程序413 {"error":"The image is larger than the 4 MB (4,000,000 bytes) limit."}
6,000,008 字节IIS 请求筛选413.1 "Request Entity Too Large",一个 IIS HTML 页面
6,000,008 字节,maxAllowedContentLength 提高到 50,000,000ASP.NETHTML 500 "Maximum request length exceeded."

较早的指南把 IIS 的拒绝描述为 404.13。IIS 7 和 8 确实使用这个代码,但这里的 IIS Express 10 返回的是 413.1。示例两者都能处理。

把 IIS 页面转换为 JSON

请求筛选在任何处理程序运行之前就拒绝了请求,但托管的 Application_EndRequest 事件仍会为它触发:

C# · Global.asax
// Optional: makes "request too large" from IIS come back as JSON on /rte/ routes.
void Application_EndRequest(object sender, EventArgs e)
{
    RteFiles.RewriteTooLarge(Context);
}
C# · App_Code/RteFiles.cs
// Call from Application_EndRequest (Global.asax). A body over maxAllowedContentLength is
// rejected by IIS request filtering before any handler runs - HTTP 413.1 with an HTML
// page (404.13 on IIS 7/8) - but EndRequest still runs, so the /rte/ routes can answer
// in JSON instead. The substatus check leaves the handlers' own 413s alone.
public static void RewriteTooLarge(HttpContext context)
{
    HttpResponse response = context.Response;
    bool rejectedByIis = (response.StatusCode == 413 && response.SubStatusCode == 1)
        || (response.StatusCode == 404 && response.SubStatusCode == 13);
    if (rejectedByIis
        && context.Request.AppRelativeCurrentExecutionFilePath.StartsWith("~/rte/", StringComparison.OrdinalIgnoreCase))
    {
        WriteError(context, 413, "The upload is larger than the server allows.");
    }
}

检查子状态码很重要。我们的第一个版本匹配 /rte/ 上的所有 413,结果覆盖了处理程序自己返回的、更精确的 413 消息。

这在本地有效,但对远程访问者仍然失败。IIS Express 和 IIS 默认使用 errorMode="DetailedLocalOnly"。我们设置了 errorMode="Custom",也就是远程访问者得到的模式。结果,在 EndRequest 中写入的 JSON 被替换成了 IIS 的 "The page was not displayed because the request entity is too large."。添加下面的配置块后问题得到解决,契约测试也仍然通过:

XML · web.config
<!-- /rte/ always answers in JSON. With errorMode="Custom" (what remote visitors get by
     default) IIS would otherwise swap its own HTML page in for the 413 that Global.asax
     writes when request filtering rejects an oversized upload. -->
<location path="rte">
  <system.webServer>
    <httpErrors existingResponse="PassThrough" />
  </system.webServer>
</location>

ASP.NET 的限制在上传处理程序内部处理。Request.Files 在 try 中读取,带有 WebEventCodes.RuntimeErrorPostTooLarge 的 HttpException 会变成 JSON 413。在提高 IIS 限制、让 ASP.NET 的限制先生效的情况下,一次 6 MB 的上传得到的是这个 JSON,而不是 HTML 500。使用示例自带的设置时,应该只有不带 Content-Length 标头的上传(分块传输)才会走这条路径。我们没有测试这种情况。

测试

在端口 5401 上运行本指南的示例项目(run.ps1),然后执行:

Shell
head -c 4500000 /dev/zero > four.jpg     # then make it start like a JPEG:
printf '\xff\xd8\xff' | cat - four.jpg > big.jpg
curl -F "file=@big.jpg" http://localhost:5401/rte/upload
{"error":"The image is larger than the 4 MB (4,000,000 bytes) limit."}

head -c 6000000 /dev/urandom > huge.jpg
curl -F "file=@huge.jpg" http://localhost:5401/rte/upload
{"error":"The upload is larger than the server allows."}

两者都是带 JSON 的 HTTP 413。第一个来自处理程序,第二个来自 Global.asax 钩子。删除 Global.asax 后再次发送 huge.jpg,就能看到 IIS 的 413.1 页面。

其他平台的同一指南

JavaScript · Next.js · PHP · ASP.NET Core · MVC 5

延伸阅读