limiter = $limiter; } public function handle($request, Closure $next, $maxAttempts = 80, $decayMinutes = 1) { // Get the real client IP, considering proxy headers $clientIp = $request->ip(); $key = $this->resolveRequestSignature($request, $clientIp); $currentAttempts = $this->limiter->attempts($key); $tooMany = $this->limiter->tooManyAttempts($key, $maxAttempts); // \Log::info('API Throttle Check', [ // 'client_ip' => $clientIp, // 'x_forwarded_for' => $request->header('X-Forwarded-For'), // 'path' => $request->path(), // 'key' => $key, // 'current_attempts' => $currentAttempts, // 'max_attempts' => $maxAttempts, // 'too_many' => $tooMany ? 'YES' : 'NO', // 'decay_minutes' => $decayMinutes // ]); if ($tooMany) { \Log::warning('API Throttle Limit Exceeded', [ 'client_ip' => $clientIp, 'key' => $key, 'current_attempts' => $currentAttempts, 'max_attempts' => $maxAttempts ]); return $this->buildException($key, $maxAttempts); } $this->limiter->hit($key, $decayMinutes * 60); $response = $next($request); return $this->addHeaders( $response, $maxAttempts, $this->calculateRemainingAttempts($key, $maxAttempts) ); } protected function resolveRequestSignature($request, $clientIp) { return sha1($clientIp . '|' . $request->path()); } protected function calculateRemainingAttempts($key, $maxAttempts) { return $this->limiter->retriesLeft($key, $maxAttempts); } protected function buildException($key, $maxAttempts) { $retryAfter = $this->limiter->availableIn($key); return response()->json([ 'message' => 'Too Many Attempts.', 'retry_after' => $retryAfter ], Response::HTTP_TOO_MANY_REQUESTS, [ 'Retry-After' => $retryAfter, 'X-RateLimit-Limit' => $maxAttempts, 'X-RateLimit-Remaining' => 0, ]); } protected function addHeaders(Response $response, $maxAttempts, $remainingAttempts) { $response->headers->add([ 'X-RateLimit-Limit' => $maxAttempts, 'X-RateLimit-Remaining' => $remainingAttempts, ]); return $response; } }