devoralive/traffic-limit-bundle
Symfony bundle for rate limiting requests via Redis using SncRedisBundle. Define multiple limit profiles (amount/ttl) and Redis clients in config, then access the generated services from the container to enforce per-key traffic limits.
Installation:
composer require devoralive/traffic-limit-bundle "dev-master"
This auto-installs snc/RedisBundle if missing.
Enable Bundles (app/AppKernel.php):
new Snc\RedisBundle\SncRedisBundle(),
new Devoralive\TrafficLimit\TrafficLimitBundle(),
Basic Configuration (config.yml):
snc_redis:
clients:
traffic_limit:
type: phpredis
dsn: redis://localhost:6379
traffic_limit:
default_limit:
enabled: true
snc_client: traffic_limit
amount: 100 # Max requests
ttl: 3600 # Time window (seconds)
First Usage (Controller):
use Devoralive\TrafficLimitBundle\Service\TrafficLimitInterface;
use Symfony\Component\HttpKernel\Exception\TooManyRequestsHttpException;
public function apiAction(Request $request, TrafficLimitInterface $limitService) {
try {
$limitService->processRequest($request->getClientIp());
// Proceed with logic...
} catch (TooManyRequestsHttpException $e) {
return new JsonResponse(['error' => 'Rate limit exceeded'], 429);
}
}
Dynamic Keys: Use any unique identifier (e.g., user_id, api_key, or ip).
$limitService->processRequest($userId); // User-specific limit
Middleware Integration (Symfony 4.3+):
// src/Kernel.php
protected function build(Request $request): void {
$this->addMiddleware(new TrafficLimitMiddleware(
$this->get('traffic_limit.default_limit'),
$request->getClientIp()
));
}
Service Container Injection:
# services.yaml
services:
App\Service\RateLimiter:
arguments:
$limitService: '@traffic_limit.default_limit'
Multiple Limits (e.g., low_limit for free tier, high_limit for premium):
traffic_limit:
low_limit:
amount: 100
ttl: 3600
high_limit:
amount: 1000
ttl: 86400
$this->get('traffic_limit.low_limit')->processRequest($ip);
Custom Exceptions:
try {
$limitService->processRequest($key);
} catch (TooManyRequestsHttpException $e) {
$e->setRetryAfter($limitService->getRetryAfter());
throw $e;
}
snc_redis client for other bundles.ttl per use case (e.g., 60 for minute-based limits).Redis Dependency:
redis-server). The bundle will fail silently if Redis is unreachable.if (!$limitService->isRedisAvailable()) {
throw new \RuntimeException('Redis unavailable');
}
Key Collisions:
ip as a key may block entire subnets. Use ip + user_agent for granularity:
$key = $request->getClientIp() . ':' . $request->headers->get('User-Agent');
TTL Misconfiguration:
ttl of 0 disables expiration. Always set a realistic value (e.g., 3600 for hourly limits).Exception Handling:
TooManyRequestsHttpException (HTTP 429). Customize responses:
return new JsonResponse(['error' => 'Rate limit exceeded'], 429, [
'Retry-After' => $limitService->getRetryAfter()
]);
redis-cli KEYS "traffic_limit:*" # List all rate-limit keys
redis-cli GET traffic_limit:ip:192.168.1.1 # Check counter
catch (TooManyRequestsHttpException $e) {
$this->logger->warning('Rate limit hit', ['key' => $key, 'retry_after' => $e->getRetryAfter()]);
}
Custom Storage:
Devoralive\TrafficLimitBundle\Service\TrafficLimit to support databases (e.g., MySQL):
class DatabaseTrafficLimit extends TrafficLimit {
public function increment($key) {
// Custom DB logic
}
}
Event-Based Limits:
kernel.request):
$eventDispatcher->addListener('kernel.request', function (GetResponseEvent $event) {
$limitService->processRequest($event->getRequest()->getClientIp());
});
Bulk Processing:
$limitService->resetKey($userId); // Reset counter
How can I help you explore Laravel packages today?