Middleware pro vypršení časového limitu požadavku v ASP.NET Core

Od Tom Dykstra

Aplikace můžou selektivně použít limity časového limitu pro požadavky. ASP.NET servery Core to ve výchozím nastavení neprodělají, protože doby zpracování požadavků se v jednotlivých scénářích liší. Například protokoly WebSockets, statické soubory a volání drahých rozhraní API by vyžadovaly jiný limit časového limitu. Proto ASP.NET Core poskytuje middleware, který konfiguruje časové limity na koncový bod i globální časový limit.

Pokud dojde k dosažení časového limitu, CancellationToken hodnota in HttpContext.RequestAborted je nastavena IsCancellationRequested na truehodnotu . Abort() není automaticky volána na žádost, takže aplikace může stále způsobit úspěšnou nebo neúspěšnou odpověď. Výchozí chování, pokud aplikace nezpracuje výjimku a vygeneruje odpověď, je vrátit stavový kód 504.

Tento článek vysvětluje, jak nakonfigurovat middleware časového limitu. Middleware časového limitu lze použít ve všech typech aplikací ASP.NET Core: minimální rozhraní API, webové rozhraní API s kontrolery, MVC a Razor Pages. Ukázková aplikace je minimální rozhraní API, ale každá funkce časového limitu, kterou ilustruje, se podporuje také v ostatních typech aplikací.

Časové limity požadavků jsou v Microsoft.AspNetCore.Http.Timeouts oboru názvů.

Poznámka: Když je aplikace spuštěná v režimu ladění, middleware časového limitu se neaktivuje. Toto chování je stejné jako u Kestrel časových limitů. Pokud chcete otestovat časové limity, spusťte aplikaci bez připojeného ladicího programu.

Přidání middlewaru do aplikace

Přidejte middleware časového limitu požadavku do kolekce služeb voláním AddRequestTimeouts.

Přidejte middleware do kanálu zpracování požadavků voláním UseRequestTimeouts.

Poznámka:

  • V aplikacích, které explicitně volají UseRouting, UseRequestTimeouts musí být volána za UseRouting.

Přidání middlewaru do aplikace se automaticky nespustí s vypršením časových limitů. Limity časového limitu musí být explicitně nakonfigurované.

Konfigurace jednoho koncového bodu nebo stránky

V případě minimálních aplikací API nakonfigurujte koncový bod na vypršení časového limitu voláním WithRequestTimeoutnebo použitím atributu [RequestTimeout] , jak je znázorněno v následujícím příkladu:

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();

U aplikací s kontrolery použijte [RequestTimeout] atribut na metodu akce nebo třídu kontroleru. U Razor aplikací Pages použijte atribut na Razor třídu stránky.

Konfigurace několika koncových bodů nebo stránek

Vytvořte pojmenované zásady pro určení konfigurace časového limitu, která se vztahuje na více koncových bodů. Přidejte zásadu voláním AddPolicy:

builder.Services.AddRequestTimeouts(options => {
    options.DefaultPolicy =
        new RequestTimeoutPolicy { Timeout = TimeSpan.FromMilliseconds(1500) };
    options.AddPolicy("MyPolicy", TimeSpan.FromSeconds(2));
});

Časový limit pro koncový bod je možné zadat podle názvu zásady:

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!"

Atribut [RequestTimeout] lze také použít k určení pojmenované zásady.

Nastavení globálních výchozích zásad časového limitu

Zadejte zásadu pro globální výchozí konfiguraci časového limitu:

builder.Services.AddRequestTimeouts(options => {
    options.DefaultPolicy =
        new RequestTimeoutPolicy { Timeout = TimeSpan.FromMilliseconds(1500) };
    options.AddPolicy("MyPolicy", TimeSpan.FromSeconds(2));
});

Výchozí časový limit platí pro koncové body, které nemají zadaný časový limit. Následující kód koncového bodu zkontroluje vypršení časového limitu, i když nevyvolá metodu rozšíření nebo použije atribut. Globální konfigurace časového limitu platí, takže kód zkontroluje časový limit:

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.

Zadání stavového kódu v zásadách

Třída RequestTimeoutPolicy má vlastnost, která může automaticky nastavit stavový kód při aktivaci časového limitu.

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.

Použití delegáta v zásadách

Třída RequestTimeoutPolicyWriteTimeoutResponse vlastnost, kterou lze použít k přizpůsobení odpovědi při aktivaci časového limitu.

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.

Zakázání časových limitů

Pokud chcete zakázat všechny časové limity, včetně výchozího globálního časového limitu, použijte [DisableRequestTimeout] atribut nebo metodu DisableRequestTimeout rozšíření:

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.

Zrušení časového limitu

Chcete-li zrušit časový limit, který již byl spuštěn, použijte metodu DisableTimeout() na IHttpRequestTimeoutFeature. Po vypršení jejich platnosti není možné vypršení časového limitu zrušit.

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.

Viz také