Limites de tamanho de upload no ASP.NET Web Forms no IIS
No IIS, um upload do ASP.NET Web Forms passa por dois limites de tamanho antes de o seu código vê-lo. Eles usam unidades diferentes, e cada um gera uma página de erro diferente, nenhuma delas em JSON. Esta página lista todas as camadas que podem recusar uma imagem grande, as configurações que o exemplo usa e as duas peças extras (um hook no Global.asax e uma linha em httpErrors) que fazem toda rejeição voltar como JSON. A segunda peça só importa para visitantes remotos.
As camadas, em ordem
- A caixa de diálogo da galeria. Com
maxUploadFileSize: 4000000, ela recusa um arquivo maior antes de enviar qualquer coisa. Um arquivo de 4,500,008 bytes recebeu "big.png is 4.3 MB. The maximum allowed size is 3.8 MB." A caixa de diálogo conta megabytes binários, e nenhuma requisição chegou ao servidor. Isso é uma conveniência, e o servidor não pode depender dela. - A filtragem de requisições do IIS:
maxAllowedContentLength, em bytes. O padrão é 30,000,000. O IIS compara esse valor com o Content-Length antes de o ASP.NET rodar. - O ASP.NET:
<httpRuntime maxRequestLength>, em kilobytes. O padrão é 4096, que equivale a 4,194,304 bytes (new HttpRuntimeSection().MaxRequestLengthimprimiu 4096 na nossa máquina). Ele é aplicado quando o corpo é lido pela primeira vez. - A verificação do próprio handler: 4,000,000 bytes por arquivo, respondida com um JSON 413.
<!-- 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" /><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>Os dois estão definidos com os mesmos 5,242,880 bytes, uma vez como 5120 KB e outra em bytes. O padrão de 4096 KB também comportaria uma imagem de 4,000,000 bytes mais as poucas centenas de bytes de overhead do multipart. A folga extra faz com que um arquivo um pouco grande demais ainda chegue ao handler e receba a mensagem específica dele.
O que cada camada devolve
| Upload | Recusado por | Resposta sem as peças extras |
|---|---|---|
| 4,500,008 bytes | o handler | 413 {"error":"The image is larger than the 4 MB (4,000,000 bytes) limit."} |
| 6,000,008 bytes | filtragem de requisições do IIS | 413.1 "Request Entity Too Large", uma página HTML do IIS |
6,000,008 bytes, com maxAllowedContentLength aumentado para 50,000,000 | ASP.NET | HTML 500 "Maximum request length exceeded." |
Guias mais antigos descrevem a rejeição do IIS como 404.13. O IIS 7 e 8 usavam esse código, mas o IIS Express 10 retornou 413.1 aqui. O exemplo trata os dois.
Como transformar a página do IIS em JSON
A filtragem de requisições rejeita a requisição antes de qualquer handler rodar, mas o evento gerenciado Application_EndRequest ainda dispara para ela:
// Optional: makes "request too large" from IIS come back as JSON on /rte/ routes.
void Application_EndRequest(object sender, EventArgs e)
{
RteFiles.RewriteTooLarge(Context);
}// 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.");
}
}A verificação do substatus importa. Nossa primeira versão capturava todo 413 em /rte/ e sobrescrevia a mensagem 413 do próprio handler, que é mais precisa.
Isso funcionou localmente e ainda falhava para visitantes remotos. O IIS Express e o IIS usam por padrão errorMode="DetailedLocalOnly". Definimos errorMode="Custom", que é o que um visitante remoto recebe. O JSON gravado no EndRequest foi então substituído pela mensagem do IIS "The page was not displayed because the request entity is too large." Adicionar este bloco resolveu o problema, e o teste de contrato continuou passando:
<!-- /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>O limite do ASP.NET é tratado no próprio handler de upload. Request.Files é lido dentro de um try, e uma HttpException com WebEventCodes.RuntimeErrorPostTooLarge vira um JSON 413. Com o limite do IIS aumentado para que o limite do ASP.NET fosse aplicado primeiro, um upload de 6 MB recebeu esse JSON em vez do HTML 500. Com as configurações que vêm no exemplo, só um upload sem cabeçalho Content-Length (chunked) deve seguir esse caminho. Não testamos esse caso.
Teste
Com o projeto de exemplo deste guia rodando na porta 5401 (run.ps1):
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."}As duas são HTTP 413 com JSON. A primeira veio do handler e a segunda do hook no Global.asax. Exclua o Global.asax e envie huge.jpg de novo para ver a página 413.1 do IIS.
O mesmo guia para outras plataformas
JavaScript · Next.js · PHP · ASP.NET Core · MVC 5