.NET
Implementando rate limiting em APIs ASP.NET Core
Aprenda a proteger APIs ASP.NET Core com o middleware integrado de rate limiting, policies nomeadas, partições por cliente, responses HTTP 429 e escolhas práticas de configuração.
Rate limiting controla quantos requests uma API aceita durante um período específico. Ele é útil para APIs públicas, endpoints de autenticação, operações de alto custo e integrações em que um cliente não deve consumir todos os recursos disponíveis.
O ASP.NET Core oferece middleware integrado de rate limiting com algoritmos fixed window, sliding window, token bucket e concurrency. As policies podem ser globais ou atribuídas somente a endpoints selecionados. (learn.microsoft.com)
Por que rate limiting é importante
Um rate limiter pode ajudar uma API a:
- impedir que um único cliente monopolize os recursos;
- reduzir picos acidentais de tráfego;
- proteger a capacidade de database, CPU e serviços externos;
- aplicar limites diferentes para diferentes usuários ou planos;
- retornar uma response previsível quando não houver capacidade disponível.
Rate limiting não é uma proteção completa contra DDoS. Grandes ataques distribuídos também devem ser tratados nos limites da infraestrutura, como CDN, firewall de aplicação web, API gateway ou serviço de proteção em nuvem. (learn.microsoft.com)
Configurando uma policy fixed window
O algoritmo fixed window permite um número definido de requests durante uma janela de tempo. Quando a janela termina, seu contador é redefinido.
A configuração de Program.cs a seguir aceita 20 requests por minuto e coloca em fila até dois requests adicionais:
using Microsoft.AspNetCore.RateLimiting;
using System.Threading.RateLimiting;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRateLimiter(options =>
{
options.RejectionStatusCode = StatusCodes.Status429TooManyRequests;
options.AddFixedWindowLimiter("api", limiterOptions =>
{
limiterOptions.PermitLimit = 20;
limiterOptions.Window = TimeSpan.FromMinutes(1);
limiterOptions.QueueLimit = 2;
limiterOptions.QueueProcessingOrder =
QueueProcessingOrder.OldestFirst;
});
});
var app = builder.Build();
app.UseRouting();
app.UseRateLimiter();
app.MapGet("/products", () => Results.Ok(new[]
{
new { Id = 1, Name = "Keyboard" },
new { Id = 2, Name = "Mouse" }
})).RequireRateLimiting("api");
app.Run();
PermitLimit define quantos requests são aceitos, enquanto Window define quando as permissões são repostas. QueueLimit controla quantos requests podem aguardar uma permissão. Defini-lo como zero rejeita imediatamente os requests excedentes.
Para policies específicas de endpoint, UseRateLimiter deve ser executado depois de UseRouting. (learn.microsoft.com)
Retornando uma response 429 útil
Por padrão, requests rejeitados podem receber o código de status HTTP 429 Too Many Requests. Um callback OnRejected permite que a API retorne uma response mais clara e, quando disponível, um header Retry-After.
builder.Services.AddRateLimiter(options =>
{
options.RejectionStatusCode = StatusCodes.Status429TooManyRequests;
options.OnRejected = async (context, cancellationToken) =>
{
if (context.Lease.TryGetMetadata(
MetadataName.RetryAfter,
out var retryAfter))
{
context.HttpContext.Response.Headers.RetryAfter =
((int)retryAfter.TotalSeconds).ToString();
}
await context.HttpContext.Response.WriteAsJsonAsync(new
{
error = "Too many requests. Please try again later."
}, cancellationToken);
};
options.AddFixedWindowLimiter("api", limiterOptions =>
{
limiterOptions.PermitLimit = 20;
limiterOptions.Window = TimeSpan.FromMinutes(1);
limiterOptions.QueueLimit = 0;
});
});
O valor de Retry-After ajuda os clientes a decidir quando outro request provavelmente terá sucesso. Ainda é importante que os clientes tratem responses 429, em vez de presumir que todos os requests serão aceitos.
Limitando cada cliente separadamente
Uma única policy compartilhada significa que todos os chamadores consomem a mesma cota de requests. O particionamento cria um limiter separado para cada usuário, chave de API, tenant ou endereço IP.
options.AddPolicy("per-client", httpContext =>
{
var clientId =
httpContext.User.Identity?.Name ??
httpContext.Connection.RemoteIpAddress?.ToString() ??
"anonymous";
return RateLimitPartition.GetFixedWindowLimiter(
partitionKey: clientId,
factory: _ => new FixedWindowRateLimiterOptions
{
PermitLimit = 10,
Window = TimeSpan.FromMinutes(1),
QueueLimit = 0,
AutoReplenishment = true
});
});
Aplique-a como qualquer outra policy nomeada:
app.MapPost("/orders", CreateOrder)
.RequireRateLimiting("per-client");
Um ID de usuário autenticado ou uma chave de API controlada geralmente é uma chave de partição mais confiável do que um endereço IP. Aplicações atrás de proxies também devem configurar corretamente os headers encaminhados antes de depender do IP do cliente.
Escolhendo um algoritmo
Use fixed window quando uma cota simples for suficiente. Use sliding window para limites mais uniformes nas transições entre janelas. Use token bucket quando rajadas controladas forem aceitáveis. Use concurrency limiting quando a principal preocupação for quantas operações de alto custo são executadas simultaneamente, em vez de quantos requests chegam por minuto. (learn.microsoft.com)
Antes do deploy em produção, mantenha os limites na configuração, monitore os requests rejeitados e faça testes de carga com os valores selecionados. Um limite deve refletir o custo e a capacidade reais do endpoint protegido — não um número arbitrário copiado para toda a API.
Referências
- Rate limiting middleware in ASP.NET Core | Microsoft Learn — learn.microsoft.com
- Dotnet Api Rate Limiting Guide 058F3a700dcb — medium.com
- Http Ratelimiter — learn.microsoft.com
- 31 Rate Limiting In Netcore — adrianbailador.github.io
- Implement Rate Limiting In Asp Net Core Web Api — c-sharpcorner.com