Freigeben über


Timeouts-Middleware in ASP.NET Core anfordern

Von Tom Dykstra

Apps können Timeoutlimits selektiv auf Anforderungen anwenden. ASP.NET Core-Server tun dies nicht standardmäßig, da die Anforderungsverarbeitungszeiten stark nach Szenario variieren. Beispielsweise erfordern WebSockets, statische Dateien und aufrufen teure APIs jeweils ein anderes Timeoutlimit. Daher stellt ASP.NET Core Middleware bereit, die Timeouts pro Endpunkt sowie ein globales Timeout konfiguriert.

Wenn ein Timeoutlimit erreicht wird, hat CancellationToken ein HttpContext.RequestAborted In IsCancellationRequested auf true. Abort() wird nicht automatisch für die Anforderung aufgerufen, sodass die Anwendung möglicherweise trotzdem eine Erfolgs- oder Fehlerantwort erzeugt. Das Standardverhalten, wenn die App die Ausnahme nicht behandelt und eine Antwort erzeugt, besteht darin, den Statuscode 504 zurückzugeben.

In diesem Artikel wird erläutert, wie Sie die Timeout-Middleware konfigurieren. Die Timeout-Middleware kann in allen Arten von ASP.NET Core-Apps verwendet werden: Minimale API, Web-API mit Controllern, MVC und Razor Seiten. Die Beispiel-App ist eine minimale API, aber jedes timeout-Feature, das veranschaulicht wird, wird auch in den anderen App-Typen unterstützt.

Anforderungstimeouts befinden sich im Microsoft.AspNetCore.Http.Timeouts Namespace.

Anmerkung: Wenn eine App im Debugmodus ausgeführt wird, löst die Timeout-Middleware nicht aus. Dieses Verhalten entspricht für Kestrel Timeouts. Führen Sie die App ohne angefügten Debugger aus, um Timeouts zu testen.

Hinzufügen der Middleware zur App

Fügen Sie die Timeouts-Middleware der Anforderungstimeouts-Middleware zur Dienstsammlung hinzu, indem Sie aufrufen AddRequestTimeouts.

Fügen Sie die Middleware zur Anforderungsverarbeitungspipeline hinzu, indem Sie die Anforderungsverarbeitungspipeline aufrufen UseRequestTimeouts.

Hinweis

  • In Apps, die explizit aufgerufen UseRoutingwerden, UseRequestTimeouts muss danach UseRoutingaufgerufen werden.

Das Hinzufügen der Middleware zur App startet nicht automatisch das Auslösen von Timeouts. Timeoutlimits müssen explizit konfiguriert werden.

Konfigurieren eines Endpunkts oder einer Seite

Konfigurieren Sie für Minimale API-Apps einen Endpunkt für ein Timeout, indem Sie WithRequestTimeout aufrufen oder das [RequestTimeout]-Attribut anwenden, wie im folgenden Beispiel gezeigt.

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

Wenden Sie für Apps mit Controllern das [RequestTimeout] Attribut auf die Aktionsmethode oder die Controllerklasse an. Wenden Razor Sie für Seiten-Apps das Attribut auf die Razor Seitenklasse an.

Konfigurieren mehrerer Endpunkte oder Seiten

Erstellen Sie benannte Richtlinien , um die Timeoutkonfiguration anzugeben, die für mehrere Endpunkte gilt. Fügen Sie eine Richtlinie hinzu, indem Sie Folgendes aufrufen AddPolicy:

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

Ein Timeout kann für einen Endpunkt anhand des Richtliniennamens angegeben werden:

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

Das [RequestTimeout] Attribut kann auch verwendet werden, um eine benannte Richtlinie anzugeben.

Festlegen der globalen Standardtimeoutrichtlinie

Geben Sie eine Richtlinie für die globale Standardtimeoutkonfiguration an:

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

Das Standardtimeout gilt für Endpunkte, für die kein Timeout angegeben ist. Der folgende Endpunktcode sucht nach einem Timeout, obwohl er die Erweiterungsmethode nicht aufruft oder das Attribut anwendet. Die globale Timeoutkonfiguration gilt, sodass der Code nach einem Timeout sucht:

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.

Angeben des Statuscodes in einer Richtlinie

Die RequestTimeoutPolicy Klasse verfügt über eine Eigenschaft, die den Statuscode automatisch festlegen kann, wenn ein Timeout ausgelöst wird.

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.

Verwenden einer Stellvertretung in einer Richtlinie

Die RequestTimeoutPolicy Klasse verfügt über eine WriteTimeoutResponse Eigenschaft, die verwendet werden kann, um die Antwort anzupassen, wenn ein Timeout ausgelöst wird.

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.

Timeouts deaktivieren

Um alle Timeouts einschließlich des standardmäßigen globalen Timeouts zu deaktivieren, verwenden Sie das [DisableRequestTimeout] Attribut oder die DisableRequestTimeout Erweiterungsmethode:

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.

Abbrechen eines Timeouts

Um ein bereits gestartetes Timeout abzubrechen, verwenden Sie die DisableTimeout() Methode für IHttpRequestTimeoutFeature. Timeouts können nicht abgebrochen werden, nachdem sie abgelaufen sind.

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.

Siehe auch