IIS 上の ASP.NET MVC 5 でのアップロードのサイズ制限
IIS 上の ASP.NET MVC 5 のアクションに送られた大きすぎる画像は、4 か所で止められる可能性があります。そのうち 2 か所は HTML で応答し、そのうち 1 か所はアクションが選ばれる前に発生するため、コントローラーの try/catch では捕捉できません。このページでは、各レイヤー、バイト単位とキロバイト単位の web.config の値、そしてすべての拒否をエディター向けの JSON の 413 にする Global.asax.cs の 2 つのイベントを紹介します。
各レイヤー
- ギャラリーダイアログ。ビューで
maxUploadFileSize: 4000000を指定すると、ダイアログはそれより大きなファイルを送信前に拒否し、「big.png is 4.3 MB. The maximum allowed size is 3.8 MB.」と表示します。ダイアログは 2 進数のメガバイトで計算し、リクエストは送信されませんでした。これは利便性のための機能です。ほかの場所やほかのクライアントからのアップロードは、依然としてサーバーに届きます。 - IIS の要求フィルタリング。
maxAllowedContentLengthはバイト単位です。既定値は 30,000,000 で、ASP.NET が起動する前に Content-Length と照合されます。 - ASP.NET。
<httpRuntime maxRequestLength>はキロバイト単位です。既定値は 4096 KB、つまり 4,194,304 バイトです。本文を最初に読み取ったときに発生します。 - RteFiles.SaveUpload: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、もう一方はバイトで記述しています。4,000,000 バイトから約 5.2 MB までのファイルはアクションに届き、それに応じたメッセージを受け取ります。
意外な点:[HttpPost] が本文を読み取る
ASP.NET の制限が先に適用されるよう maxAllowedContentLength を 50,000,000 に引き上げ、アクション内のファイルへのアクセスを try/catch で囲みました。それでも、6 MB のアップロードは HTML の 500「Maximum request length exceeded.」を返しました。スタックトレースを見ると理由がわかります。
[HttpException (0x80004005): Maximum request length exceeded.]
System.Web.HttpRequest.GetEntireRawContent()
System.Web.HttpRequest.FillInFormCollection()
System.Web.HttpRequest.get_Form()
System.Web.Mvc.HttpRequestExtensions.GetHttpMethodOverride(HttpRequestBase request)
System.Web.Mvc.AcceptVerbsAttribute.IsValidForRequest(...)
System.Web.Mvc.ActionMethodSelectorBase.RunSelectionFilters(...)[HttpPost] は、MVC がまだアクションを選んでいる段階で、X-HTTP-Method-Override というフォームフィールドを探します。Request.Form を読み取ると本文全体が読み取られ、そこで maxRequestLength が例外をスローします。この時点では、アクションもアクションフィルターもまだ実行されていません。それでも Application_Error はこれを捕捉できます。
// Optional, both below: oversized uploads answer in JSON on /rte/ like every other error.
//
// IIS rejects bodies over maxAllowedContentLength before MVC runs (HTTP 413.1, HTML page),
// but EndRequest still runs.
protected void Application_EndRequest(object sender, EventArgs e)
{
RteFiles.RewriteTooLarge(new HttpContextWrapper(Context));
}
// A body over <httpRuntime maxRequestLength> (reachable when maxAllowedContentLength is
// the larger of the two, or for a chunked upload) throws "Maximum request length
// exceeded" when MVC first reads the form: during action selection ([HttpPost] looks
// for an X-HTTP-Method-Override form field), before any action or filter can catch it.
protected void Application_Error(object sender, EventArgs e)
{
HttpException ex = Server.GetLastError() as HttpException;
if (ex != null && ex.WebEventCode == WebEventCodes.RuntimeErrorPostTooLarge
&& Request.AppRelativeCurrentExecutionFilePath.StartsWith("~/rte/", StringComparison.OrdinalIgnoreCase))
{
Server.ClearError();
RteFiles.WriteError(new HttpContextWrapper(Context), 413, "The upload is larger than the server allows.");
}
}これを追加すると、同じ 6 MB のアップロードは 413 {"error":"The upload is larger than the server allows."} を返しました。
IIS 独自の 413.1
サンプルに含まれる値のままでは、6 MB のアップロードは ASP.NET に届きません。IIS の要求フィルタリングが HTML ページで 413.1「Request Entity Too Large」を返します(IIS 7 と 8 ではこれに 404.13 が使われており、サンプルはどちらにも対応しています)。上記と同じリージョンにある Application_EndRequest は、このリクエストでも実行されます。RteFiles.RewriteTooLarge はサブステータスをチェックするため、アクション自体の 413 のメッセージはそのまま残ります。
// Call from Application_EndRequest (Global.asax.cs). 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(HttpContextBase context)
{
HttpResponseBase 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.");
}
}自分のマシンでは、これで十分です。IIS の既定の errorMode="DetailedLocalOnly" では、リモートの訪問者にはカスタムエラーページが表示されます。同じ web.config を持つ Web Forms のサンプルで、リモートの訪問者の代わりとして errorMode="Custom" を設定したところ、IIS は結局 JSON を独自の「request entity is too large」のページに置き換えました。次の location ブロックは、/rte/ に限って JSON をそのまま通します。
<!-- /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.cs writes when request filtering rejects an oversized upload. -->
<location path="rte">
<system.webServer>
<httpErrors existingResponse="PassThrough" />
</system.webServer>
</location>テストする
このガイドのサンプルプロジェクトをポート 5402 で実行した状態で、以下を実行します。
head -c 4500000 /dev/zero > four.jpg
printf '\xff\xd8\xff' | cat - four.jpg > big.jpg
curl -F "file=@big.jpg" http://localhost:5402/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:5402/rte/upload
{"error":"The upload is larger than the server allows."}どちらも JSON 付きの HTTP 413 です。1 つ目は RteFiles から、2 つ目は IIS の 413.1 の後で Application_EndRequest から返されたものです。アクションの選択時のケースを確認するには、maxAllowedContentLength を 5,242,880 より大きくしてから、もう一度 huge.jpg を送信します。Application_Error がそれに応答します。
ほかのプラットフォーム向けの同じガイド
JavaScript · Next.js · PHP · ASP.NET Core · Web Forms