ASP.NET Core - Rate limiting

Rate limiting je technika používaná hlavne pri API na kontrolu a obmedzenie počtu požiadaviek, ktoré môžu klienti vykonať na API v definovanom čase.
Patrí do skupiny Resiliences paternov, ktoré zabezpečujú, že API zostane stabilné a dostupné pre všetkých používateľov, čím sa predchádza zneužitiu alebo nadmernému využitiu, ktoré by mohlo ohroziť jeho výkon.

V minulosti sa v ASP.NET Core na rate limiting používala knižnica AspNetCoreRateLimit. Od verzie .NET 7 je v ASP.NET Core rate limiting dostupný priamo v rámci frameworku.

Vyberte si jeden z dostupných algoritmov rate limitingu:

  • Fixed window
  • Sliding window
  • Token bucket
  • Concurrency

💁 Jednotlivé metódy nájdeš detailne vysvetlené priamo v dokumentácii.

Najskôr zaregistruj policy:

builder.Services.AddRateLimiter(limiter =>
{
    // 👇 Define the status code to be returned when the request is rejected.
    limiter.RejectionStatusCode = StatusCodes.Status429TooManyRequests;

    // 👇 Define the policy.
    limiter.AddFixedWindowLimiter(policyName: "products", options =>
    {
        options.PermitLimit = 10;
        options.Window = TimeSpan.FromSeconds(1);

        // 👇 If you want to use a queue to process requests that exceed the limit, you can set the following options.
        options.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
        options.QueueLimit = 4;
    });
});

Následne pridaj middleware do pipeline:

app.UseRateLimiter();

A použite policy pri definícií endpointu:

app.MapGet("/products", () =>
{
    return new string[] { "product1", "product2", "product3", "product4" };
}).RequireRateLimiting("products"); // 👈 Add the policy to endpoint.

Pokiaľ máme definovanú policy na celej skupine endpointov, môžete potom disablovať rate limiting pre konkrétny endpoint:

var group = app.MapGroup("/api/products")
    .RequireRateLimiting("products");

group.MapGet("/", () =>
{
    return new string[] { "product1", "product2", "product3", "product4" };
}).DisableRateLimiting(); // 👈 Disable the policy for the endpoint.

V prípade controllerov môžete použiť atribúty [EnableRateLimiting(...)] a [DisableRateLimiting].

[EnableRateLimiting("products")]
public class ProductsController : Controller
{
    [HttpGet]
    public IEnumerable<string> Get()
    {
        return new string[] { "product1", "product2", "product3", "product4" };
    }

    [DisableRateLimiting]
    [HttpGet("{id}")]
    public string Get(int id)
    {
        return "product";
    }
}

Multitenancy

Ak staviate multi-tenant systém, tak pravdepodobne chcete mať rate limiting pre pre každého tenant-a. Napríklad chcete aby tenant mohol vykonať 10 požiadaviek za sekundu. Prípadne chcete mať rôzne limity pre rôzne tenant-y. Toto je možné dosiahnuť pomocou RateLimitPartition triedy.

builder.Services.AddRateLimiter(limiter =>
{
    // 👇 Define custom policy.
    limiter.AddPolicy("fixed-by-tenant",
        context => RateLimitPartition
            .GetFixedWindowLimiter(
                context.Request.Headers["tenant-id"], // 👈 Get tenant id
                _ => new FixedWindowRateLimiterOptions
                {
                    Window = TimeSpan.FromSeconds(1),
                    PermitLimit = 10
                }));
    
    // 👇 If you want different policy per tenant
    limiter.AddPolicy("fixed-by-tenant-2",
        context => RateLimitPartition
            .GetFixedWindowLimiter(
                context.Request.Headers["tenant-id"], // 👈 Get tenant id
                tenantId => GetOptionsByTenant(tenantId))); 
                // 👆 Get options by tenan from your configuration
});