aslain.dev
0%
01 Hizmetler 02 Hakkımda 03 Projeler 04 Stack 05 Blog 06 İletişim
← Tüm makaleler Développement Web

API Rate Limiting : stratégies de limitation des requêtes

L'API rate limiting est la technique qui consiste à restreindre le nombre de requêtes qu'un client peut envoyer à votre API sur une fenêtre de temps donnée. L'objectif n'est jamais unique : bloquer les abus et les attaques par force brute, garder des coûts d'infrastructure prévisibles, empêcher qu'un seul client n'épuise les ressources au détriment des autres et garantir une répartition équitable de l'usage. Une couche de limitation bien conçue rend votre API à la fois plus sûre et plus stable.

Pourquoi avez-vous besoin du rate limiting ?

Un endpoint non protégé peut s'effondrer rapidement dans plusieurs scénarios. Une boucle infinie dans un client, un bot malveillant ou un utilisateur qui abuse de votre offre gratuite peut déclencher des centaines de requêtes par seconde. Ces situations entraînent :

  • Épuisement des ressources : connexions à la base de données, CPU et mémoire monopolisés par un seul client.
  • Explosion des coûts : les factures cloud et les appels d'API tierces croissent sans contrôle.
  • Risque de sécurité : devinette de mot de passe, force brute d'OTP et scraping deviennent faciles.
  • Iniquité : un utilisateur agressif ruine l'expérience de tous les autres.

L'algorithme token bucket

Le token bucket est la méthode la plus utilisée grâce à sa flexibilité. L'idée est simple : chaque client dispose d'un seau de capacité fixe, et des jetons y sont ajoutés à un rythme régulier. Chaque requête consomme un jeton ; si le seau est vide, la requête est rejetée. Comme le seau a une capacité, il tolère de courtes rafales (bursts) tout en maintenant le débit moyen constant dans le temps.

class TokenBucket {
  constructor(capacity, refillPerSecond) {
    this.capacity = capacity;
    this.tokens = capacity;
    this.refillRate = refillPerSecond;
    this.last = Date.now();
  }

  tryRemove(count = 1) {
    const now = Date.now();
    const elapsed = (now - this.last) / 1000;
    this.tokens = Math.min(this.capacity, this.tokens + elapsed * this.refillRate);
    this.last = now;

    if (this.tokens >= count) {
      this.tokens -= count;
      return true;
    }
    return false;
  }
}

Ici, capacity définit la taille de rafale autorisée et refillRate fixe le débit moyen soutenable. Par exemple, avec une capacité de 20 et un rythme de remplissage de 5 par seconde, un client peut envoyer jusqu'à 20 requêtes en rafale, mais se stabilise à 5 requêtes par seconde sur le long terme.

L'algorithme sliding window

Un simple compteur à « fenêtre fixe » (par exemple 100 requêtes par minute) crée un problème de bordure : si 100 requêtes arrivent dans la dernière seconde d'une fenêtre et 100 dans la première seconde de la suivante, 200 requêtes passent en deux secondes. La sliding window résout cela avec une plage de temps glissante. La forme la plus précise est le sliding window log, qui stocke l'horodatage de chaque requête et élimine celles qui sortent de la fenêtre. On l'implémente couramment dans Redis avec un sorted set :

-- ajouter l'horodatage avec ZADD, purger les anciens, puis compter
local key = KEYS[1]
local now = tonumber(ARGV[1])
local window = tonumber(ARGV[2])
local limit = tonumber(ARGV[3])

redis.call('ZREMRANGEBYSCORE', key, 0, now - window)
local count = redis.call('ZCARD', key)
if count < limit then
  redis.call('ZADD', key, now, now)
  redis.call('PEXPIRE', key, window)
  return 1
end
return 0

La méthode du log est la plus précise, mais elle stocke chaque horodatage par client. Si la mémoire est limitée, préférez l'approche dite sliding window counter : elle pondère les compteurs de la fenêtre fixe actuelle et de la précédente pour estimer une valeur approximative, en utilisant bien moins de mémoire au prix d'une petite marge d'erreur.

Partager les compteurs dans une architecture distribuée

Si vous exécutez plusieurs serveurs applicatifs, chacun gardant son propre compteur en mémoire multiplie de fait la limite par le nombre de serveurs. La solution consiste à conserver les compteurs dans un stockage central et rapide — généralement Redis. Les commandes atomiques de Redis (INCR, opérations sur les sorted sets) et les scripts Lua exécutés en un seul aller-retour assurent un comptage cohérent sans conditions de concurrence. De nombreux frameworks le proposent nativement : Laravel expose la façade RateLimiter et un middleware throttle, côté Express il y a express-rate-limit avec un store Redis, et NGINX dispose de la directive limit_req.

Réponses et en-têtes HTTP corrects

Envoyer les bons signaux lors du rejet d'une requête fait partie d'une bonne conception d'API. Renvoyez le code standard 429 Too Many Requests et indiquez au client quand il pourra réessayer :

  • Retry-After : nombre de secondes avant un nouvel essai autorisé.
  • RateLimit-Limit : total de requêtes autorisées par fenêtre.
  • RateLimit-Remaining : quota de requêtes restant.
  • RateLimit-Reset : temps restant avant la réinitialisation du compteur.
HTTP/1.1 429 Too Many Requests
Retry-After: 30
RateLimit-Limit: 100
RateLimit-Remaining: 0
RateLimit-Reset: 30
Content-Type: application/json

{"error":"rate_limit_exceeded","message":"Trop de requêtes. Réessayez dans 30 secondes."}

Encouragez les clients à lire ces en-têtes et à réessayer avec un backoff exponentiel. Ainsi, un client rejeté ne reviendra pas tous en même temps dès que la limite est levée, évitant une nouvelle vague de charge.

Conseils pratiques

  • Choisissez la bonne clé : limitez par utilisateur/clé d'API pour le trafic authentifié et par IP pour le trafic anonyme. L'IP seule peut induire en erreur derrière un proxy ou un NAT.
  • Limites par paliers : appliquez des limites plus strictes aux endpoints coûteux (recherche, export, connexion) et plus souples aux lectures légères.
  • Fail-open ou fail-closed : décidez délibérément s'il faut rejeter ou laisser passer les requêtes si Redis tombe.
  • Observez : suivez votre taux de 429 et quelles clés atteignent la limite ; distinguez l'abus réel des pics légitimes.

Questions fréquentes

Faut-il utiliser token bucket ou sliding window ?

Si vous voulez autoriser de courtes rafales tout en préservant le débit moyen, le token bucket est idéal et simple à implémenter. Si vous devez éliminer absolument le problème de doublement à la bordure et souhaitez un comptage très précis, choisissez le sliding window log. Pour la plupart des API, le token bucket est largement suffisant.

Où appliquer le rate limiting ?

Idéalement de façon superposée : en frontal avec NGINX/CDN/API gateway pour une protection grossière, et dans la couche applicative pour des limites fines et propres au métier. La couche applicative peut prendre des décisions plus intelligentes car elle connaît le contexte, comme l'identité de l'utilisateur.

Quelle différence entre 429 et 503 ?

429 Too Many Requests signifie que le client a dépassé sa propre limite ; la responsabilité incombe au client. 503 Service Unavailable indique que le serveur est globalement surchargé ou en maintenance. Pour le rate limiting, 429 est toujours le bon code.

Vous voulez construire une couche de rate limiting solide pour votre API ? Du token bucket aux compteurs distribués basés sur Redis, je peux concevoir une solution adaptée à votre infrastructure existante. Contactez-moi et sécurisons votre projet ensemble.

Bu kategorideki tüm yazılar →

Devamı için