friendsofhyperf / rate-limit
Rate limiting component for Hyperf with support for multiple algorithms (Fixed Window, Sliding Window, Token Bucket, Leaky Bucket).
Fund package maintenance!
huangdijia
hdj.me/sponsors
Installs: 17
Dependents: 0
Suggesters: 0
Security: 0
Stars: 0
Watchers: 0
Forks: 0
pkg:composer/friendsofhyperf/rate-limit
Requires
- hyperf/collection: ~3.1.0
- hyperf/config: ~3.1.0
- hyperf/context: ~3.1.0
- hyperf/di: ~3.1.0
- hyperf/redis: ~3.1.0
- hyperf/stringable: ~3.1.0
- hyperf/support: ~3.1.0
Suggests
- hyperf/http-server: Required for middleware support.
This package is auto-updated.
Last update: 2025-11-19 00:54:10 UTC
README
Rate limiting component for Hyperf with support for multiple algorithms (Fixed Window, Sliding Window, Token Bucket, Leaky Bucket).
Installation
composer require friendsofhyperf/rate-limit
Requirements
- Hyperf ~3.1.0
- Redis
Features
- Multiple Rate Limiting Algorithms
- Fixed Window
- Sliding Window
- Token Bucket
- Leaky Bucket
- Flexible Usage
- Annotation-based rate limiting via Aspect
- Custom middleware support
- Smart Order for Multiple Annotations
- Automatic prioritization of multiple RateLimit annotations
- Intelligent ordering by strictness (maxAttempts/decay ratio)
- More restrictive limits evaluated first for better performance
- Flexible Key Generation
- Default method/class-based keys
- Custom key with placeholders support
- Array keys support
- Callable keys support
- Customizable Responses
- Custom response message
- Custom HTTP response code
- Multi Redis Pool Support
Usage
Method 1: Using Annotation
The easiest way to add rate limiting is using the #[RateLimit] attribute:
<?php namespace App\Controller; use FriendsOfHyperf\RateLimit\Annotation\RateLimit; use FriendsOfHyperf\RateLimit\Algorithm; class UserController { /** * Basic rate limiting: 60 attempts per 60 seconds */ #[RateLimit(maxAttempts: 60, decay: 60)] public function index() { // Your code here } /** * Using sliding window algorithm */ #[RateLimit( maxAttempts: 100, decay: 60, algorithm: Algorithm::SLIDING_WINDOW )] public function api() { // Your code here } /** * Custom key with user ID placeholder */ #[RateLimit( key: 'user:{userId}:action', maxAttempts: 10, decay: 3600 )] public function create($userId) { // Your code here } /** * Using array key */ #[RateLimit( key: ['user', '{userId}', 'create'], maxAttempts: 5, decay: 60 )] public function update($userId) { // Your code here } /** * Custom response message and code */ #[RateLimit( maxAttempts: 5, decay: 60, response: 'Too many requests, please try again later.', responseCode: 429 )] public function login() { // Your code here } /** * Using specific Redis pool */ #[RateLimit( maxAttempts: 60, decay: 60, pool: 'rate_limit' )] public function heavyOperation() { // Your code here } }
Annotation Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
key |
string|array |
'' |
Rate limit key. Supports: 'user:{user_id}', ['user', '{user_id}'], or callable |
maxAttempts |
int |
60 |
Maximum number of attempts allowed |
decay |
int |
60 |
Time window in seconds |
algorithm |
Algorithm |
Algorithm::FIXED_WINDOW |
Algorithm to use: fixed_window, sliding_window, token_bucket, leaky_bucket |
pool |
?string |
null |
The Redis connection pool to use |
response |
string |
'Too Many Attempts.' |
Custom response when rate limit is exceeded |
responseCode |
int |
429 |
HTTP response code when rate limit is exceeded |
Multiple RateLimits with AutoSort
When you need multiple rate limits on the same method (e.g., per-minute and per-hour limits), you can use the AutoSort annotation to automatically prioritize them:
<?php use FriendsOfHyperf\RateLimit\Annotation\RateLimit; use FriendsOfHyperf\RateLimit\Annotation\AutoSort; class ApiController { /** * Multiple rate limits with smart prioritization * Stricter limits (smaller maxAttempts/decay ratio) are evaluated first */ #[AutoSort] #[RateLimit(maxAttempts: 10, decay: 60)] // 10 requests/minute - evaluated first #[RateLimit(maxAttempts: 100, decay: 3600)] // 100 requests/hour - evaluated second public function expensiveOperation() { // Your code here } /** * Without AutoSort, rate limits are evaluated in declaration order */ #[RateLimit(maxAttempts: 100, decay: 3600)] // Evaluated first #[RateLimit(maxAttempts: 10, decay: 60)] // Evaluated second public function anotherOperation() { // Your code here } }
Benefits of AutoSort:
- Performance: Stricter limits are checked first, avoiding unnecessary checks of more lenient limits
- Intelligence: Automatically calculates priority based on limit strictness (maxAttempts/decay ratio)
- Opt-in: Only affects methods where
AutoSortis explicitly used - Backward Compatible: Existing code continues to work without changes
Key Placeholders
The key parameter supports dynamic placeholders that will be replaced with method arguments:
// Named placeholders #[RateLimit(key: 'user:{userId}:{action}')] public function action($userId, $action) // Array format (automatically joined with ':') #[RateLimit(key: ['user', '{userId}', '{action}'])] public function action($userId, $action)
Method 2: Using Middleware
For HTTP requests, you can create custom middleware extending RateLimitMiddleware:
<?php namespace App\Middleware; use FriendsOfHyperf\RateLimit\Middleware\RateLimitMiddleware; use FriendsOfHyperf\RateLimit\Algorithm; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\RequestHandlerInterface; class ApiRateLimitMiddleware extends RateLimitMiddleware { // Override default properties protected int $maxAttempts = 100; protected int $decay = 60; protected Algorithm $algorithm = Algorithm::SLIDING_WINDOW; protected string $responseMessage = 'API rate limit exceeded'; protected int $responseCode = 429; // Or customize key resolution protected function resolveKey(ServerRequestInterface $request): string { return 'api:' . $this->getClientIp(); } }
Then register the middleware in your config:
// config/autoload/middlewares.php return [ 'http' => [ App\Middleware\ApiRateLimitMiddleware::class, ], ];
Rate Limiting Algorithms
Fixed Window (默认)
Simplest algorithm, counts requests in fixed time windows.
#[RateLimit(algorithm: Algorithm::FIXED_WINDOW)]
Pros: Simple, memory efficient Cons: Can allow burst requests at window boundaries
Sliding Window
More accurate than fixed window, spreads requests evenly.
#[RateLimit(algorithm: Algorithm::SLIDING_WINDOW)]
Pros: Smooths out bursts, more accurate Cons: Slightly more complex
Token Bucket
Allows burst traffic while maintaining average rate.
#[RateLimit(algorithm: Algorithm::TOKEN_BUCKET)]
Pros: Allows burst traffic, flexible Cons: Requires more configuration
Leaky Bucket
Processes requests at constant rate, queues bursts.
#[RateLimit(algorithm: Algorithm::LEAKY_BUCKET)]
Pros: Smooth output rate, prevents bursts Cons: Can delay requests
Custom Rate Limiter
You can implement your own rate limiter by implementing RateLimiterInterface:
<?php namespace App\RateLimit; use FriendsOfHyperf\RateLimit\Contract\RateLimiterInterface; class CustomRateLimiter implements RateLimiterInterface { public function tooManyAttempts(string $key, int $maxAttempts, int $decay): bool { // Your implementation } public function availableIn(string $key): int { // Your implementation } public function remaining(string $key, int $maxAttempts): int { // Your implementation } }
Exception Handling
When rate limit is exceeded, a RateLimitException is thrown:
<?php try { $userController->index(); } catch (FriendsOfHyperf\RateLimit\Exception\RateLimitException $e) { // Rate limit exceeded $message = $e->getMessage(); // "Too Many Attempts. Please try again in X seconds." $code = $e->getCode(); // 429 }
Configuration
The component uses Hyperf's Redis configuration. You can specify which Redis pool to use in the annotation or middleware:
// Using specific Redis pool #[RateLimit(pool: 'rate_limit')]
Make sure to configure your Redis pool in config/autoload/redis.php:
return [ 'default' => [ 'host' => env('REDIS_HOST', 'localhost'), 'port' => env('REDIS_PORT', 6379), 'auth' => env('REDIS_AUTH', null), 'db' => 0, 'pool' => [ 'min_connections' => 1, 'max_connections' => 30, ], ], 'rate_limit' => [ 'host' => env('REDIS_HOST', 'localhost'), 'port' => env('REDIS_PORT', 6379), 'auth' => env('REDIS_AUTH', null), 'db' => 1, 'pool' => [ 'min_connections' => 5, 'max_connections' => 50, ], ], ];
Examples
Example 1: Login Rate Limiting
Limit login attempts to prevent brute force attacks:
#[RateLimit(
key: 'login:{email}',
maxAttempts: 5,
decay: 300, // 5 minutes
response: 'Too many login attempts. Please try again after 5 minutes.',
responseCode: 429
)]
public function login(string $email, string $password)
{
// Login logic here
}
Example 2: API Endpoint Rate Limit
Different rate limits for different API endpoints:
class ApiController { // Public API: 100 requests per minute #[RateLimit(maxAttempts: 100, decay: 60)] public function public() { // Public endpoint } // Premium API: 1000 requests per minute #[RateLimit(maxAttempts: 1000, decay: 60)] public function premium() { // Premium endpoint } }
Example 3: User-based Rate Limiting
Rate limit per user:
#[RateLimit(
key: ['user', '{userId}', 'action'],
maxAttempts: 10,
decay: 3600 // 1 hour
)]
public function performAction(int $userId)
{
// Action logic here
}
Example 4: IP-based Rate Limiting
Rate limit by IP address using middleware:
class IpRateLimitMiddleware extends RateLimitMiddleware { protected function resolveKey(ServerRequestInterface $request): string { return 'ip:' . $this->getClientIp(); } }
Example 5: Multiple Rate Limits with Smart Order
Use AutoSort to efficiently handle multiple rate limits on expensive operations:
class ReportController { /** * Expensive report generation with multiple protection levels * AutoSort ensures stricter limits are checked first for better performance */ #[AutoSort] #[RateLimit(maxAttempts: 5, decay: 60, response: 'Too many requests. Max 5 per minute')] // Emergency brake #[RateLimit(maxAttempts: 30, decay: 3600, response: 'Hourly limit exceeded. Max 30 per hour')] // Sustained load #[RateLimit(maxAttempts: 100, decay: 86400, response: 'Daily limit exceeded. Max 100 per day')] // Daily cap public function generateReport($reportType) { // Expensive report generation logic here } }