Passa al contenuto principale

Rate Limiter

ASP.NET Core 7+ include Microsoft.AspNetCore.RateLimiting, middleware nativo per limitare il numero di richieste. Non richiede librerie esterne.

Algoritmi disponibili

AlgoritmoComportamento
Fixed WindowN richieste per finestra temporale fissa
Sliding WindowN richieste in una finestra mobile
Token BucketToken consumati ad ogni richiesta, rigenerati nel tempo
ConcurrencyN richieste simultanee massime

Configurazione base (Fixed Window)

// Program.cs
builder.Services.AddRateLimiter(options =>
{
options.RejectionStatusCode = StatusCodes.Status429TooManyRequests;

options.AddFixedWindowLimiter("api", o =>
{
o.PermitLimit = 100;
o.Window = TimeSpan.FromMinutes(1);
o.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
o.QueueLimit = 10;
});
});

var app = builder.Build();
app.UseRateLimiter();

Sliding Window

Distribuisce i permessi in segmenti all'interno della finestra:

options.AddSlidingWindowLimiter("sliding", o =>
{
o.PermitLimit = 100;
o.Window = TimeSpan.FromMinutes(1);
o.SegmentsPerWindow = 6; // finestra divisa in segmenti da 10s
o.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
o.QueueLimit = 5;
});

Token Bucket

Adatto per burst controllati: si accumulano token fino a un massimo, si consumano ad ogni richiesta:

options.AddTokenBucketLimiter("bucket", o =>
{
o.TokenLimit = 100;
o.ReplenishmentPeriod = TimeSpan.FromSeconds(10);
o.TokensPerPeriod = 20;
o.AutoReplenishment = true;
o.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
o.QueueLimit = 5;
});

Concurrency

Limita le richieste simultanee, non il throughput nel tempo:

options.AddConcurrencyLimiter("concurrency", o =>
{
o.PermitLimit = 10;
o.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
o.QueueLimit = 5;
});

Applicare a controller o endpoint

// A livello di controller
[EnableRateLimiting("api")]
[ApiController]
[Route("api/[controller]")]
public class OrdiniController : ControllerBase { }

// A livello di singolo endpoint
app.MapGet("/api/prodotti", GetProdotti)
.RequireRateLimiting("api");

// Disabilitare su un endpoint specifico (override del controller)
[DisableRateLimiting]
[HttpGet("health")]
public IActionResult Health() => Ok();

Rate limiting per utente

Il caso più comune in produzione: limite per utente autenticato, non globale:

builder.Services.AddRateLimiter(options =>
{
options.RejectionStatusCode = StatusCodes.Status429TooManyRequests;

options.AddPolicy("per-utente", context =>
{
var utente = context.User.FindFirst(ClaimTypes.NameIdentifier)?.Value;

return utente is not null
? RateLimitPartition.GetFixedWindowLimiter(utente, _ => new FixedWindowRateLimiterOptions
{
PermitLimit = 60,
Window = TimeSpan.FromMinutes(1),
QueueLimit = 0
})
: RateLimitPartition.GetFixedWindowLimiter("anonimo", _ => new FixedWindowRateLimiterOptions
{
PermitLimit = 10,
Window = TimeSpan.FromMinutes(1),
QueueLimit = 0
});
});
});

Gestire il rifiuto

options.OnRejected = async (context, cancellationToken) =>
{
context.HttpContext.Response.StatusCode = StatusCodes.Status429TooManyRequests;

if (context.Lease.TryGetMetadata(MetadataName.RetryAfter, out var retryAfter))
{
context.HttpContext.Response.Headers.RetryAfter =
((int)retryAfter.TotalSeconds).ToString();
}

await context.HttpContext.Response.WriteAsync(
"Troppe richieste. Riprova tra poco.", cancellationToken);
};