La limitation d’accès à une ressource est une problématique courante, par exemple pour s’assurer que les clients d’une API Web ne dépassent pas les quotas d’utilisation qui leurs sont alloués.

L’algorithme du seau à jetons fournit une réponse simple et ingénieuse que je vous propose d’illustrer par une implémentation en PHP objet où le stockage des données de contrôle est confié à Memcache.

Le commentaire placé en début de l’implémentation montre comment limiter à 30 fois par heure l’accès à une ressource.

<?php
/*
 * Usage:
 *
 * $_30_times = 30;
 * $_per_hour = 60 * 60.0;
 * try {
 *     if ((new RateLimiter())->allow("resource-to-rate-limit", $_30_times, $_per_hour)) {
 *         // Access granted
 *     } else {
 *         // Access denied
 *     }
 * } catch (Exception $e) {
 *     // Something went wrong with Memcache
 * }
 */
 
if (! class_exists('Memcache')) {
	throw new Exception("RateLimiter requires Memcache.");
}
 
class RateLimiter {
	private $quota, $ttl, $time, $stock;
 
	public function allow($key, $quota = 60, $period = 60.0) {
		$allow = true;
 
		try {
                        $memcache = new Memcache();
		} catch (Exception $e) {
			throw new Exception('Failed to create memcache object.');
                }
 
		if (! $memcache->connect('127.0.0.1')) {
			throw new Exception('Failed to connect to memcache server.');
		}
 
		if (! ($cached = $memcache->get($key))) {
			$quota = max(1, intval($quota));
			$period = max(0.000001, floatval($period));
 
			$this->quota = $quota;
			$this->ttl = $period / $quota;
			$this->time = microtime(true) - $period - $this->ttl;
			$this->stock = $quota - 1;
 
			if (! $memcache->add($key, $this)) {
				throw new Exception('Failed to add memcache key.');
			}
		} else {
			$this->quota = $cached->quota;
			$this->ttl = $cached->ttl;
			$this->time = microtime(true);
			$this->stock = min($cached->stock + intval(($this->time - $cached->time) / $this->ttl), $this->quota);
 
			if ($this->stock > 0) {
				$this->stock--;
			} else {
				$allow = false;
			}
 
			if (! $memcache->replace($key, $this)) {
				throw new Exception('Failed to replace memcache key.');
			}
		}
 
		if (! $memcache->close()) {
			throw new Exception('Failed to close memcache connection.');
		}
 
		unset($memcache);
 
		return $allow;
	}
}

En jouant sur le nom de la resource, vous pourrez combiner plusieurs paramètres pour spécifier au mieux le contrôle que vous souhaitez pouvoir exercer.

À supposer que nous soyons dans le code d’un point d’API nommé « api-auth » en charge de l’authentification des utilisateurs, la ressource pourrait être nommée dynamiquement « api-auth-$_SERVER[REMOTE_ADDR] ».

Cette construction du nom de la ressource permettrait de limiter l’accès à « api-auth » de manière sélective en fonction le l’adresse IP du client distant.

N’hésitez pas à cloner cette classe et à en proposer des variantes, comme une implémentation utilisant Memcached ou Redis au lieu de Memcache, voire une implémentation en Python utilisant les décorateurs pour contrôler l’accès à une fonction ou une méthode.