PHP
Client-Side Leaky Bucket Rate Limiter for API Calls
Implement a simple client-side leaky bucket rate limiter in PHP to control your outbound API request frequency and avoid exceeding external API limits.
<?php
class LeakyBucketRateLimiter {
private $bucketCapacity; // Max requests allowed in a burst
private $leakRatePerSecond; // How many requests 'leak' out per second
private $tokens;
private $lastRefillTime;
/**
* @param int $capacity The maximum number of tokens the bucket can hold (burst capacity).
* @param float $leakRate The rate at which tokens are added to the bucket (requests per second).
*/
public function __construct($capacity, $leakRate) {
if ($capacity <= 0 || $leakRate <= 0) {
throw new InvalidArgumentException("Capacity and leak rate must be positive.");
}
$this->bucketCapacity = $capacity;
$this->leakRatePerSecond = $leakRate;
$this->tokens = $capacity; // Start with a full bucket
$this->lastRefillTime = microtime(true);
}
private function refill() {
$currentTime = microtime(true);
$timeElapsed = $currentTime - $this->lastRefillTime;
if ($timeElapsed > 0) {
$tokensToAdd = $timeElapsed * $this->leakRatePerSecond;
$this->tokens = min($this->bucketCapacity, $this->tokens + $tokensToAdd);
$this->lastRefillTime = $currentTime;
}
}
/**
* Attempts to consume a token. If the bucket is empty, it will wait.
* @return bool True if a token was consumed (request can proceed), false if it timed out waiting.
*/
public function acquireToken($timeoutSeconds = 30) {
$startTime = microtime(true);
while (true) {
$this->refill();
if ($this->tokens >= 1) {
$this->tokens -= 1;
return true; // Token acquired, proceed with request
}
if ((microtime(true) - $startTime) > $timeoutSeconds) {
// Timeout reached, could not acquire token
return false;
}
// Sleep for a short period before retrying
usleep(50000); // 50 milliseconds
}
}
}
// Example Usage:
// Assume API allows 100 requests per minute (1.66 requests per second)
// and a burst of 10 requests.
// $rateLimiter = new LeakyBucketRateLimiter(10, 1.66);
// for ($i = 0; $i < 20; $i++) {
// if ($rateLimiter->acquireToken()) {
// echo "Request " . ($i + 1) . ": Sending API call at " . date('H:i:s.v') . "
";
// // Simulate API call
// // usleep(100000); // Simulate network latency
// } else {
// echo "Request " . ($i + 1) . ": Rate limit exceeded or timed out waiting.
";
// break;
// }
// }
How it works: This PHP snippet implements a client-side leaky bucket rate limiter, a mechanism to control the outgoing frequency of API requests to respect external API limits. The bucket has a `capacity` (burst limit) and a `leakRate` (steady-state request rate). When `acquireToken()` is called, the bucket is 'refilled' based on elapsed time, and if there are enough 'tokens' (representing available requests), one is consumed, and the request proceeds. If the bucket is empty, the client waits until a token becomes available or a timeout occurs, preventing the application from overwhelming the target API and incurring rate limit errors.