Une interface de programmation ouverte sans limitation fonctionne parfaitement jusqu'au jour où un client mal écrit tourne en boucle, où un partenaire lance une synchronisation complète en pleine journée, ou où quelqu'un décide de tester la résistance du service. La limitation de débit n'est pas une mesure de défiance envers les utilisateurs légitimes : c'est ce qui garantit que le service reste disponible pour tous, y compris quand l'un d'eux se comporte mal. Elle se met en place en quelques heures, elle repose sur des mécanismes bien établis , documentés depuis longtemps et implémentés dans toutes les bibliothèques sérieuses,, et son principal enjeu n'est pas technique mais consiste à choisir la bonne unité de comptage et les bons seuils. Ces deux décisions déterminent si le dispositif protège réellement le service ou s'il se contente de gêner les utilisateurs honnêtes.
Pourquoi limiter le débit
La première raison est la protection de la disponibilité. Toute ressource est finie : connexions à la base, mémoire, processus disponibles. Un client émettant mille requêtes par seconde consomme l'intégralité de ces ressources et rend le service indisponible pour tous les autres, sans qu'aucune intention malveillante ne soit nécessaire. Une boucle mal écrite dans un script de synchronisation produit exactement le même effet qu'une attaque, et elle est infiniment plus fréquente. La limitation transforme cet incident global en incident local, limité au client fautif. C'est exactement l'effet recherché : le service continue de fonctionner pour les vingt autres clients pendant que l'un d'eux se heurte à sa limite.
La deuxième raison est économique. Sur une architecture où les ressources se paient à l'usage, un client en boucle produit une facture. Sur une architecture à ressources fixes, il produit un incident. Dans les deux cas, la limitation borne le coût maximal d'un client donné, ce qui rend le service prévisible. C'est également ce qui permet de proposer des niveaux de service différenciés, chaque palier correspondant à un débit autorisé. La notion générale d'interface est rappelée dans notre article sur l'API REST et sa définition. Sans limitation, aucune offre commerciale différenciée n'est réellement tenable, puisque rien n'empêche un client de consommer les ressources prévues pour un autre palier.
La troisième raison est la sécurité. Une limitation stricte sur les points d'entrée d'authentification rend impraticable l'essai systématique de mots de passe. Elle ralentit également l'énumération de ressources, où un attaquant parcourt les identifiants un par un pour découvrir ce qui existe. Sur ces points d'entrée précis, la limitation est une mesure de sécurité à part entière et non un simple réglage de confort, ce qui justifie des seuils bien plus stricts qu'ailleurs. Quelques essais par minute suffisent largement à un usage légitime, alors qu'un attaquant en a besoin de milliers.

Les algorithmes de limitation
Quatre mécanismes couvrent l'ensemble des besoins, du plus simple au plus élaboré, et le choix entre eux dépend surtout du comportement que l'on souhaite autoriser. Le compteur par fenêtre fixe divise le temps en intervalles, une minute par exemple, et compte les requêtes dans l'intervalle courant. C'est le plus simple à écrire et il souffre d'un défaut connu : un client peut émettre le double du quota autorisé à cheval sur deux fenêtres, en concentrant ses requêtes à la fin de l'une et au début de la suivante. Sur des seuils larges, ce défaut est sans conséquence pratique. Il devient en revanche gênant sur les points d'entrée sensibles, où l'on cherche précisément à empêcher les rafales.
La fenêtre glissante corrige ce point en comptant les requêtes sur les soixante dernières secondes plutôt que sur la minute courante. Elle demande de conserver l'horodatage de chaque requête, ce qui coûte plus cher en mémoire, ou d'utiliser une approximation pondérée entre la fenêtre courante et la précédente, qui donne un résultat très proche pour un coût comparable à la fenêtre fixe. C'est en général le meilleur compromis pour une interface publique. L'approximation pondérée s'écrit en quelques lignes et se stocke dans un service de cache mémoire, ce qui la rend très économique.
Le seau à jetons ajoute une notion précieuse : la tolérance aux pointes. Le client dispose d'une réserve de jetons qui se reconstitue à un rythme constant, et chaque requête en consomme un. Il peut donc émettre une rafale tant que la réserve le permet, puis se trouve limité au rythme de reconstitution. Ce comportement correspond exactement à l'usage réel d'un client légitime, qui alterne des périodes calmes et des rafales, et c'est pourquoi il est retenu par la plupart des services établis. Cette logique d'exploitation rejoint les pratiques décrites dans notre article sur le DevOps et ses principes. Le quatrième mécanisme, le seau percé, lisse au contraire complètement le débit sans autoriser de rafale, ce qui convient aux traitements dont le coût unitaire est élevé et constant.
Sur quelle clé compter
Le choix de l'unité de comptage détermine l'efficacité et les effets de bord du dispositif, et il est bien plus délicat que le choix de l'algorithme. Compter par adresse réseau est le réflexe le plus courant et le plus problématique : plusieurs utilisateurs partagent souvent la même adresse publique, une entreprise entière ou un opérateur mobile pouvant se présenter sous une seule adresse. Une limitation stricte par adresse bloque alors des utilisateurs légitimes en masse. Le symptôme est caractéristique : des rejets concentrés sur quelques adresses correspondant à de grosses organisations ou à des opérateurs mobiles.
Compter par clé d'interface, lorsque les clients sont authentifiés, est nettement supérieur : chaque client dispose de son propre quota, et un client abusif ne pénalise que lui même. C'est la solution à retenir chaque fois que l'authentification est obligatoire. Elle suppose que la clé soit vérifiée avant le comptage, sinon un attaquant peut envoyer des clés inventées et faire exploser le nombre de compteurs à maintenir, ce qui constitue une attaque en soi. Le contrôle de la clé doit donc précéder le comptage, et les clés inconnues doivent être rejetées avant toute écriture de compteur.
Pour les points d'entrée accessibles sans authentification, la combinaison d'un comptage par adresse avec des seuils généreux et d'un comptage plus strict par ressource demandée offre un bon compromis. Sur un formulaire de contact ou une tentative de connexion, le comptage doit combiner l'adresse et l'identifiant visé, afin de ralentir à la fois les essais sur un compte donné et les essais depuis une origine donnée. Cette double clé est la configuration recommandée sur ces points sensibles. La détection des comportements anormaux relève ensuite d'une logique plus large, décrite dans notre article sur la manière de centraliser et exploiter les journaux de sécurité.
Origines relevées sur des interfaces exposées publiquement. Les deux premières causes se règlent par un échange avec le client plutôt que par un durcissement des seuils.
Où placer la limitation
Plus le contrôle intervient tôt dans la chaîne, moins il coûte cher. Une limitation appliquée par le répartiteur de charge ou par le service en périphérie rejette la requête avant qu'elle n'atteigne l'application, ce qui préserve entièrement les ressources du serveur. C'est la position idéale, et elle est disponible sans développement sur la plupart des offres d'hébergement modernes. Elle protège en outre contre les volumes que le serveur ne pourrait pas absorber, même pour les rejeter. Son inconvénient est de ne pas connaître le contexte applicatif, ce qui limite la finesse des règles. Elle ignore notamment l'identité du client authentifié, information qui n'existe qu'au niveau applicatif.
Une limitation appliquée par le serveur web intervient un peu plus tard mais avant le démarrage de l'interpréteur, ce qui reste très économique. Les modules disponibles pour les serveurs courants permettent des règles par adresse et par chemin, avec une configuration déclarative. C'est un bon compromis lorsque le contrôle en périphérie n'est pas disponible, et il se met en place en quelques lignes de configuration. Il faut simplement veiller à ce que le serveur voie la véritable adresse d'origine et non celle du service placé devant lui, réglage qui s'oublie systématiquement.
Une limitation applicative, écrite dans le code, arrive en dernier et coûte le plus cher, puisque toute la pile a déjà démarré. Elle offre en contrepartie la seule finesse réellement utile : compter par client authentifié, appliquer des quotas différents selon le niveau de service, pondérer selon le coût réel de l'opération demandée. Ces trois capacités ne sont accessibles nulle part ailleurs, ce qui rend ce niveau de contrôle indispensable dès que les clients sont identifiés. La bonne architecture combine les deux niveaux, un filtre grossier en amont et un contrôle fin dans l'application. Le filtre amont protège l'infrastructure, le contrôle applicatif protège l'équité entre clients, et les deux objectifs sont distincts.
| Emplacement | Coût par requête rejetée | Finesse des règles |
|---|---|---|
| Service en périphérie | Nul pour le serveur | Faible |
| Répartiteur de charge | Très faible | Faible |
| Serveur web | Faible | Moyenne |
| Point d'entrée applicatif | Moyen | Élevée |
| Contrôleur métier | Élevé | Très élevée |
Répondre correctement au client
Une requête rejetée doit renvoyer le code de réponse dédié à cette situation, distinct de celui d'une erreur d'autorisation ou d'une indisponibilité. Ce code existe précisément pour cet usage et les bibliothèques clientes savent l'interpréter. Renvoyer une erreur générique oblige le développeur du client à deviner ce qui se passe , en l'absence de tout indice dans la réponse,, et il conclura souvent que le service est en panne, ce qui produit exactement le comportement que l'on cherche à éviter : une nouvelle tentative immédiate. Renvoyer une erreur serveur est encore pire, puisque la plupart des bibliothèques clientes réessaient automatiquement dans ce cas.
La réponse doit comporter un en-tête indiquant le délai à attendre avant de réessayer. C'est l'information la plus utile de tout le dispositif, puisqu'elle permet à un client correctement écrit de se réguler tout seul. Y ajouter des en-têtes indiquant la limite applicable, le nombre de requêtes restantes et l'instant de réinitialisation du quota transforme la limitation en information exploitable, et permet aux clients de répartir leur charge plutôt que de se heurter au mur. Ces en-têtes suivent des conventions largement adoptées, qu'il vaut mieux respecter plutôt que d'inventer les siennes.
Le corps de la réponse doit expliquer la situation en une phrase, indiquer la limite et pointer vers la documentation. Cette clarté fait toute la différence en pratique : un développeur qui comprend immédiatement ce qui se passe corrige son client en dix minutes, un développeur qui ne comprend pas ouvre un ticket et attend. Le coût de traitement de ce ticket dépasse largement celui de la rédaction du message d'erreur. Sur une interface destinée à des partenaires, ce point relève autant de la relation commerciale que de la technique. Une limitation bien expliquée passe pour du sérieux, une limitation opaque pour de la mauvaise volonté. Une page de documentation décrivant les limites, avec des exemples de gestion côté client, évite l'essentiel des sollicitations.
Les cas particuliers
Toutes les opérations n'ont pas le même coût, et un quota unique en nombre de requêtes traite de la même façon une lecture triviale et un export complet. Pondérer chaque opération par son coût réel, en consommant plusieurs jetons pour les traitements lourds, donne un dispositif nettement plus juste. Cette pondération demande de connaître le coût des opérations , en temps de traitement comme en volume de données retourné,, ce qui suppose de l'avoir mesuré, exercice utile en lui même. À défaut de mesure précise, deux ou trois classes de coût suffisent déjà à rendre le dispositif nettement plus juste.
Les opérations par lots méritent une attention particulière. Un client qui doit récupérer dix mille enregistrements les demandera en dix mille requêtes s'il n'a pas d'autre choix, ce qui est absurde pour tout le monde. Proposer un point d'entrée permettant de traiter plusieurs éléments en une requête, avec un quota adapté, résout le problème à la source. C'est souvent la meilleure réponse à un client qui sature les limites : lui donner un moyen plus efficace de faire ce qu'il cherche à faire. Un point d'entrée de synchronisation incrémentale, ne renvoyant que ce qui a changé, règle d'ailleurs la plupart de ces situations.
Enfin, certains clients méritent des quotas particuliers : un partenaire important, un service interne, un outil de supervision. Ces exceptions doivent être gérées par configuration et non par des conditions écrites dans le code, faute de quoi elles se multiplient et deviennent ingérables. Un service de supervision interrogeant l'interface toutes les trente secondes doit par exemple être exclu du comptage, sans quoi il consomme un quota et fausse les mesures. Une table associant chaque clé à son niveau de service, modifiable sans redéploiement, est la bonne structure et elle se met en place dès le départ. Cette table doit également porter la date de révision de chaque exception, faute de quoi les dérogations accordées un jour deviennent définitives.
Observer et régler
Les seuils ne se devinent pas, ils s'observent. La bonne méthode consiste à mettre le dispositif en place en mode observation, sans rejeter aucune requête, et à enregistrer ce qui aurait été rejeté. Quelques jours de ce fonctionnement donnent la distribution réelle du trafic par client, et les seuils se placent ensuite bien au dessus du comportement légitime observé. Activer une limitation avec des seuils choisis au hasard produit invariablement des rejets de clients parfaitement normaux. Ces rejets se paient en tickets, en appels et en confiance, trois choses bien plus coûteuses que quelques jours d'observation. Une semaine d'observation suffit sur la plupart des services, deux si l'activité comporte des pointes hebdomadaires marquées.
Les rejets doivent être journalisés avec la clé concernée, l'heure et le point d'entrée visé. Ces données permettent de distinguer un client mal écrit d'une attaque, et de contacter le premier plutôt que de le laisser se heurter au mur pendant des semaines. La distinction se fait sur la régularité du rythme et sur la nature des points d'entrée sollicités, deux critères très parlants. Un tableau de bord affichant les dix clés les plus limitées suffit à repérer les situations à traiter , sans qu'il soit nécessaire de construire un outillage élaboré,, et il devrait être consulté au même rythme que les autres indicateurs d'exploitation. Un client qui atteint sa limite chaque jour à la même heure signale presque toujours une tâche planifiée mal dimensionnée de son côté.
Enfin, les seuils doivent être revus périodiquement. Les usages évoluent, les partenaires grandissent, les clients se multiplient. Un seuil fixé il y a deux ans peut être devenu trop strict pour un usage légitime ou trop généreux pour la protection recherchée. La croissance d'un partenaire est la cause la plus fréquente de la première situation. Une révision semestrielle, à partir des données réellement observées, maintient le dispositif utile, et elle est bien plus rapide que le réglage initial puisque la méthode est déjà en place. Il vaut mieux annoncer à l'avance tout durcissement des seuils, afin que les clients puissent adapter leur code sans subir de rupture. Un délai de deux à quatre semaines entre l'annonce et l'application est généralement suffisant, et il vaut mieux l'accompagner d'un en tête indiquant le quota restant, qui permet aux clients de s'adapter automatiquement.
💬 0 commentaires
✍️ Laisser un commentaire
Créez un compte gratuit ou connectez-vous pour commenter.