ASP.NET Core 中的要求逾時中介軟體
作者:Tom Dykstra
應用程式可以選擇性地將逾時限制套用至要求。 ASP.NET 核心伺服器預設不會這麼做,因為要求處理時間會因情節而異。 例如,WebSockets、靜態檔案和呼叫昂貴的 API 都需要不同的逾時限制。 因此,ASP.NET Core 提供中介軟體,以設定每個端點的逾時以及全域逾時。
達到逾時限制時,HttpContext.RequestAborted 中的 CancellationToken 會將 IsCancellationRequested 設定為 true
。 Abort() 不會在要求上自動呼叫,因此應用程式仍可能會產生成功或失敗的回應。 如果應用程式未處理例外狀況並產生回應,則預設行為是傳回狀態碼 504。
本文說明如何設定逾時中介軟體。 逾時中介軟體可用於所有類型的 ASP.NET Core 應用程式:最小 API、具有控制器的 Web API、MVC 和 Razor 頁面。 範例應用程式是「最小 API」,但其他應用程式類型也支援它所說明的每個逾時功能。
要求逾時位於 Microsoft.AspNetCore.Http.Timeouts 命名空間中。
注意:當應用程式以偵錯模式執行時,逾時中介軟體不會觸發。 此行為與 Kestrel 逾時相同。 若要測試逾時,請執行應用程式,而不附加偵錯工具。
將中介軟體新增至應用程式
藉由呼叫 AddRequestTimeouts,將要求逾時中介軟體新增至服務集合。
透過呼叫 UseRequestTimeouts 將中介軟體新增至要求處理管道。
注意
- 在明確呼叫 UseRouting 的應用程式中,必須在
UseRouting
之後呼叫UseRequestTimeouts
。
將中介軟體新增至應用程式不會自動開始觸發逾時。 逾時限制必須明確設定。
設定一個端點或頁面
針對最少的 API 應用程式,呼叫 或套用 WithRequestTimeout [RequestTimeout]
屬性,將端點設定為逾時,如下列範例所示:
using Microsoft.AspNetCore.Http.Timeouts;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRequestTimeouts();
var app = builder.Build();
app.UseRequestTimeouts();
app.MapGet("/", async (HttpContext context) => {
try
{
await Task.Delay(TimeSpan.FromSeconds(10), context.RequestAborted);
}
catch (TaskCanceledException)
{
return Results.Content("Timeout!", "text/plain");
}
return Results.Content("No timeout!", "text/plain");
}).WithRequestTimeout(TimeSpan.FromSeconds(2));
// Returns "Timeout!"
app.MapGet("/attribute",
[RequestTimeout(milliseconds: 2000)] async (HttpContext context) => {
try
{
await Task.Delay(TimeSpan.FromSeconds(10), context.RequestAborted);
}
catch (TaskCanceledException)
{
return Results.Content("Timeout!", "text/plain");
}
return Results.Content("No timeout!", "text/plain");
});
// Returns "Timeout!"
app.Run();
對於具有控制器的應用程式,請將 [RequestTimeout]
屬性套用至動作方法或控制器類別。 針對 Razor 頁面應用程式,將屬性套用至 Razor 頁面類別。
設定多個端點或頁面
建立具名原則,以指定套用至多個端點的逾時設定。 藉由呼叫 AddPolicy 來新增原則:
builder.Services.AddRequestTimeouts(options => {
options.DefaultPolicy =
new RequestTimeoutPolicy { Timeout = TimeSpan.FromMilliseconds(1500) };
options.AddPolicy("MyPolicy", TimeSpan.FromSeconds(2));
});
您可以依原則名稱指定端點的逾時:
app.MapGet("/namedpolicy", async (HttpContext context) => {
try
{
await Task.Delay(TimeSpan.FromSeconds(10), context.RequestAborted);
}
catch (TaskCanceledException)
{
return Results.Content("Timeout!", "text/plain");
}
return Results.Content("No timeout!", "text/plain");
}).WithRequestTimeout("MyPolicy");
// Returns "Timeout!"
[RequestTimeout]
屬性也可用來指定具名原則。
設定全域預設逾時原則
指定全域預設逾時組態的原則:
builder.Services.AddRequestTimeouts(options => {
options.DefaultPolicy =
new RequestTimeoutPolicy { Timeout = TimeSpan.FromMilliseconds(1500) };
options.AddPolicy("MyPolicy", TimeSpan.FromSeconds(2));
});
預設逾時適用於未指定逾時的端點。 下列端點程式碼會檢查逾時,雖然它不會呼叫擴充方法或套用屬性。 全域逾時組態會套用,因此程式碼會檢查逾時:
app.MapGet("/", async (HttpContext context) => {
try
{
await Task.Delay(TimeSpan.FromSeconds(10), context.RequestAborted);
}
catch
{
return Results.Content("Timeout!", "text/plain");
}
return Results.Content("No timeout!", "text/plain");
});
// Returns "Timeout!" due to default policy.
在原則中指定狀態碼
RequestTimeoutPolicy 類別具有屬性,可在觸發逾時時自動設定狀態碼。
builder.Services.AddRequestTimeouts(options => {
options.DefaultPolicy = new RequestTimeoutPolicy {
Timeout = TimeSpan.FromMilliseconds(1000),
TimeoutStatusCode = 503
};
options.AddPolicy("MyPolicy2", new RequestTimeoutPolicy {
Timeout = TimeSpan.FromMilliseconds(1000),
WriteTimeoutResponse = async (HttpContext context) => {
context.Response.ContentType = "text/plain";
await context.Response.WriteAsync("Timeout from MyPolicy2!");
}
});
});
app.MapGet("/", async (HttpContext context) => {
try
{
await Task.Delay(TimeSpan.FromSeconds(10), context.RequestAborted);
}
catch (TaskCanceledException)
{
throw;
}
return Results.Content("No timeout!", "text/plain");
});
// Returns status code 503 due to default policy.
在原則中使用委派
RequestTimeoutPolicy
類別具有 WriteTimeoutResponse 屬性,可用來在觸發逾時時自訂回應。
builder.Services.AddRequestTimeouts(options => {
options.DefaultPolicy = new RequestTimeoutPolicy {
Timeout = TimeSpan.FromMilliseconds(1000),
TimeoutStatusCode = 503
};
options.AddPolicy("MyPolicy2", new RequestTimeoutPolicy {
Timeout = TimeSpan.FromMilliseconds(1000),
WriteTimeoutResponse = async (HttpContext context) => {
context.Response.ContentType = "text/plain";
await context.Response.WriteAsync("Timeout from MyPolicy2!");
}
});
});
app.MapGet("/usepolicy2", async (HttpContext context) => {
try
{
await Task.Delay(TimeSpan.FromSeconds(10), context.RequestAborted);
}
catch (TaskCanceledException)
{
throw;
}
return Results.Content("No timeout!", "text/plain");
}).WithRequestTimeout("MyPolicy2");
// Returns "Timeout from MyPolicy2!" due to WriteTimeoutResponse in MyPolicy2.
停用逾時
若要停用包括預設全域逾時在內的所有逾時,請使用 [DisableRequestTimeout]
屬性或 DisableRequestTimeout 擴充方法:
app.MapGet("/disablebyattr", [DisableRequestTimeout] async (HttpContext context) => {
try
{
await Task.Delay(TimeSpan.FromSeconds(10), context.RequestAborted);
}
catch
{
return Results.Content("Timeout!", "text/plain");
}
return Results.Content("No timeout!", "text/plain");
});
// Returns "No timeout!", ignores default timeout.
app.MapGet("/disablebyext", async (HttpContext context) => {
try
{
await Task.Delay(TimeSpan.FromSeconds(10), context.RequestAborted);
}
catch
{
return Results.Content("Timeout!", "text/plain");
}
return Results.Content("No timeout!", "text/plain");
}).DisableRequestTimeout();
// Returns "No timeout!", ignores default timeout.
取消逾時
若要取消已啟動的逾時,請在 IHttpRequestTimeoutFeature 上使用 DisableTimeout() 方法。 逾時無法在逾時到期後取消。
app.MapGet("/canceltimeout", async (HttpContext context) => {
var timeoutFeature = context.Features.Get<IHttpRequestTimeoutFeature>();
timeoutFeature?.DisableTimeout();
try
{
await Task.Delay(TimeSpan.FromSeconds(10), context.RequestAborted);
}
catch (TaskCanceledException)
{
return Results.Content("Timeout!", "text/plain");
}
return Results.Content("No timeout!", "text/plain");
}).WithRequestTimeout(TimeSpan.FromSeconds(1));
// Returns "No timeout!" since the default timeout is not triggered.