Accueil > Nouveauté d’ASP.NET Core 7 : le middleware Rate Limiting
Mohamed Ben Slimen
9 novembre 2022
Read this post in English

Nouveauté d’ASP.NET Core 7 : le middleware Rate Limiting

Nouveauté d'ASP.NET Core 7 : le middleware Rate Limiting

Dans la nouvelle version d’ASP.NET Core 7, un nouveau middleware est désormais disponible pour la gestion de limitation de débit. A quoi sert-il ? Comment le configurer ? Dans le nouvel article de cette série, nous vous proposons de découvrir les fonctionnalités de base de Rate Limiting.

 

Qu’est-ce qu’une limitation de débit ?

 

La limitation de débit est un mécanisme qui permet de contrôler le flux des demandes pour une ressource.

La technique utilisée repose sur la définition d’une métrique, souvent une valeur numérique, qui représente le seuil maximal ou minimal autorisé pour une ressource. Au-delà de cette limite, le système ne sera pas en mesure de garantir le traitement des demandes. Dans certains scénarios, on ajoute à l’équation le facteur temps pour définir une fenêtre de limitation de débit.

Si cette limite est atteinte, le système proposera alors un scénario alternatif qui peut varier d’un simple refus de la demande jusqu’à la mise en place d’un traitement différé.

 

Pourquoi mettre en place une limitation de débit ?

 

La notion de limitation de débit peut parfois laisser penser qu’il s’agit d’une dégradation de service alors que c’est l’inverse.

Un limiteur de débit contribue à l’amélioration de la sécurité de nos applications. Il agit en effet comme un contrôleur de flux qui empêche toute utilisation malveillante ou frauduleuse de nos systèmes, réduisant ainsi l’exposition à certains types d’attaques telles que les attaques DoS et DDoS, les attaques par force brute ou encore le « Web scraping » illégal.

Un limiteur de débit permet de se protéger contre une évolution vertigineuse des coûts d’exploitation. Par exemple, il est fortement recommandé d’utiliser un limiteur de débit pour les applications dotées d’un modèle de mise à l’échelle automatique, car il offre une protection contre toutes les évolutions excessives et non justifiées des charges de travail.

Au-delà des questions de sécurité et d’optimisation des coûts, un limiteur de débit peut être utilisé dans le cadre des applications dites “As a Service” afin de contrôler la fréquence d’accès à certaines ressources selon des plans tarifaires définis par le fournisseur du service.

 

La limitation de débit dans ASP.NET Core 7

 

Dans la nouvelle version .NET 7, Microsoft a annoncé qu’elle introduira un nouveau package System.Threading.RateLimiting prenant en charge la limitation de débit avec une implémentation des 4 modèles algorithmiques les plus répandus :

  • Token bucket
  • Concurrency
  • Fixed window limiter
  • Sliding window limiter

 

En plus de ces quatre modèles, on trouve aussi un nouveau middleware permettant d’ajouter des limiteurs de débit pour les applications ASP.NET Core.

Ce nouveau middleware est distribué via le package Microsoft.AspNetCore.RateLimiting

Les deux nouveaux packages sont disponibles via la nouvelle version du SDK .NET 7, que vous pouvez télécharger ici : https://dotnet.microsoft.com/en-us/download/dotnet/7.0

Dans la suite de cet article, nous allons passer en revue les différentes facettes de cette nouvelle annonce.

 

Configurer “Rate Limiting” dans une application ASP.NET Core

 

Avant de parler code, commençons par définir le cadre du problème.

Nous souhaitons exposer une route d’API qui retournera les prévisions météorologiques.

Pour les raisons que nous avons évoquées au début de notre article (sécurité, maîtrise des coûts, etc.), nous allons utiliser un limiteur de débit pour répondre aux prérequis suivants :

  • Prérequis 1 : notre API doit autoriser 5 requêtes maximum par appareil connecté, et cela pour un intervalle de 10 secondes.
  • Prérequis 2 : si la limite est dépassée, notre API doit retourner le code HTTP 429 avec le message suivant « vous avez atteint le nombre maximal des demandes autorisées pour votre adresse IP ({@ip_address}). »

 

Ajouter le middleware Rate Limiting dans notre application

 

Comme pour tous les middlewares ASP.NET Core, la technique reste la même :

  1. Utiliser la méthode d’extension « AddRateLimiter()» pour ajouter les services et la configuration requis au niveau de l’IoC.
  2. Ajouter le middleware à notre pipeline ASP.NET Core avec la méthode « UseRateLimiter()». L’ordre d’ajout est important pour ne pas dénaturer le fonctionnement d’autres middlewares comme la gestion du cache ou le système d’authentification.

 

Choix et configuration du limiteur de débit

 

Pour répondre à la première exigence, nous allons utiliser un FixedWindowRateLimiter (nous reviendrons plus tard dans l’article sur les spécificités de ce limiteur) :

  • PermitLimit : représente le nombre de demandes autorisées.
  • Window : la durée d’un cycle de limitation, ici de 10 secondes. Après chaque cycle, la valeur PermitLimit sera réinitialisée à 5 nouvelles demandes autorisées.

 

Adapter le message de retour en cas de limitation

 

En cas de dépassement du seuil limite, les demandes seront rejetées par le middleware et un message d’erreur avec le code « 503 – service unavailable » sera envoyé à l’initiateur de la demande.

Ce code d’erreur indique que notre API n’est pas en mesure de traiter la requête. Ceci pourrait engendrer une mauvaise interprétation du message par certains consommateurs du service, car il ne s’agit pas d’indisponibilité de service, mais plutôt d’un dépassement du nombre de requêtes autorisées.

Dans ce cas, nous allons utiliser l’option « RejectionStatusCode » pour changer le code de retour par un code plus adapté : le code HTTP « 429 – too many requests ».

Pour personnaliser le message de retour, nous utiliserons la fonction  « OnRejected » qui permet de définir une réponse plus détaillée en utilisant des informations depuis le « HttpContext ».

 

Partitions et variations de limiteur de débit

 

Dans l’exemple précédent, lors de la création du limiteur de débit, nous avons utilisé le « HttpContext » pour construire une clef de partition.

Les clefs de partition offrent une option intéressante qui nous permet de varier l’utilisation de plusieurs limiteurs pour répondre à une multitude de scénarios.

Pour comprendre concrètement ce que cela signifie, nous allons introduire de nouvelles règles pour notre API.

Prérequis 3 : une offre d’abonnement sera proposée dans les prochains jours avec les modalités suivantes :

  • Offre gratuite : les utilisateurs de cette offre seront limités à 5 demandes par minute.
  • Offre standard : les utilisateurs avec une offre standard disposent de 60 demandes par minute.
  • Offre premium : les utilisateurs avec une offre premium ne seront soumis à aucune limitation.

Le code ci-dessous montre comment utiliser trois variations du même limiteur pour répondre à cette nouvelle règle.

 

Modèles d’application des limites du débit

 

Dans les exemples précédents, nous avons utilisé un seul type de limiteur qui est à Fenêtre fixe. Mais qu’en est-il des trois autres algorithmes restants ?

Dans cette partie, nous allons revenir sur la spécificité de chaque modèle.

 

Le modèle du limiteur à « Token bucket limit »

 

Comme son nom l’indique, le modèle du « Token bucket limit » repose sur un système de bac à jetons : pour qu’une demande soit traitée, il faut disposer d’un jeton (token) valide. Une fois que la demande est traitée, le jeton est détruit. Le nombre maximal de jetons autorisés est défini par la capacité totale du bac.

Pour chaque demande, le système doit vérifier s’il reste des jetons disponibles dans le bac. Si c’est le cas, un jeton sera attribué à cette demande et la demande sera traitée. Si aucun jeton n’est disponible, la demande sera alors rejetée.

Il est possible de remettre des jetons dans le bac à des intervalles de temps fixes et définis à l’avance. En revanche, et c’est la particularité de ce modèle, le nombre total de jetons disponibles ne pourra jamais dépasser la capacité du bac sinon les jetons seront perdus.

 

Le modèle du limiteur à Fenêtre fixe (Fixed window limit / Fixed Window Counter)

 

Le modèle du limiteur de débit à fenêtre fixe repose sur une limitation à deux variables : un compteur pour les demandes autorisées et un intervalle de temps, appelé fenêtre du limiteur. Dans ce modèle, la fenêtre est considérée comme une entité fixe et indivisible.

Pour chaque demande reçue dans l’intervalle de temps, le compteur sera décompté par une unité. Si le compteur atteint la valeur 0 avant la fenêtre du temps, les prochaines demandes seront rejetées.

Le compteur sera réinitialisé à sa valeur initiale à chaque nouveau cycle de fenêtre. À ce moment-là, les nouvelles demandes seront de nouveau autorisées.

 

Le modèle du limiteur à Fenêtre glissante (Sliding window limit / Sliding window Counter)

 

Contrairement au précédent modèle de fenêtre fixe, le limiteur à fenêtre glissante ne limite pas les requêtes en fonction d’une unité de temps indivisible. La fenêtre du limiteur est divisée en plusieurs segments de temps égal.

Par exemple, pour une fenêtre de 20 minutes et un nombre de segments par fenêtre égal à 2, nous obtenons 2 fenêtres de segments de 10 minutes chacun.

Les nouvelles demandes reçues seront attribuées à la fenêtre du segment en cours. Leur nombre sera décompté du nombre des demandes reçues et elles ne peuvent plus être attribuées, du moins pour le moment.

A la fin de chaque intervalle de segment, la fenêtre du limiteur glisse par unité de segment vers la droite, ce qui libèrera un segment du côté gauche, le nombre des demandes attribuées à ce segment libéré sera de nouveau réattribué.

 

Le sens de glissement (gauche, droite) est donné pour faciliter la construction d’une image imaginaire pour mieux comprendre le fonctionnement de ce modèle.

Pour encore mieux visualiser les étapes et l’évolution des différentes métriques, voici une simulation du code ci-dessus :

  • Légendes :
    • Intervalle / segment : représente la fenêtre du segment “S” en cours.
      • Disponibles : c’est le nombre de demandes autorisées pour le segment courant nommé “S”.
      • Prises : indique le nombre de demandes reçues pendant la fenêtre du segment “S”.
      • Réattribuées : c’est le nombre de demandes autorisées de nouveau suite à la libération du segment “S-2”.
      • Reportées : c’est le nombre de demandes autorisées pour le nouveau segment “S+1” à la fin de la période du segment “S”.
    • Simulation :

A l’instant T=0, le segment en cours est le S2 (en gras dans le tableau).

Time Disponibles (S) Prises (S) Réattribués (S-2) Reportées (S+1)
0 /S1 40 0 0 0
0 /S2 40 10 0 30
10/S3 30 5 0 25
20/S4 25 10 10 25
30/S5 25 20 5 10
30/S6 10 0 10 20

 

 

Le modèle du limiteur d’accès concurrent (Concurrency limit)

 

L’objectif de ce modèle est de limiter le nombre de requêtes à traiter en simultané par une ressource.

Pour chaque demande, le nombre de requêtes autorisées sera décompté d’une unité et un verrou est appliqué sur cette valeur durant la période de traitement de la demande. Une fois la limite atteinte, toutes les demandes futures seront rejetées.

Le compteur sera incrémenté d’une unité quand une demande en cours est terminée et que le verrou est levé.

A titre personnel, je trouve que ce modèle est le plus simple à comprendre, mais le moins prévisible de tous.

 

Test du limiteur de débit

 

Avant la fin, il est important de rappeler que le déploiement d’un limiteur de débit dans un environnement de production ne peut pas être sans conséquences, qu’elles soient positives ou négatives. Il ne faut donc pas négliger la phase de test pour valider le bon fonctionnement de vos limiteurs avant de les envoyer en production.

Des outils comme JMeter et Azure Load Testing vous permettent de réaliser des tests de charge afin de valider la concordance entre vos besoins et le résultat produit par vos limiteurs.

Voici le résultat obtenu avec JMeter pour notre tout premier scénario :

résultat obtenu avec JMeter pour notre tout premier scénario

 

Aller plus loin sur Rate Limiting

 

Dans cet article, nous avons pris connaissance des fonctionnalités de base du nouveau middleware Rate Limiting. Il reste encore de nombreuses autres fonctionnalités à découvrir comme la gestion des stratégies, la gestion de l’authentification ou encore la prise en charge d’un reverse proxy, etc.

Vous trouverez plus de détails et d’autres informations utiles dans cette annonce : Rate Limiting for .NET.

 

Vous souhaitez en savoir plus sur les nouveautés de .NET 7 ? Retrouvez les autres articles de cette série :

Nos autres articles
Commentaires
Laisser un commentaire

Restez au courant des dernières actualités !
Le meilleur de l’actualité sur le Cloud, le DevOps, l’IT directement dans votre boîte mail.