ASP.NET Core 7 - Usando Rate Limiting


 Neste artigo vou apresentar o novo recurso nativo da ASP .NET Core 7 : o middleware Rate Limiting.

O termo Rate Limiting, traduzido literalmente, significa limitação de taxa, e, em aplicações ASP .NET Core esta relacionado com limitar o número de requests que podem ser feitos em um período de tempo.

Assim, a limitação de taxa é uma forma de controlar a quantidade de tráfego que uma aplicação MVC ou Web ou API, limitando o número de request que podem ser feitas em um determinado período de tempo. Isso pode ajudar a melhorar o desempenho do site ou aplicativo e evitar que ele pare de responder.

A partir do .NET 7, a ASP.NET Core inclui um middleware integrado de limitação de taxa, que pode ser usado para limitar a taxa de aplicativos para Web.

Neste artigo veremos como configurar e usar o middleware de limitação de taxa na ASP.NET Core.

Rate Limiting

Cada aplicativo que você constrói está compartilhando recursos. O aplicativo é executado em um servidor que compartilha sua CPU, memória e E/S de disco, em um banco de dados que armazena dados de todos os seus usuários.

Quer sejam acidentais ou intencionais, os usuários podem exaurir esses recursos de uma forma que afete os demais usuários. Um script pode fazer muitas solicitações ou uma nova implantação da sua aplicação pode estar chamando uma API específica muitas vezes e isso resultar na lentidão do banco de dados. Por padrão todos os seus usuários obtêm acesso a uma quantidade igual de recursos compartilhados, dentro do limite do que seu aplicativo pode suportar.

Digamos que o banco de dados usado por seu aplicativo possa manipular com segurança cerca de 1.000 consultas por minuto. Em seu aplicativo, você pode definir um limite para permitir apenas 1.000 solicitações por minuto para evitar que o banco de dados receba mais solicitações.

Em vez de um limite global de “1.000 solicitações por minuto”, você pode observar o uso médio do aplicativo e, por exemplo, definir um limite de “100 solicitações por usuário por minuto”. Ou encadear esses limites e definir a limitação como:   “100 solicitações por usuário por minuto e 1000 solicitações por minuto”.

Os limites de taxa  vão ajudar a evitar que o servidor seja sobrecarregado por muitas solicitações e ainda garante que todos os usuários tenham uma chance justa de obter suas solicitações processadas.

Usando o middleware Rate Limite

Em aplicações que usam o  .NET 7 (ou superior), agora existe um middleware de limitação de taxa  disponível e pronto para uso, e, ele fornece uma maneira de aplicar a limitação de taxa ao seu aplicativo da Web e aos endpoints da sua API.

Da mesma forma que é feita para outros middlewares, para habilitar o middleware Rate Limiting da ASP.NET Core, teremos que adicionar os serviços necessários à coleção de serviços e, em seguida, habilitar o middleware para todos os pipelines de solicitação.

A configuração  é feita na classe Program usando o método AddRateLimiter para um objeto builder.Services onde podemos definir algumas propriedades para o Rate Limiting através dos métodos de extensão da classe RateLimiterOptionsExtensions.

Existem vários algoritmos diferentes que podem ser usados com a limitação da taxa para controlar o fluxo de requisições.  Atualmente no .NET 7 são fornecidos os seguintes controles:

1 - Concurrency limit - O limitador de simultaneidade limita quantos requests simultâneas podem acessar um recurso. Se o seu limite for 10, então 10 requests podem acessar um recurso de uma só vez e o próximo rquest não será permitido. Depois que um request for concluído, o número de request permitidos aumenta para 1, quando um segundo request for concluído, o número aumenta para 2, etc.

É a forma mais simples de limitação de taxa. Ele não olha para o tempo, apenas para o número de solicitações simultâneas : “Permitir 10 solicitações simultâneas”. 

2- Token bucket limit - Token bucket é um algoritmo cujo nome deriva da descrição de como ele funciona. Imagine que há um balde cheio até a borda com fichas. Quando um request chega, ele pega um token e o mantém para sempre. Após algum período de tempo, alguém adiciona um número predeterminado de tokens de volta ao balde, nunca adicionando mais do que o balde pode conter. Se o balde estiver vazio, quando um request chegar, o request terá o acesso negado ao recurso.

O limite de balde de token permite controlar a taxa de fluxo e permite rajadas. Pense “você recebe 100 requests a cada minuto”, se você fizer todas elas em 10 segundos, terá que esperar 1 minuto antes poder fazer mais requests.

3 - Fixed window limit Usa o algoritmo de janela fixa de tempo e quantidade de requisições. A janela é uma quantidade de tempo que nosso limite é aplicado antes de passarmos para a próxima janela. No caso de janela fixa, mover para a próxima janela significa redefinir o limite de volta ao seu ponto inicial. Vamos imaginar que haja uma sala de cinema com uma sala individual com capacidade para 100 pessoas e que o filme tenha 2 horas de duração. Quando o filme começa, deixamos as pessoas começarem a fazer fila para a próxima exibição, que será em 2 horas, e, até 100 pessoas podem fazer fila antes de começarmos a dizer para elas voltarem em outro momento. Terminado o filme de 2 horas, a fila de 0 a 100 pessoas pode entrar na sala de cinema e reiniciar a fila. Isso é o mesmo que mover a janela no algoritmo de janela fixa.

Permite aplicar limites como “60 solicitações por minuto”. A cada minuto, 60 solicitações podem ser feitas. Um a cada segundo, mas também 60 de uma só vez.

4- Sliding window limitUsa uma janela variável de tempo e quantidade requisições, movendo o segmento a cada intervalo de tempo de segmento. Um segmento faz parte de uma janela, se pegarmos a janela anterior de 2 horas e a dividirmos em 4 segmentos, teremos agora 4 segmentos de 30 minutos. Há também um índice de segmento atual que sempre apontará para o segmento mais recente em uma janela. As solicitações durante um período de 30 minutos vão para o segmento atual e a cada 30 minutos a janela desliza um segmento. Se houve algum pedido durante o segmento pelo qual a janela desliza, eles agora são atualizados e nosso limite aumenta nesse valor. Se não houver solicitações, nosso limite permanece o mesmo.

É semelhante ao limite da janela fixa, mas usa segmentos para limites mais refinados. Pense “60 solicitações por minuto, com 1 solicitação por segundo”.

Além disso podemos definir algumas propriedades para o Rate Limiting como :

  1. PermitLimit (int) - Define o número de requests para uma janela específica;
  2. Window (TimeSpan) - Define o tempo da janela para requisições;
  3. QueueProcessingOrder  - Define a ordem do processamento;
  4. QueueLimit (int) - Define a quantidade máxima permitida de requests acumulada;

Assim considerando apenas os algorítmos Fixed Window Limit e Sliding Window Limit podemos fazer a seguinte configuração na classe Program:

Exemplo de configuração:

using ApiRateLimiting.Services;
using Microsoft.AspNetCore.RateLimiting;
using System.Threading.RateLimiting;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
...

builder.Services.AddRateLimiter(_ => _
    .AddFixedWindowLimiter(policyName: "LimiterPolicy", options =>
    {
        options.PermitLimit = 1;
        options.Window = TimeSpan.FromSeconds(10); // Permite apenas 1 a cada 10 segundos
        options.QueueProcessingOrder = QueueProcessingOrder.OldestFirst; //obtem o mais antigo da fila
        options.QueueLimit = 3; // Enfileira apenas 3 request quando o limite for ultrapassado
    }).
   .AddSlidingWindowLimiter("sliding", options =>
   {
        options.PermitLimit = 5;
        options.Window = TimeSpan.FromSeconds(10);
        options.SegmentsPerWindow = 5;
        options.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
        options.QueueLimit = 2;
    }));

var app = builder.Build();

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

Vamos entender o código:

A chamada para builder.Services.AddRateLimiter(...) registra o middleware ASP.NET Core com a coleção de serviços, incluindo suas opções de configuração. Há muitas opções que podem ser especificadas, como o código de status HTTP que está sendo retornado, o que deve acontecer quando a limitação de taxa se aplica e políticas adicionais.

Neste exemplo, estamos adicionando um AddFixedWindowLimiter e um AddSlidingWindowLimiter e usando as propriedades para configuramos cada rate limiting usando o policyName, que é o identificador da política de Rate Limiting especificado, e que precisará ser passado no momento de atribuir ao Controller ou Action, como será feito posteriormente;

O FixedWindowLimiter é então configurado para reabastecer automaticamente as solicitações permitidas e permite “1 request a cada 10 segundos” e o SlidingWindowLimiter é configurado para permitir '5 requests por segundo'.

Após configurar os Rate Limiters, é necessário chamar app.UseRateLimiter() para poder usar o recurso.

Para mostrar um exemplo prático vamos criar uma Web Api ASP .NET Core no .NET 7.0 usando o VS 2022 Community com o nome ApiRateLimiting.

recursos usados:

Criando o projeto API

Abra o VS 2022 e acione o menu Create New Project;

Na janela Add New Project selecione o template ASP.NET Core Web API informe o nome ApiRateLimiting e a seguir defina as seguintes configurações para criar o projeto:

Não vamos habilitar o Swagger e vamos usar controladores no projeto.

Crie no projeto a pasta Services e nesta pasta crie a classe a interface IHoraAtualService que representa nosso modelo de domínio:

namespace ApiRateLimiting.Services;

public interface IHoraAtualService
{
   TimeOnly HoraAtual();
}

Nesta interface definimos um contrato usando o recurso da struct TimeOnly.

A seguir na mesma pasta crie a classe HoraAtualService que implementa esta interface:

public class HoraAtualService : IHoraAtualService
{
 
public HoraAtualService()
  {}

   public TimeOnly HoraAtual()
   {
     
return TimeOnly.FromDateTime(DateTime.Now);
   }
}

Agora vamos configurar o middleware Rate Limiting na classe Program:

using ApiRateLimiting.Services;
using
Microsoft.AspNetCore.RateLimiting;
using
System.Threading.RateLimiting;

var builder = WebApplication.CreateBuilder(args);

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

builder.Services.AddScoped<IHoraAtualService, HoraAtualService>();

builder.Services.AddRateLimiter(_ => _
   .AddFixedWindowLimiter(policyName:
"LimiterPolicy", options =>
   {
      options.PermitLimit = 1;
      options.Window = TimeSpan.FromSeconds(10);
// Permite apenas 1 a cada 10 segundos
      options.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
//obtem o mais antigo da fila
      options.QueueLimit = 3;
// Enfileira apenas 3 request quando o limite for ultrapassado
    }));

var app = builder.Build();

app.UseRateLimiter();

// Configure the HTTP request pipeline.
app.UseHttpsRedirection();
app.MapControllers();

app.Run();

Registramos o serviço e configuramos o middleware para o Rate Limiting.

A seguir vamos criar na pasta Controllers o controlador HoraAtualController onde vamos usar o atributo EnableRateLimiting na Action:

using Microsoft.AspNetCore.RateLimiting;

namespace ApiRateLimiting.Controllers;

[Route("api/[controller]")]
[ApiController]

public
class HoraAtualController : ControllerBase
{
   IHoraAtualService _servico;

    public HoraAtualController(IHoraAtualService servico)
    {
        _servico = servico;
    }

    [HttpGet]
    [EnableRateLimiting(
"LimiterPolicy")]
   
public IActionResult Index()
    {
     
return Ok(_servico.HoraAtual());
    }
}

Executando o projeto iremos obter a hora atual:

Se tentarmos executar um segundo request logo a seguir veremos que isso não será feito imediatamente mas somente após 10 segundos conforme a politica definida na configuração do Rate Limiting.

Pegue o projeto aqui: ApiRateLimiting.zip  ...

"Regozijai-vos sempre.
Orai sem cessar.
Em tudo dai graças, porque esta é a vontade de Deus em Cristo Jesus para convosco."
1 Tessalonicenses 5:16-18

Referências:


José Carlos Macoratti