redis = new RedisClient([ 'scheme' => 'tcp', 'host' => $config['host'], 'port' => $config['port'], 'password' => $config['password'], 'timeout' => 0.5, // 500ms connection timeout to fail fast ]); $this->redis->connect(); } catch (Throwable $e) { // Degrade gracefully if Redis server is down $this->redis = null; } } } /** * Handle rate limiting logic. */ public function handle(Request $request, Response $response, callable $next): void { if ($this->redis === null) { // Redis unavailable, skip throttle check to avoid service outage $next(); return; } $ip = $request->getIp(); $path = $request->getPath(); $key = "rate_limit:" . md5($ip . ":" . $path); try { $current = $this->redis->get($key); if ($current !== null && (int)$current >= $this->limit) { $ttl = $this->redis->ttl($key); $response->header('Retry-After', (string)max(1, $ttl)); throw new Exception("Too Many Requests. Rate limit exceeded.", 429); } if ($current === null) { // First request in the time frame window $this->redis->setex($key, $this->window, 1); $current = 0; } else { $this->redis->incr($key); } // Set rate limit headers $remaining = $this->limit - ((int)$current + 1); $response->header('X-RateLimit-Limit', (string)$this->limit); $response->header('X-RateLimit-Remaining', (string)max(0, $remaining)); } catch (Throwable $e) { if ($e->getCode() === 429) { throw $e; } // Logging or catching connection dropping mid-request } $next(); } }