Limiter le nombre de requêtes vers une API

22 décembre 2018 · 3 min de lecture

Vous avez développé une API publique et vous voulez éviter les abus ? Il est possible de limiter l’accès à un nombre de requêtes déterminé selon une période donnée. Par exemple, si un utilisateur envoie deux demandes successives alors que vous n’autorisez qu’une requête toutes les 5 secondes, il obtiendra la réponse suivante :

Limite atteinte, veuillez réessayer dans 4 secondes.

Principe

L’adresse IP de l’utilisateur peut être utilisée pour l’identifier de manière unique. Lorsqu’il envoie une requête, on enregistre l’IP avec un horodatage d’expiration. Lors de la requête suivante, on vérifie si elle est autorisée à recevoir une réponse en comparant cette valeur avec la date et heure courante.

Solutions techniques

On pourrait utiliser une base de données comme MySQL ou SQLite pour enregistrer les données mais cela n’est pas conseillé :

  • si le trafic de votre appplication web est important, cela consommera beaucoup de ressources système et augmentera les temps d’accès.
  • il faudrait purger les adresses IP enregistrées pour éviter de gonfler inutilement la base de données.

Redis est donc conseillé pour ce cas d’utilisation. Ce logiciel est une base de données de type clé/valeur s’exécutant en mémoire. Si vous n’avez pas le système requis pour installer un serveur Redis, il existe une alternative : Redix. Celui-ci ne fonctionne pas en mémoire car il est basé sur BadgerDB. Il reste cependant très rapide et simple à utiliser.

Le code

Voici le code source en PHP pour limiter l’accès à votre API en autorisant une requête toutes les 5 secondes pour tout utilisateur et en utilisant Redix.

Installation du client Redis/Redix :

composer require predis/predis

Téléchargez Redix selon votre système d’exploitation, et démarrez le service :

./redix_linux_amd64

La réponse suivante indique que Redix écoute sur les ports 6380 pour le protocole RESP et 7090 pour le protocole HTTP.

hugo-search-engine

Dans votre API, ajoutez le code suivant en entête :

<?php
require_once 'class.ratelimit.redix.php';

$rl = new RateLimit();
$waitfor = $rl->getSleepTime($_SERVER['REMOTE_ADDR']);
if ($waitfor>0) {
    echo 'Limite atteinte, veuillez réessayer dans '.$waitfor.'s';
    exit;    
    }

// Réponse de votre API
echo 'réponse API';

Le code du script class.ratelimit.redix.php est :

<?php
require_once __DIR__.'/vendor/autoload.php';
Predis\Autoloader::register();

class RateLimit {

    private $redis;
    const RATE_LIMIT_SECS = 5; // autoriser 1 requête toutes les x secondes

    public function __construct() {
        $this->redis = new Predis\Client([
            'scheme' => 'tcp',
            'host'   => 'localhost', // ou IP du serveur sur lequel s'exécute Redix
            'port'   => 6380
            ]);
        }

    /**
     * Retourne le temps en secs à attendre avant d'autoriser à nouveau les requêtes pour l'utilisateur IP
     * @param ip {String}
     */
    public function getSleepTime($ip) {
        $value = $this->redis->get($ip);
        if(empty($value)) {
            // si la clé n'existe pas, on l'insère avec date/heure courant et expiration dans x secs
            $this->redis->set($ip, time(), self::RATE_LIMIT_SECS*1000);
            return 0;
            } 
        return self::RATE_LIMIT_SECS - (time() - intval(strval($value)));
        } // getSleepTime

    } // class RateLimit

Conclusion

Cette solution est relativement simple à mettre en place. La commande TTL peut également être utilisée dans la fonction getSleepTime. Elle retourne le temps restant à vivre d’une donnée avant expiration. Mais celle-ci n’est pas implémentée actuellement dans Redix.

Pour autoriser un plus grand nombre de requêtes, par exemple 180 en 15 minutes, Redis propose une solution de limitation des API qui utilise les paramètres INCR et EXPIRE.

Ressources

PARTAGER

A LIRE EGALEMENT