Dela via


Begära timeouter mellanprogram i ASP.NET Core

Av Tom Dykstra

Appar kan tillämpa tidsgränser selektivt på begäranden. ASP.NET Core-servrar gör inte detta som standard eftersom bearbetningstiderna för begäranden varierar kraftigt efter scenario. Till exempel skulle WebSockets, statiska filer och anrop av dyra API:er kräva olika tidsgränser. Så ASP.NET Core tillhandahåller mellanprogram som konfigurerar tidsgränser per slutpunkt samt en global tidsgräns.

När en tidsgräns uppnås har IsCancellationRequested en CancellationToken in HttpContext.RequestAborted angetts till true. Abort() anropas inte automatiskt på begäran, så programmet kan fortfarande generera ett lyckat eller misslyckat svar. Standardbeteendet om appen inte hanterar undantaget och genererar ett svar är att returnera statuskod 504.

Den här artikeln beskriver hur du konfigurerar timeout-mellanprogrammet. Timeout-mellanprogrammet kan användas i alla typer av ASP.NET Core-appar: Minimalt API, webb-API med kontrollanter, MVC och Razor sidor. Exempelappen är ett minimalt API, men varje timeout-funktion som visas stöds också i de andra apptyperna.

Tidsgränser för begäran finns i Microsoft.AspNetCore.Http.Timeouts namnområdet.

Not: När en app körs i felsökningsläge utlöses inte timeout-mellanprogrammet. Det här beteendet är detsamma som för Kestrel tidsgränser. Om du vill testa tidsgränser kör du appen utan att felsökaren är ansluten.

Lägg till mellanprogrammet i appen

Lägg till timeout-mellanprogrammet för begäran i tjänstsamlingen genom att anropa AddRequestTimeouts.

Lägg till mellanprogrammet i pipelinen för bearbetning av begäranden genom att anropa UseRequestTimeouts.

Anmärkning

  • I appar som uttryckligen anropar UseRoutingUseRequestTimeouts måste anropas efter UseRouting.

Att lägga till mellanprogrammet i appen börjar inte automatiskt utlösa tidsgränser. Tidsgränser måste konfigureras explicit.

Konfigurera en slutpunkt eller sida

För minimala API-appar konfigurerar du en slutpunkt till timeout genom att anropa WithRequestTimeouteller genom att använda [RequestTimeout] attributet, som du ser i följande exempel:

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

För appar med kontrollanter använder du [RequestTimeout] attributet för åtgärdsmetoden eller kontrollantklassen. För Razor Pages-appar använder du attributet för sidklassen Razor .

Konfigurera flera slutpunkter eller sidor

Skapa namngivna principer för att ange tidsgränskonfiguration som gäller för flera slutpunkter. Lägg till en princip genom att anropa AddPolicy:

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

En tidsgräns kan anges för en slutpunkt efter principnamn:

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

Attributet [RequestTimeout] kan också användas för att ange en namngiven princip.

Ange global standardprincip för timeout

Ange en princip för den globala standardkonfigurationen för timeout:

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

Standardtimeouten gäller för slutpunkter som inte har en angiven tidsgräns. Följande slutpunktskod söker efter en timeout även om den inte anropar tilläggsmetoden eller tillämpar attributet. Den globala tidsgränskonfigurationen gäller, så koden söker efter en tidsgräns:

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.

Ange statuskoden i en princip

Klassen RequestTimeoutPolicy har en egenskap som automatiskt kan ange statuskoden när en tidsgräns utlöses.

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.

Använda ett ombud i en princip

Klassen RequestTimeoutPolicy har en WriteTimeoutResponse egenskap som kan användas för att anpassa svaret när en timeout utlöses.

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.

Inaktivera tidsgränser

Om du vill inaktivera alla tidsgränser, inklusive den globala standardtimeouten [DisableRequestTimeout]DisableRequestTimeout , använder du attributet eller tilläggsmetoden:

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.

Avbryt en tidsgräns

Om du vill avbryta en tidsgräns som redan har startats använder du metoden på DisableTimeout()IHttpRequestTimeoutFeature. Tidsgränser kan inte avbrytas när de har upphört att gälla.

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.

Se även