Toute API exposée au public finira par se faire marteler, que ce soit par un client buggé qui boucle sur une erreur, un scraper, ou quelqu’un qui en abuse franchement. La limitation de débit est la façon de protéger votre service et de le garder équitable pour tout le monde. J’en ai ajouté à beaucoup d’API, et les concepts sont plus simples que le jargon le laisse croire.
Choisissez un algorithme
Il existe quelques approches classiques qui arbitrent entre précision et coût. La fenêtre fixe compte les requêtes par tranche de temps, disons 100 par minute, et se réinitialise à la frontière. C’est très simple mais cela autorise des rafales aux bords, puisqu’un client peut tirer 100 requêtes à la fin d’une minute et 100 au début de la suivante. La fenêtre glissante lisse cela en pondérant la fenêtre précédente. Le seau à jetons remplit des jetons à un rythme constant et laisse les clients les dépenser en rafales jusqu’à un plafond, ce qui colle le mieux au trafic réel.
- Fenêtre fixe : la plus facile à construire, autorise des rafales aux bords.
- Fenêtre glissante : plus précise, un peu plus de comptabilité.
- Seau à jetons : gère les rafales avec souplesse, mon choix habituel.
Un seau à jetons simple
L’idée est que chaque client a un seau de jetons. Chaque requête coûte un jeton, et les jetons se rechargent avec le temps. Si le seau est vide, la requête est rejetée. Voici la logique de base, en ignorant le stockage un instant :
function allow(bucket, now, rate, capacity) {
const elapsed = (now - bucket.last) / 1000;
bucket.tokens = Math.min(capacity, bucket.tokens + elapsed * rate);
bucket.last = now;
if (bucket.tokens >= 1) {
bucket.tokens -= 1;
return true;
}
return false;
}
Cette fonction recharge selon le temps écoulé, plafonne le seau pour que les jetons ne s’accumulent pas indéfiniment, et dépense un jeton par requête autorisée. C’est peut-être quinze lignes et cela couvre la grande majorité des besoins réels.
Identifiez correctement le client
La limitation de débit ne vaut que ce que vaut votre idée de ce qu’est un client. Pour le trafic authentifié, indexez la limite sur la clé d’API ou l’identifiant utilisateur, ce qui est fiable. Pour le trafic anonyme, vous vous rabattez sur l’adresse IP, qui est imparfaite car des utilisateurs derrière le même réseau partagent une IP et les proxys peuvent l’usurper. Si vous êtes derrière un proxy ou un CDN, lisez l’en-tête transmis, mais ne lui faites confiance que lorsque la requête est vraiment passée par votre infrastructure. Mal choisir la clé revient soit à punir des utilisateurs innocents, soit à ne pas arrêter les abuseurs.
Répondez de la bonne façon
Quand vous rejetez une requête, faites-le avec le bon statut HTTP et des en-têtes utiles pour que les clients bien élevés s’adaptent. Le statut est 429 Too Many Requests. Incluez toujours un en-tête Retry-After indiquant au client combien de temps attendre, et les en-têtes standard de limitation pour qu’il s’auto-régule avant de heurter le mur :
HTTP/1.1 429 Too Many Requests
Retry-After: 30
RateLimit-Limit: 100
RateLimit-Remaining: 0
RateLimit-Reset: 30
Content-Type: application/json
{ "error": "limite de debit depassee, reessayez dans 30 secondes" }
Un bon client lit ces en-têtes et lève le pied poliment. Un mauvais les ignore, mais au moins vous lui avez laissé sa chance, et vous avez une trace propre de la raison de votre refus.
Où stocker les compteurs
Un compteur en mémoire fonctionne pour un seul serveur et s’effondre dès que vous passez à deux, car chaque instance a sa propre vue. Pour quoi que ce soit de distribué, il vous faut un état partagé. Redis est le choix classique ; il est rapide et possède des opérations d’incrément atomiques qui rendent cela facile. À la périphérie, un magasin clé-valeur comme le KV de Cloudflare ou les Durable Objects fait le même travail près de l’utilisateur. Quoi que vous choisissiez, la mise à jour du compteur doit être atomique, sinon deux requêtes simultanées peuvent lire le même compte et passer toutes les deux.
Combinez-la avec d’autres défenses
La limitation de débit est une couche, pas tout le mur. Associez-la à l’authentification, à la validation des entrées et à des délais d’attente raisonnables. Placez-la le plus tôt possible dans le cycle de vie de la requête, idéalement avant tout travail coûteux, pour qu’un flot de requêtes bloquées ne vous coûte presque rien. Si vous déployez ce genre de service vous-même, la même plateforme de périphérie que je décris dans le déploiement sur Cloudflare Pages peut faire tourner l’API et son magasin de limitation ensemble, et vous pouvez la publier en confiance via un pipeline GitHub Actions. Construisez le limiteur une fois, gardez-le ennuyeux, et il fait tranquillement son travail sous charge.

Laisser un commentaire