sanmai/rate-limiter
最新稳定版本:0.1.4
Composer 安装命令:
composer require sanmai/rate-limiter
包简介
Cache-based rate limiter using sliding window algorithm
README 文档
README
Cache-based API rate limiting for PHP applications.
Table of Contents
- Overview
- Features
- How It Works
- Installation
- Quick Start
- Advanced Usage
- Cache Adapters
- Technical Details
- Contributing
- License
Installation
composer require sanmai/rate-limiter
Overview
Rate limiting with the sliding window approach.
Real-world example: Imagine you need to limit API requests to 100 per minute and 1000 per hour per client. This library lets you create a rate limiter with a 1-minute window and 1-hour observation period, then check if a client exceeds either of these limits.
Features
- Two-level limiting - Window-based and period-based limits
- Lazy evaluation - Calculates limits only when needed
- PSR-compatible - Easily integrates with PSR-15 middleware
How it works (the simple version)
This rate limiter provides two types of limits:
- Window limits - Controls request rates in the most recent time window (e.g., 100 requests per minute)
- Period limits - Controls total requests over a longer observation period (e.g., 1000 requests per hour)
The rate limiter itself tracks requests, while the limits are set when checking if they've been exceeded.
Quick Start
Setting up a rate limiter
// Import necessary classes use SlidingWindowCounter\RateLimiter\RateLimiter; use SlidingWindowCounter\Cache\MemcachedAdapter; // Create a rate limiter for an IP address with 1-minute windows and 1-hour observation period $rateLimiter = RateLimiter::create( '192.168.1.1', // Subject being rate limited (e.g., IP address) 'api_requests', // Name for your rate limiter 60, // Window size: 60 seconds (1 minute) 3600, // Observation period: 3600 seconds (1 hour) new MemcachedAdapter($memcached) );
Tracking requests
// Record a request from this client $rateLimiter->increment(); // You can also increment by a specific amount (for weighted actions) $rateLimiter->increment(2); // Count this action as 2 requests
Checking rate limits
// Check if the client has exceeded window limit (100 requests per minute) $windowResult = $rateLimiter->checkWindowLimit(100); if ($windowResult->isLimitExceeded()) { // Window limit exceeded - client is sending requests too quickly echo $windowResult->getLimitExceededMessage(); // Example output: "Rate limit exceeded for 192.168.1.1: 120 actions in the window (limit: 100)" // Return 429 Too Many Requests response header('HTTP/1.1 429 Too Many Requests'); header(sprintf('Retry-After: %d', $windowResult->getWaitTimeSeconds())); exit; } // Check if the client has exceeded period limit (1000 requests per hour) $periodResult = $rateLimiter->checkPeriodLimit(1000); if ($periodResult->isLimitExceeded()) { // Period limit exceeded - client has sent too many requests in the observation period echo $periodResult->getLimitExceededMessage(); // Return 429 Too Many Requests response header('HTTP/1.1 429 Too Many Requests'); header(sprintf('Retry-After: %d', $periodResult->getWaitTimeSeconds())); exit; }
Getting more information
// Get information about the current rate limit status $windowResult = $rateLimiter->checkWindowLimit(100); // Subject being rate limited $subject = $windowResult->getSubject(); // e.g., "192.168.1.1" // Current count in the window $count = $windowResult->getCount(); // Maximum limit $limit = $windowResult->getLimit(); // Type of limit $limitType = $windowResult->getLimitType(); // "window" or "period" // Get the limit message (only if exceeded) $message = $windowResult->getLimitExceededMessage(); // Get wait time in seconds (rounded up) - useful for Retry-After header $waitSeconds = $windowResult->getWaitTimeSeconds(); // Get wait time in nanoseconds - useful for precise sleeping $waitNanoseconds = $windowResult->getWaitTime(); // Get wait time with jitter to avoid thundering herd (0.5 = up to 50% extra delay) $waitWithJitter = $windowResult->getWaitTime(0.5); // Get the latest value in the current window $currentValue = $rateLimiter->getLatestValue(); // Get the total across all windows in the observation period $totalRequests = $rateLimiter->getTotal();
Advanced Usage
Using multiple rate limiters for different constraints
You can create different rate limiters for different types of constraints:
// General rate limiter with 1-minute windows and 1-hour observation period $generalLimiter = RateLimiter::create($clientIp, 'general_api', 60, 3600, $cache); // Check if client exceeds 100 requests per minute $windowResult = $generalLimiter->checkWindowLimit(100); // Check if client exceeds 1000 requests per hour $periodResult = $generalLimiter->checkPeriodLimit(1000); // Stricter limiter for sensitive endpoints with same time parameters $sensitiveLimiter = RateLimiter::create($clientIp, 'sensitive_api', 60, 3600, $cache); // Check if client exceeds 10 requests per minute for sensitive endpoints $sensitiveWindowResult = $sensitiveLimiter->checkWindowLimit(10); // Check if client exceeds 50 requests per hour for sensitive endpoints $sensitivePeriodResult = $sensitiveLimiter->checkPeriodLimit(50);
Self-throttling for background jobs
When you control both ends (e.g., a background job calling your own API), you can use the wait time to self-throttle instead of failing:
use DuoClock\DuoClock; $clock = new DuoClock(); $rateLimiter = RateLimiter::create($jobId, 'batch_processing', 60, 3600, $cache); foreach ($items as $item) { $rateLimiter->increment(); $result = $rateLimiter->checkWindowLimit(100); if ($result->isLimitExceeded()) { // Wait until the rate limit resets using DuoClock's nanosleep $clock->nanosleep($result->getWaitTime()); } processItem($item); }
If you're not using DuoClock, you can use PHP's time_nanosleep() directly:
$ns = $result->getWaitTime(); time_nanosleep(intdiv($ns, 1_000_000_000), $ns % 1_000_000_000);
When multiple workers compete for the same rate limit, use jitter to spread out retries and avoid thundering herd:
$result = $rateLimiter->checkWindowLimit(100); if ($result->isLimitExceeded()) { // Add up to 50% random delay to spread out competing workers $clock->nanosleep($result->getWaitTime(0.5)); }
Note on wait time calculation: The wait time assumes a uniform distribution of requests across the window. If requests are bursty (clustered at the start or end of the window), the actual required wait time may differ. For most use cases this approximation works well.
Implementing in middleware
Here's how you might implement rate limiting in a PSR-15 middleware:
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface { $ip = $request->getServerParams()['REMOTE_ADDR']; // Create rate limiter $rateLimiter = RateLimiter::create($ip, 'api_requests', 60, 3600, $this->cache); // Increment the counter $rateLimiter->increment(); // Check window limit (e.g., 100 requests per minute) $windowResult = $rateLimiter->checkWindowLimit(100); if ($windowResult->isLimitExceeded()) { return $this->createRateLimitResponse( $windowResult->getLimitExceededMessage(), $windowResult->getWaitTimeSeconds() ); } // Check period limit (e.g., 1000 requests per hour) $periodResult = $rateLimiter->checkPeriodLimit(1000); if ($periodResult->isLimitExceeded()) { return $this->createRateLimitResponse( $periodResult->getLimitExceededMessage(), $periodResult->getWaitTimeSeconds() ); } // Limits not exceeded, continue with the request return $handler->handle($request); }
Error Handling
Here are some common scenarios and how to handle them:
try { // Create the rate limiter $rateLimiter = RateLimiter::create($ip, 'api_requests', 60, 3600, $cache); // Increment and check limits $rateLimiter->increment(); $windowResult = $rateLimiter->checkWindowLimit(100); // Handle rate limit exceeded if ($windowResult->isLimitExceeded()) { // Log the rate limit event $this->logger->warning('Rate limit exceeded', [ 'ip' => $ip, 'count' => $windowResult->getCount(), 'limit' => $windowResult->getLimit(), 'type' => $windowResult->getLimitType() ]); // Return appropriate response with calculated wait time return $this->createRateLimitResponse( $windowResult->getLimitExceededMessage(), $windowResult->getWaitTimeSeconds() ); } } catch (Exception $e) { // If the cache service is unavailable, fail open (allow the request) $this->logger->error('Rate limiter error', ['exception' => $e]); // Continue processing the request return $handler->handle($request); }
Cache Adapters
This library uses the cache adapters provided by the sanmai/sliding-window-counter library. For information about available adapters and how to create your own, please refer to the sliding window counter documentation.
License
See the LICENSE file for details.
统计信息
- 总下载量: 3.89k
- 月度下载量: 0
- 日度下载量: 0
- 收藏数: 7
- 点击次数: 1
- 依赖项目数: 0
- 推荐数: 0
其他信息
- 授权协议: Apache-2.0
- 更新时间: 2025-04-11