Notes
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de vous connecter ou de modifier des répertoires.
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de modifier des répertoires.
Dans cet article, vous allez apprendre à créer un gestionnaire HTTP côté client qui limite le nombre de requêtes qu’il envoie. Vous verrez un HttpClient qui accède à la ressource "www.example.com"
. Les ressources sont consommées par les applications qui s’appuient sur elles, et lorsqu’une application effectue trop de demandes pour une seule ressource, elle peut entraîner une contention de ressources. La contention des ressources se produit lorsqu’une ressource est consommée par trop d’applications et que la ressource ne peut pas servir toutes les applications qui le demandent. Cela peut entraîner une mauvaise expérience utilisateur et, dans certains cas, il peut même entraîner une attaque par déni de service (DoS). Pour plus d’informations sur DoS, consultez OWASP : Déni de service.
Qu’est-ce que la limitation du débit ?
La limitation du débit est le concept de limitation de la quantité d’accès à une ressource. Par exemple, vous savez peut-être qu’une base de données que votre application accède peut gérer en toute sécurité 1 000 requêtes par minute, mais elle peut ne pas gérer beaucoup plus que cela. Vous pouvez placer un limiteur de débit dans votre application qui autorise uniquement 1 000 requêtes toutes les minutes et rejette toutes les demandes avant qu’elles puissent accéder à la base de données. Par conséquent, la limitation du débit de votre base de données permet à votre application de gérer un nombre sécurisé de requêtes. Il s’agit d’un modèle courant dans les systèmes distribués, où vous pouvez avoir plusieurs instances d’une application en cours d’exécution et que vous souhaitez vous assurer qu’elles n’essaient pas toutes d’accéder à la base de données en même temps. Il existe plusieurs algorithmes de limitation de débit différents pour contrôler le flux des requêtes.
Pour utiliser la limitation de débit dans .NET, vous allez référencer le package NuGet System.Threading.RateLimiting .
Implémenter une DelegatingHandler
sous-classe
Pour contrôler le flux de requêtes, vous implémentez une sous-classe personnalisée DelegatingHandler . Il s’agit d’un type de HttpMessageHandler ce qui vous permet d’intercepter et de gérer les requêtes avant qu’elles ne soient envoyées au serveur. Vous pouvez également intercepter et gérer les réponses avant qu’elles ne soient retournées à l’appelant. Dans cet exemple, vous allez implémenter une sous-classe personnalisée DelegatingHandler
qui limite le nombre de requêtes pouvant être envoyées à une seule ressource. Considérez la classe personnalisée ClientSideRateLimitedHandler
suivante :
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();
}
}
}
Le code C# précédent :
- Hérite du type
DelegatingHandler
. - Implémente l’interface IAsyncDisposable.
- Définit un
RateLimiter
champ affecté par le constructeur. - Remplace la
SendAsync
méthode pour intercepter et gérer les requêtes avant qu’elles ne soient envoyées au serveur. - Remplace la DisposeAsync() méthode pour supprimer l’instance
RateLimiter
.
En regardant un peu plus près la SendAsync
méthode, vous verrez qu’elle :
- S’appuie sur l’instance
RateLimiter
pour acquérir unRateLimitLease
à partir deAcquireAsync
. - Lorsque la
lease.IsAcquired
propriété esttrue
, la demande est envoyée au serveur. - Sinon, un HttpResponseMessage est retourné avec un code de statut
429
et, silease
contient une valeurRetryAfter
, l’en-têteRetry-After
est défini sur cette valeur.
Émuler de nombreuses requêtes simultanées
Pour placer cette sous-classe personnalisée DelegatingHandler
dans le test, vous allez créer une application console qui émule de nombreuses requêtes simultanées. Cette Program
classe crée un HttpClient avec le ClientSideRateLimitedHandler
personnalisé :
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})");
}
Dans l’application console précédente :
- Les
TokenBucketRateLimiterOptions
sont configurés avec une limite de jetons8
, un ordre de traitement de file d'attente deOldestFirst
, une limite de file d'attente de3
, une période de réapprovisionnement de1
millisecondes, une valeur de jetons par période de2
, et une valeur de réapprovisionnement automatique detrue
. - Un
HttpClient
créé avec leClientSideRateLimitedHandler
qui est configuré avec leTokenBucketRateLimiter
. - Pour émuler 100 requêtes, Enumerable.Range crée 100 URL, chacune avec un paramètre de chaîne de requête unique.
- Deux Task objets sont attribués à partir de la Parallel.ForEachAsync méthode, en fractionnant les URL en deux groupes.
-
HttpClient
est utilisé pour envoyer une requêteGET
à chaque URL et la réponse est écrite dans la console. - Task.WhenAll attend que les deux tâches se terminent.
Étant donné que HttpClient
est configuré avec ClientSideRateLimitedHandler
, toutes les demandes ne parviennent pas à la ressource serveur. Vous pouvez tester cette assertion en exécutant l’application console. Vous verrez que seule une fraction du nombre total de requêtes sont envoyées au serveur, et que le reste est rejeté avec un code d’état HTTP de 429
. Essayez de modifier l’objet options
utilisé pour créer l’objet TokenBucketRateLimiter
pour voir comment le nombre de demandes envoyées au serveur change.
Considérez l’exemple de sortie suivant :
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)
Vous remarquerez que les premières entrées journalisées sont toujours les 429 réponses immédiatement retournées et que les dernières entrées sont toujours les 200 réponses. Cela est dû au fait que la limite de débit est rencontrée côté client et évite d’effectuer un appel HTTP à un serveur. C’est une bonne chose, car cela signifie que le serveur n’est pas inondé de requêtes. Cela signifie également que la limite de débit est appliquée de manière cohérente sur tous les clients.
Notez également que la chaîne de requête de chaque URL est unique : examinez le iteration
paramètre pour voir qu’il est incrémenté d’un pour chaque requête. Ce paramètre permet d’illustrer que les 429 réponses ne proviennent pas des premières requêtes, mais plutôt des requêtes effectuées après la limite de débit atteinte. Les 200 réponses arrivent plus tard, mais ces demandes ont été effectuées plus tôt, avant que la limite ne soit atteinte.
Pour mieux comprendre les différents algorithmes de limitation de débit, essayez de réécrire ce code pour accepter une autre RateLimiter implémentation. En plus de TokenBucketRateLimiter, vous pouvez essayer :
Résumé
Dans cet article, vous avez appris à implémenter un fichier personnalisé ClientSideRateLimitedHandler
. Ce modèle peut être utilisé pour implémenter un client HTTP limité à débit pour les ressources que vous connaissez ont des limites d’API. De cette façon, vous empêchez votre application cliente d’effectuer des requêtes inutiles sur le serveur, et vous empêchez également votre application d’être bloquée par le serveur. En outre, avec l’utilisation des métadonnées pour stocker les valeurs de minutage des nouvelles tentatives, vous pouvez également implémenter la logique de nouvelle tentative automatique.