共用方式為


ASP.NET Core 中的請求逾時中介軟體

姆·戴克斯特拉

應用程式可以選擇性地將逾時限制套用至要求。 ASP.NET Core 伺服器預設不會執行此動作,因為要求處理時間會因案例而異。 例如,WebSocket、靜態檔案和呼叫昂貴的 API 都需要不同的逾時限制。 因此 ASP.NET Core 提供了配置每個端點的超時以及全局超時的中間件。

當達到逾時限制時,CancellationTokenHttpContext.RequestAborted 中已被設定為 IsCancellationRequestedAbort() 不會在要求上自動呼叫,因此應用程式仍可能產生成功或失敗回應。 如果應用程式未處理例外狀況並產生回應,預設行為是傳回狀態碼 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.

另請參閱