IIS 上の ASP.NET Web Forms でのアップロードのサイズ制限
IIS 上の ASP.NET Web Forms のアップロードは、コードに届く前に 2 つのサイズ制限を通過します。この 2 つは単位が異なり、それぞれ異なるエラーページを返しますが、どちらも JSON ではありません。このページでは、大きな画像を拒否できるすべてのレイヤー、サンプルで使っている設定、そしてすべての拒否を JSON で返すための 2 つの追加要素(Global.asax のフックと httpErrors の 1 行)を紹介します。2 つ目の要素が関係するのはリモートの訪問者だけです。
各レイヤーとその順序
- ギャラリーダイアログ。
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 と照合します。 - ASP.NET:
<httpRuntime maxRequestLength>。単位はキロバイトです。既定値は 4096、つまり 4,194,304 バイトです(当社のマシンではnew HttpRuntimeSection().MaxRequestLengthが 4096 を出力しました)。本文が最初に読み取られるときに適用されます。 - ハンドラー独自のチェック:ファイルごとに 4,000,000 バイトで、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>どちらも同じ 5,242,880 バイトに設定しており、一方は 5120 KB、もう一方はバイトで記述しています。既定の 4096 KB でも、4,000,000 バイトの画像と数百バイトのマルチパートのオーバーヘッドは収まります。余裕を持たせているのは、少しだけ大きすぎるファイルでもハンドラーに届き、それに応じたメッセージを受け取れるようにするためです。
各レイヤーが返すもの
| アップロード | 拒否するもの | 追加要素がない場合のレスポンス |
|---|---|---|
| 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,000 に引き上げた場合) | ASP.NET | HTML の 500「Maximum request length exceeded.」 |
古いガイドでは、IIS による拒否は 404.13 と説明されています。IIS 7 と 8 ではそのコードが使われていましたが、ここでは IIS Express 10 は 413.1 を返しました。サンプルはどちらにも対応しています。
IIS のページを JSON に変える
要求フィルタリングはハンドラーが実行される前にリクエストを拒否しますが、マネージドの Application_EndRequest イベントはそのリクエストでも発生します。
// 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.");
}
}サブステータスのチェックが重要です。最初のバージョンでは /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.」に置き換えられました。次のブロックを追加すると解決し、契約テストにも引き続き合格しました。
<!-- /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 に変換します。ASP.NET の制限が先に適用されるよう IIS の制限を引き上げた状態では、6 MB のアップロードは HTML の 500 ではなくその JSON を受け取りました。サンプルに含まれる設定のままであれば、この経路を通るのは Content-Length ヘッダーのない(チャンク形式の)アップロードだけのはずです。これはテストしていません。
テストする
このガイドのサンプルプロジェクトをポート 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."}どちらも JSON 付きの HTTP 413 です。1 つ目はハンドラーから、2 つ目は Global.asax のフックから返されたものです。Global.asax を削除してもう一度 huge.jpg を送信すると、IIS の 413.1 のページを確認できます。
ほかのプラットフォーム向けの同じガイド
JavaScript · Next.js · PHP · ASP.NET Core · MVC 5