Middleware di timeout della richiesta in ASP.NET Core

Tom Dykstra

Le app possono applicare limiti di timeout in modo selettivo alle richieste. ASP.NET i server Core non eseguono questa operazione per impostazione predefinita poiché i tempi di elaborazione delle richieste variano notevolmente in base a uno scenario. Ad esempio, WebSocket, file statici e chiamate api costose richiedono un limite di timeout diverso. Quindi ASP.NET Core fornisce middleware che configura i timeout per endpoint e un timeout globale.

Quando viene raggiunto un limite di timeout, in CancellationToken HttpContext.RequestAborted è IsCancellationRequested impostato su true. Abort() non viene chiamato automaticamente sulla richiesta, quindi l'applicazione può comunque produrre una risposta riuscita o non riuscita. Il comportamento predefinito se l'app non gestisce l'eccezione e genera una risposta consiste nel restituire il codice di stato 504.

Questo articolo illustra come configurare il middleware di timeout. Il middleware di timeout può essere usato in tutti i tipi di app core di ASP.NET: API minima, API Web con controller, MVC e Razor pagine. L'app di esempio è un'API minima, ma ogni funzionalità di timeout illustrata è supportata anche negli altri tipi di app.

I timeout delle richieste si trovano nello spazio dei Microsoft.AspNetCore.Http.Timeouts nomi .

Nota: quando un'app è in esecuzione in modalità di debug, il middleware di timeout non viene attivato. Questo comportamento è uguale a quello per Kestrel i timeout. Per testare i timeout, eseguire l'app senza il debugger collegato.

Aggiungere il middleware all'app

Aggiungere il middleware di timeout della richiesta alla raccolta di servizi chiamando AddRequestTimeouts.

Aggiungere il middleware alla pipeline di elaborazione delle richieste chiamando UseRequestTimeouts.

Nota

  • Nelle app che chiamano UseRoutingin modo esplicito , UseRequestTimeouts devono essere chiamate dopo UseRouting.

L'aggiunta del middleware all'app non avvia automaticamente i timeout di attivazione. I limiti di timeout devono essere configurati in modo esplicito.

Configurare un endpoint o una pagina

Per le app per le API minime, configurare un endpoint per il timeout chiamando WithRequestTimeouto applicando l'attributo , come illustrato nell'esempio [RequestTimeout] seguente:

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

Per le app con controller, applicare l'attributo [RequestTimeout] al metodo di azione o alla classe controller. Per Razor le app Pages, applicare l'attributo alla Razor classe page.

Configurare più endpoint o pagine

Creare criteri denominati per specificare la configurazione di timeout applicabile a più endpoint. Aggiungere un criterio chiamando AddPolicy:

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

È possibile specificare un timeout per un endpoint in base al nome del criterio:

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

L'attributo [RequestTimeout] può essere usato anche per specificare un criterio denominato.

Impostare i criteri di timeout predefiniti globali

Specificare un criterio per la configurazione di timeout predefinita globale:

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

Il timeout predefinito si applica agli endpoint che non hanno un timeout specificato. Il codice endpoint seguente verifica la presenza di un timeout anche se non chiama il metodo di estensione o applica l'attributo. Si applica la configurazione globale del timeout, quindi il codice verifica la presenza di un timeout:

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.

Specificare il codice di stato in un criterio

La RequestTimeoutPolicy classe ha una proprietà che può impostare automaticamente il codice di stato quando viene attivato un timeout.

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.

Usare un delegato in un criterio

La RequestTimeoutPolicy classe ha una WriteTimeoutResponse proprietà che può essere usata per personalizzare la risposta quando viene attivato un timeout.

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.

Disabilitare i timeout

Per disabilitare tutti i timeout, incluso il timeout globale predefinito, usare l'attributo [DisableRequestTimeout] o il metodo di DisableRequestTimeout estensione:

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.

Annullare un timeout

Per annullare un timeout già avviato, usare il DisableTimeout() metodo in IHttpRequestTimeoutFeature. I timeout non possono essere annullati dopo la scadenza.

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.

Vedi anche