Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
Neste artigo, você aprenderá a criar um manipulador HTTP do lado do cliente que limita o número de solicitações enviadas por ele. Você verá um HttpClient que acessa o recurso "www.example.com". Os recursos são consumidos por aplicativos que dependem deles e, quando um aplicativo faz muitas solicitações para um único recurso, isso pode levar à contenção de recursos. A contenção de recursos ocorre quando um recurso é consumido por muitos aplicativos e o recurso não consegue atender a todos os aplicativos que o solicitam. Isso pode resultar em uma experiência ruim do usuário e, em alguns casos, pode até levar a um ataque de DoS (negação de serviço). Para obter mais informações sobre o DoS, consulte OWASP: Negação de Serviço.
O que é a limitação de taxa?
A limitação de taxa é o conceito de limitar o quanto um recurso pode ser acessado. Por exemplo, você pode saber que um banco de dados acessado por seu aplicativo pode lidar com segurança com 1.000 solicitações por minuto, mas ele pode não lidar com muito mais do que isso. Você pode colocar um limitador de taxa em seu aplicativo que permite apenas 1.000 solicitações a cada minuto e rejeita mais solicitações antes que elas possam acessar o banco de dados. Assim, ele limita a taxa do banco de dados e permite que o aplicativo manipule um número seguro de solicitações. Esse é um padrão comum em sistemas distribuídos, em que você pode ter várias instâncias de um aplicativo em execução e deseja garantir que elas não tentem acessar o banco de dados ao mesmo tempo. Há vários algoritmos de limitação de taxa diferentes para controlar o fluxo de solicitações.
Para usar a limitação de taxa no .NET, você fará referência ao pacote NuGet System.Threading.RateLimiting .
Implementar uma DelegatingHandler subclasse
Para controlar o fluxo de solicitações, implemente uma subclasse personalizada DelegatingHandler . Esse é um tipo de HttpMessageHandler que permite interceptar e manipular solicitações antes de serem enviadas ao servidor. Você também pode interceptar e manipular as respostas antes que elas sejam retornadas ao chamador. Neste exemplo, você implementará uma subclasse personalizada DelegatingHandler que limita o número de solicitações que podem ser enviadas a um único recurso. Considere a seguinte classe personalizada ClientSideRateLimitedHandler :
internal sealed class ClientSideRateLimitedHandler(
RateLimiter limiter)
: DelegatingHandler(new HttpClientHandler()), IAsyncDisposable
{
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
using RateLimitLease lease = await limiter.AcquireAsync(
permitCount: 1, cancellationToken);
if (lease.IsAcquired)
{
return await base.SendAsync(request, cancellationToken);
}
var response = new HttpResponseMessage(HttpStatusCode.TooManyRequests);
if (lease.TryGetMetadata(
MetadataName.RetryAfter, out TimeSpan retryAfter))
{
response.Headers.Add(
"Retry-After",
((int)retryAfter.TotalSeconds).ToString(
NumberFormatInfo.InvariantInfo));
}
return response;
}
async ValueTask IAsyncDisposable.DisposeAsync()
{
await limiter.DisposeAsync().ConfigureAwait(false);
Dispose(disposing: false);
GC.SuppressFinalize(this);
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (disposing)
{
limiter.Dispose();
}
}
}
O código C# anterior:
- Herda o tipo
DelegatingHandler. - Implementa a interface IAsyncDisposable.
- Define um campo
RateLimiteratribuído a partir do construtor. - Substitui o método
SendAsyncpara interceptar e manipular solicitações antes de serem enviadas ao servidor. - Sobrescreve o método DisposeAsync() para liberar a instância
RateLimiter.
Olhando um pouco mais de perto para o SendAsync método, você verá que ele:
- Depende da instância
RateLimiterpara adquirir umRateLimitLeasedoAcquireAsync. - Quando a
lease.IsAcquiredpropriedade étrue, a solicitação é enviada para o servidor. - Caso contrário, um HttpResponseMessage será retornado com um código de status
429e, se oleasecontiver um valorRetryAfter, o cabeçalhoRetry-Afterserá definido para esse valor.
Emular muitas solicitações simultâneas
Para colocar essa subclasse personalizada DelegatingHandler à prova, você criará um aplicativo de console que emula muitas solicitações simultâneas. Essa classe Program cria um HttpClient com o ClientSideRateLimitedHandler personalizado:
var options = new TokenBucketRateLimiterOptions
{
TokenLimit = 8,
QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
QueueLimit = 3,
ReplenishmentPeriod = TimeSpan.FromMilliseconds(1),
TokensPerPeriod = 2,
AutoReplenishment = true
};
// Create an HTTP client with the client-side rate limited handler.
using HttpClient client = new(
handler: new ClientSideRateLimitedHandler(
limiter: new TokenBucketRateLimiter(options)));
// Create 100 urls with a unique query string.
var oneHundredUrls = Enumerable.Range(0, 100).Select(
i => $"https://example.com?iteration={i:0#}");
// Flood the HTTP client with requests.
var floodOneThroughFortyNineTask = Parallel.ForEachAsync(
source: oneHundredUrls.Take(0..49),
body: (url, cancellationToken) => GetAsync(client, url, cancellationToken));
var floodFiftyThroughOneHundredTask = Parallel.ForEachAsync(
source: oneHundredUrls.Take(^50..),
body: (url, cancellationToken) => GetAsync(client, url, cancellationToken));
await Task.WhenAll(
floodOneThroughFortyNineTask,
floodFiftyThroughOneHundredTask);
static async ValueTask GetAsync(
HttpClient client, string url, CancellationToken cancellationToken)
{
using var response =
await client.GetAsync(url, cancellationToken);
Console.WriteLine(
$"URL: {url}, HTTP status code: {response.StatusCode} ({(int)response.StatusCode})");
}
No aplicativo de console anterior:
- Os
TokenBucketRateLimiterOptionssão configurados com um limite de token de8, e a ordem de processamento de filaOldestFirst, um limite de fila3, um período de reabastecimento de1milissegundos, um valor de tokens por período de2e um valor de reabastecimento automático detrue. - Um
HttpClienté criado com oClientSideRateLimitedHandlerconfigurado com oTokenBucketRateLimiter. - Para emular 100 solicitações, Enumerable.Range cria 100 URLs, cada uma com um parâmetro de cadeia de caracteres de consulta exclusivo.
- Dois Task objetos são atribuídos do Parallel.ForEachAsync método, dividindo as URLs em dois grupos.
- O
HttpClienté usado para enviar uma solicitaçãoGETpara cada URL e a resposta é gravada no console. - Task.WhenAll aguarda a conclusão das duas tarefas.
Dado que a HttpClient está configurada com a ClientSideRateLimitedHandler, nem todas as solicitações chegarão ao recurso do servidor. Você pode testar essa declaração executando o aplicativo de console. Você verá que apenas uma fração do número total de solicitações é enviada ao servidor e o restante é rejeitado com um código de status HTTP de 429. Tente alterar o options objeto usado para criar TokenBucketRateLimiter para ver como o número de solicitações enviadas para o servidor é alterado.
Considere a seguinte saída de exemplo:
URL: https://example.com?iteration=06, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=60, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=55, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=59, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=57, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=11, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=63, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=13, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=62, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=65, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=64, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=67, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=14, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=68, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=16, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=69, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=70, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=71, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=17, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=18, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=72, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=73, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=74, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=19, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=75, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=76, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=79, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=77, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=21, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=78, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=81, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=22, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=80, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=20, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=82, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=83, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=23, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=84, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=24, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=85, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=86, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=25, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=87, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=26, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=88, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=89, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=27, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=90, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=28, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=91, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=94, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=29, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=93, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=96, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=92, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=95, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=31, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=30, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=97, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=98, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=99, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=32, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=33, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=34, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=35, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=36, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=37, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=38, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=39, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=40, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=41, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=42, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=43, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=44, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=45, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=46, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=47, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=48, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=15, HTTP status code: OK (200)
URL: https://example.com?iteration=04, HTTP status code: OK (200)
URL: https://example.com?iteration=54, HTTP status code: OK (200)
URL: https://example.com?iteration=08, HTTP status code: OK (200)
URL: https://example.com?iteration=00, HTTP status code: OK (200)
URL: https://example.com?iteration=51, HTTP status code: OK (200)
URL: https://example.com?iteration=10, HTTP status code: OK (200)
URL: https://example.com?iteration=66, HTTP status code: OK (200)
URL: https://example.com?iteration=56, HTTP status code: OK (200)
URL: https://example.com?iteration=52, HTTP status code: OK (200)
URL: https://example.com?iteration=12, HTTP status code: OK (200)
URL: https://example.com?iteration=53, HTTP status code: OK (200)
URL: https://example.com?iteration=07, HTTP status code: OK (200)
URL: https://example.com?iteration=02, HTTP status code: OK (200)
URL: https://example.com?iteration=01, HTTP status code: OK (200)
URL: https://example.com?iteration=61, HTTP status code: OK (200)
URL: https://example.com?iteration=05, HTTP status code: OK (200)
URL: https://example.com?iteration=09, HTTP status code: OK (200)
URL: https://example.com?iteration=03, HTTP status code: OK (200)
URL: https://example.com?iteration=58, HTTP status code: OK (200)
URL: https://example.com?iteration=50, HTTP status code: OK (200)
Você observará que as primeiras entradas registradas são sempre as 429 respostas retornadas imediatamente e as últimas entradas são sempre as 200 respostas. Isso ocorre porque o limite de taxa é encontrado no lado do cliente e evita fazer uma chamada HTTP para um servidor. Isso é bom porque significa que o servidor não está inundado de solicitações. Isso também significa que o limite de taxa é imposto consistentemente em todos os clientes.
Observe também que a cadeia de caracteres de consulta de cada URL é exclusiva: examine o iteration parâmetro para ver se ele é incrementado por um para cada solicitação. Esse parâmetro ajuda a ilustrar que as 429 respostas não são das primeiras solicitações, mas sim das solicitações feitas após o limite de taxa ser atingido. As 200 respostas chegam mais tarde, mas essas solicitações foram feitas anteriormente antes do limite ser atingido.
Para ter uma melhor compreensão dos vários algoritmos de limitação de taxa, tente reescrever esse código para aceitar uma implementação diferente RateLimiter . Além de TokenBucketRateLimiter, você pode tentar:
Resumo
Neste artigo, você aprendeu a implementar um recurso personalizado ClientSideRateLimitedHandler. Esse padrão pode ser usado para implementar um cliente HTTP limitado por taxa para recursos que você sabe que têm limites de API. Dessa forma, você está impedindo que seu aplicativo cliente faça solicitações desnecessárias ao servidor e também está impedindo que seu aplicativo seja bloqueado pelo servidor. Além disso, com o uso de metadados para armazenar valores de tempo de repetição, você também pode implementar a lógica de repetição automática.