IIS 上の ASP.NET Web Forms でのアップロードのサイズ制限

Web Forms で大きなアップロードを拒否できるレイヤーの図。ギャラリーダイアログ、IIS の要求フィルタリング、ASP.NET の httpRuntime、アップロードハンドラー

IIS 上の ASP.NET Web Forms のアップロードは、コードに届く前に 2 つのサイズ制限を通過します。この 2 つは単位が異なり、それぞれ異なるエラーページを返しますが、どちらも JSON ではありません。このページでは、大きな画像を拒否できるすべてのレイヤー、サンプルで使っている設定、そしてすべての拒否を JSON で返すための 2 つの追加要素(Global.asax のフックと httpErrors の 1 行)を紹介します。2 つ目の要素が関係するのはリモートの訪問者だけです。

各レイヤーとその順序

  1. ギャラリーダイアログ。maxUploadFileSize: 4000000 を指定すると、それより大きなファイルを何も送信せずに拒否します。4,500,008 バイトのファイルでは「big.png is 4.3 MB. The maximum allowed size is 3.8 MB.」と表示されました。ダイアログは 2 進数のメガバイトで計算し、リクエストはサーバーに届きませんでした。これは利便性のための機能で、サーバーはこれに頼ることはできません。
  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 バイトの画像と数百バイトのマルチパートのオーバーヘッドは収まります。余裕を持たせているのは、少しだけ大きすぎるファイルでもハンドラーに届き、それに応じたメッセージを受け取れるようにするためです。

各レイヤーが返すもの

アップロード拒否するもの追加要素がない場合のレスポンス
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.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 に変換します。ASP.NET の制限が先に適用されるよう IIS の制限を引き上げた状態では、6 MB のアップロードは HTML の 500 ではなくその JSON を受け取りました。サンプルに含まれる設定のままであれば、この経路を通るのは 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 です。1 つ目はハンドラーから、2 つ目は Global.asax のフックから返されたものです。Global.asax を削除してもう一度 huge.jpg を送信すると、IIS の 413.1 のページを確認できます。

ほかのプラットフォーム向けの同じガイド

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

次に読む