diff --git a/src/Cache/RateLimit/Middlewares/RateLimiter.php b/src/Cache/RateLimit/Middlewares/RateLimiter.php index acbf5e16..5a34bc73 100644 --- a/src/Cache/RateLimit/Middlewares/RateLimiter.php +++ b/src/Cache/RateLimit/Middlewares/RateLimiter.php @@ -55,10 +55,14 @@ public function handleRequest(Request $request, RequestHandler $next): Response $remaining = max(0, $perMinuteLimit - $current); $resetTime = time() + $this->rateLimiter->getTtl($clientIp); - $response->addHeader('x-ratelimit-limit', (string) $perMinuteLimit); - $response->addHeader('x-ratelimit-remaining', (string) $remaining); - $response->addHeader('x-ratelimit-reset', (string) $resetTime); - $response->addHeader('x-ratelimit-reset-after', (string) $this->rateLimiter->getTtl($clientIp)); + if ($isCustom || ! $response->hasHeader('x-ratelimit-limit')) { + $response->replaceHeaders([ + 'x-ratelimit-limit' => (string) $perMinuteLimit, + 'x-ratelimit-remaining' => (string) $remaining, + 'x-ratelimit-reset' => (string) $resetTime, + 'x-ratelimit-reset-after' => (string) $this->rateLimiter->getTtl($clientIp), + ]); + } return $response; } diff --git a/tests/Feature/Cache/CustomRateLimiterTest.php b/tests/Feature/Cache/CustomRateLimiterTest.php index 73fbabcb..558d08c1 100644 --- a/tests/Feature/Cache/CustomRateLimiterTest.php +++ b/tests/Feature/Cache/CustomRateLimiterTest.php @@ -75,3 +75,32 @@ $this->get(path: '/custom') ->assertStatusCode(HttpStatus::TOO_MANY_REQUESTS); }); + +it('does not duplicate rate limit headers when global and custom limiters both run', function (): void { + Config::set('app.port', 14337); + Config::set('cache.rate_limit.enabled', true); + Config::set('cache.rate_limit.per_minute', 60); + + Route::get('/custom-with-global', fn (): Response => response()->plain('Ok')) + ->middleware(RateLimiter::perMinute(10)); + + $this->app->run(); + + $responseHeaders = $this->get(path: '/custom-with-global') + ->assertOk() + ->getHeaders(); + + $headers = [ + 'x-ratelimit-limit', + 'x-ratelimit-remaining', + 'x-ratelimit-reset', + 'x-ratelimit-reset-after', + ]; + + foreach ($headers as $header) { + expect($responseHeaders[$header] ?? [])->toHaveCount(1); + } + + expect($responseHeaders['x-ratelimit-limit'])->toBe(['10']); + expect($responseHeaders['x-ratelimit-remaining'])->toBe(['9']); +});