ASP.NET.Core - Rate Limiting em Minimal APIs


 Neste artigo veremos como usar os recurso Rate Limiting nas minimal APIs da ASP.NET Core.
 
As minimal APIs oferecem uma abordagem mais simplificada e leve para a criação de APIs em comparação com os métodos tradicionais disponíveis na ASP.NET Core.

Elas foram introduzidas para simplificar o processo de criação de APIs, removendo a necessidade de configurar uma quantidade significativa de código boilerplate. Elas são destinadas a casos simples e rápidos nos quais uma API pequena e direta é necessária.

O Rate Limiting (limitação de taxa) é um mecanismo que restringe a taxa de solicitações que um cliente pode fazer em um determinado período de tempo. Esse recurso é útil para proteger uma aplicação contra ataques de negação de serviço (DoS) ou para impedir que um único cliente sobrecarregue o servidor com um grande número de solicitações em um curto espaço de tempo.

O Rate Limiting funciona usando um algoritmo que mede o número de solicitações que um cliente fez em um determinado período de tempo. Se o número de solicitações exceder o limite, o algoritmo pode bloquear ou retardar a solicitação subsequente.

O ASP.NET Core fornece várias maneiras de implementar o Rate Limiting. Uma maneira é usar o middleware de limitação de taxa, que é um componente que pode ser adicionado ao pipeline de solicitação do seu aplicativo. O middleware de limitação de taxa -  Microsoft.AspNetCore.RateLimiting - usa um algoritmo padrão para limitar a taxa de solicitações.

Outra maneira de implementar o Rate Limiting é usar uma implementação personalizada do algoritmo de limitação de taxa. Isso pode ser útil se você precisar de um controle mais granular sobre a forma como o Rate Limiting é implementado.

Aqui está um exemplo de como usar o middleware de limitação de taxa para limitar a taxa de solicitações a 10 solicitações por segundo:

builder.Services.AddRateLimiter(rateLimiterOptions =>
{
    rateLimiterOptions.AddFixedWindowLimiter("fixed", options =>
   {
       options.PermitLimit = 3;
       options.Window = TimeSpan.FromSeconds(10);
       options.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
       options.QueueLimit = 1;
    });
    rateLimiterOptions.RejectionStatusCode = StatusCodes.Status429TooManyRequests;
});

Neste artigo, veremos como criar um middleware de limitação de taxa para Minimal APIs, fornecendo uma solução robusta para gerenciar o tráfego de solicitações.

Implementando o Middleware de limitação de taxa

Vamos implementar um middleware de limitação de taxa onde se um request for feito para o mesmo IP dentro de 60 segundos de um request anterior será retornado o status code 429 indicando muitos requests.

Para isso vamos criar no projeto MinApiTeste a pasta MyRateLimiting e nesta pasta vamos criar a classe RateLimitingMiddleware com este código:

public class RateLimitingMiddleware
{
   private readonly RequestDelegate _next;
  private static Dictionary<string, DateTime>? _requestTracker;

   public RateLimitingMiddleware(RequestDelegate next)
   {
      _next = next;
      _requestTracker = new Dictionary<string, DateTime>();
    }

     public async Task InvokeAsync(HttpContext context)
     {
         var clientKey = context.Request.HttpContext
                                   .Connection.RemoteIpAddress.ToString();

           if (_requestTracker.ContainsKey(clientKey) &&
                     DateTime.Now.Subtract(_requestTracker[clientKey]).TotalSeconds < 60)
            {
                 context.Response.StatusCode = 429; // Too Many Requests
                await context.Response.WriteAsync("Limitação de taxa excedida. Tente novamente mais tarde.");
                return;
       }
      _requestTracker[clientKey] = DateTime.Now;
     await _next(context);
}

A seguir para usar vamos definir o código abaixo na classe Program do projeto MinApiTeste :

using MinApiTeste.MyRateLimiting;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}

app.UseHttpsRedirection();

app.UseMiddleware<RateLimitingMiddleware>();

var summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};

app.MapGet("/weatherforecast", () =>
{
    var forecast = Enumerable.Range(1, 5).Select(index =>
    new WeatherForecast
    (
        DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
        Random.Shared.Next(-20, 55),
             summaries[Random.Shared.Next(summaries.Length)]
    ))
    .ToArray();
    return forecast;
})
.WithName("GetWeatherForecast")
.WithOpenApi();

app.Run();

internal record WeatherForecast(DateOnly Date, int TemperatureC, string? Summary)
{
   public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
}

Estamos adicionando RateLimitingMiddleware ao pipeline do aplicativo usando o código:

app.UseMiddleware<RateLimitingMiddleware>();.

Isso garante que cada solicitação passe pela verificação de limitação de taxa.

Considerações

Embora a implementação básica seja simples, há diversas considerações para uma solução pronta para produção:

• A implementação atual utiliza um dicionário na memória que não é adequado para ambientes distribuídos. Considere usar um cache distribuído como o Redis.

• A implementação da limitação de taxa com base nas funções do usuário ou nos endpoints da API pode fornecer um controle mais granular.

• Otimize o uso e o desempenho da memória implementando um mecanismo de rastreamento mais eficiente, possivelmente com uma expiração variável para entradas no dicionário.

Pegue o projeto aqui :   MinApiTeste.zip

E estamos conversados...

"Na verdade, na verdade vos digo que vós chorareis e vos lamentareis, e o mundo se alegrará, e vós estareis tristes, mas a vossa tristeza se converterá em alegria."
João 16:20

Referências:


José Carlos Macoratti