湯 姆·戴克斯特拉
應用程式可以選擇性地將逾時限制套用至要求。 ASP.NET Core 伺服器預設不會執行此動作,因為要求處理時間會因案例而異。 例如,WebSocket、靜態檔案和呼叫昂貴的 API 都需要不同的逾時限制。 因此 ASP.NET Core 提供了配置每個端點的超時以及全局超時的中間件。
當達到逾時限制時,CancellationToken 在 HttpContext.RequestAborted 中已被設定為 IsCancellationRequested。 Abort() 不會在要求上自動呼叫,因此應用程式仍可能產生成功或失敗回應。 如果應用程式未處理例外狀況並產生回應,預設行為是傳回狀態碼 504。
本文說明如何設定逾時中介軟體。 逾時中介軟體可用於所有類型的 ASP.NET Core 應用程式:最小 API、具有控制器的 Web API、MVC 和 Razor Pages。 範例應用程式是簡化 API,但範例應用程式所示範的各項逾時功能也支援其他應用程式類型。
要求逾時位於命名空間中 Microsoft.AspNetCore.Http.Timeouts 。
注意: 當應用程式在偵錯模式中執行時,不會觸發超時中介程式。 此行為與逾時相同Kestrel。 若要測試超時,請在不附加偵錯工具的情況下執行應用程式。
將中介軟體新增至應用程式
呼叫 AddRequestTimeouts,將要求逾時中介軟體新增至服務集合。
將中介軟體新增至要求處理管線,方法是呼叫 UseRequestTimeouts。
備註
- 在明確呼叫 UseRouting的應用程式中,必須在呼叫
UseRequestTimeouts之後呼叫UseRouting。
將中介軟體加入至應用程式不會自動觸發逾時。 必須明確設定逾時限制。
設定一個端點或頁面
對於最小 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] 屬性套用至動作方法或控制器類別。 若為 Pages 應用程式,請 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.
取消超時
若要取消已啟動的逾時,請使用DisableTimeout()方法在IHttpRequestTimeoutFeature上。 逾時到期後無法取消。
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.