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