Bagikan melalui


Meminta middleware batas waktu di ASP.NET Core

Oleh Tom Dykstra

Aplikasi dapat menerapkan batas waktu habis secara selektif untuk permintaan. ASP.NET Server inti tidak melakukan ini secara default karena waktu pemrosesan permintaan sangat bervariasi menurut skenario. Misalnya, WebSocket, file statis, dan memanggil API mahal masing-masing akan memerlukan batas waktu habis yang berbeda. Jadi ASP.NET Core menyediakan middleware yang mengonfigurasi batas waktu per titik akhir serta batas waktu global.

Ketika batas waktu habis terbentur, in CancellationTokenHttpContext.RequestAborted telah IsCancellationRequested diatur ke true. Abort() tidak secara otomatis dipanggil berdasarkan permintaan, sehingga aplikasi mungkin masih menghasilkan respons keberhasilan atau kegagalan. Perilaku default jika aplikasi tidak menangani pengecualian dan menghasilkan respons adalah mengembalikan kode status 504.

Artikel ini menjelaskan cara mengonfigurasi middleware batas waktu. Middleware batas waktu dapat digunakan di semua jenis aplikasi ASP.NET Core: API Minimal, API Web dengan pengontrol, MVC, dan Razor Pages. Aplikasi sampel adalah API Minimal, tetapi setiap fitur batas waktu yang diilustrasikannya juga didukung di jenis aplikasi lain.

Batas waktu permintaan ada di Microsoft.AspNetCore.Http.Timeouts namespace layanan.

Catatan: Saat aplikasi berjalan dalam mode debug, middleware batas waktu tidak memicu. Perilaku ini sama dengan untuk Kestrel batas waktu. Untuk menguji batas waktu, jalankan aplikasi tanpa debugger terlampir.

Menambahkan middleware ke aplikasi

Tambahkan middleware batas waktu permintaan ke koleksi layanan dengan memanggil AddRequestTimeouts.

Tambahkan middleware ke alur pemrosesan permintaan dengan memanggil UseRequestTimeouts.

Catatan

  • Dalam aplikasi yang secara eksplisit memanggil UseRouting, UseRequestTimeouts harus dipanggil setelah UseRouting.

Menambahkan middleware ke aplikasi tidak secara otomatis mulai memicu batas waktu. Batas waktu habis harus dikonfigurasi secara eksplisit.

Mengonfigurasi satu titik akhir atau halaman

Untuk aplikasi API minimal, konfigurasikan titik akhir ke batas waktu dengan memanggil WithRequestTimeout, atau dengan menerapkan [RequestTimeout] atribut , seperti yang ditunjukkan dalam contoh berikut:

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

Untuk aplikasi dengan pengontrol, terapkan [RequestTimeout] atribut ke metode tindakan atau kelas pengontrol. Untuk Razor aplikasi Pages, terapkan atribut ke Razor kelas halaman.

Mengonfigurasi beberapa titik akhir atau halaman

Buat kebijakan bernama untuk menentukan konfigurasi batas waktu yang berlaku untuk beberapa titik akhir. Tambahkan kebijakan dengan memanggil AddPolicy:

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

Batas waktu dapat ditentukan untuk titik akhir berdasarkan nama kebijakan:

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] juga dapat digunakan untuk menentukan kebijakan bernama.

Mengatur kebijakan batas waktu default global

Tentukan kebijakan untuk konfigurasi batas waktu default global:

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

Batas waktu default berlaku untuk titik akhir yang tidak memiliki batas waktu yang ditentukan. Kode titik akhir berikut memeriksa batas waktu meskipun tidak memanggil metode ekstensi atau menerapkan atribut . Konfigurasi batas waktu global berlaku, sehingga kode memeriksa batas waktu:

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.

Tentukan kode status dalam kebijakan

Kelas RequestTimeoutPolicy memiliki properti yang dapat secara otomatis mengatur kode status saat batas waktu dipicu.

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.

Menggunakan delegasi dalam kebijakan

Kelas RequestTimeoutPolicy memiliki WriteTimeoutResponse properti yang dapat digunakan untuk menyesuaikan respons saat waktu habis dipicu.

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.

Menonaktifkan batas waktu

Untuk menonaktifkan semua batas waktu termasuk batas waktu global default, gunakan [DisableRequestTimeout] atribut atau DisableRequestTimeout metode ekstensi:

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.

Membatalkan batas waktu

Untuk membatalkan batas waktu yang telah dimulai, gunakan DisableTimeout() metode pada IHttpRequestTimeoutFeature. Batas waktu tidak dapat dibatalkan setelah kedaluwarsa.

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.

Baca juga