Global limit and routes setting a limit cause double response headers
See original GitHub issueA default limit set as :
limiter = Limiter(key_func=default_identifier,
default_limits="10/minute",
headers_enabled=True,
storage_uri=settings.RateLimit.redis_uri,
in_memory_fallback_enabled=True,
swallow_errors=True)
app.state.limiter = limiter
app.add_middleware(SlowAPIMiddleware)
app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)
With routes that use the global limit, eg do not use the @limiter.limit decorator they respond with headers as expected with:
x-ratelimit-limit: 10
x-ratelimit-remaining: 9
x-ratelimit-reset: 1610412027
But routes that use the @limitier.limit decorator now get double headers as such:
x-ratelimit-limit: 1000,1000
x-ratelimit-remaining: 950,950
x-ratelimit-reset: 1612495738,1612495738
and an error is thrown:
2021-01-12 14:09:37,215 [17636] ERROR: Failed to update rate limit headers. Swallowing error
Traceback (most recent call last):
File "D:\programming\financefeast_api\venv\lib\site-packages\slowapi\extension.py", line 381, in _inject_headers
retry_after = parsedate_to_datetime(existing_retry_after_header)
File "C:\Python38\lib\email\utils.py", line 198, in parsedate_to_datetime
*dtuple, tz = _parsedate_tz(data)
TypeError: cannot unpack non-iterable NoneType object
If the parameter ‘swallow_errors’ is set to False, SlowAPI throws an stack-trace with:
2021-01-12 13:44:28,823 [16576] ERROR: Exception in ASGI application
Traceback (most recent call last):
File "D:\programming\financefeast_api\venv\lib\site-packages\uvicorn\protocols\http\h11_impl.py", line 394, in run_asgi
result = await app(self.scope, self.receive, self.send)
File "D:\programming\financefeast_api\venv\lib\site-packages\uvicorn\middleware\proxy_headers.py", line 45, in __call__
return await self.app(scope, receive, send)
File "D:\programming\financefeast_api\venv\lib\site-packages\uvicorn\middleware\debug.py", line 81, in __call__
raise exc from None
File "D:\programming\financefeast_api\venv\lib\site-packages\uvicorn\middleware\debug.py", line 78, in __call__
await self.app(scope, receive, inner_send)
File "D:\programming\financefeast_api\venv\lib\site-packages\fastapi\applications.py", line 199, in __call__
await super().__call__(scope, receive, send)
File "D:\programming\financefeast_api\venv\lib\site-packages\starlette\applications.py", line 111, in __call__
await self.middleware_stack(scope, receive, send)
File "D:\programming\financefeast_api\venv\lib\site-packages\starlette\middleware\errors.py", line 181, in __call__
raise exc from None
File "D:\programming\financefeast_api\venv\lib\site-packages\starlette\middleware\errors.py", line 159, in __call__
await self.app(scope, receive, _send)
File "D:\programming\financefeast_api\venv\lib\site-packages\starlette\middleware\base.py", line 25, in __call__
response = await self.dispatch_func(request, self.call_next)
File ".\app\middleware\request_context.py", line 42, in dispatch
raise e
File ".\app\middleware\request_context.py", line 38, in dispatch
response = await call_next(request)
File "D:\programming\financefeast_api\venv\lib\site-packages\starlette\middleware\base.py", line 45, in call_next
task.result()
File "D:\programming\financefeast_api\venv\lib\site-packages\starlette\middleware\base.py", line 38, in coro
await self.app(scope, receive, send)
File "D:\programming\financefeast_api\venv\lib\site-packages\starlette\middleware\base.py", line 25, in __call__
response = await self.dispatch_func(request, self.call_next)
File "D:\programming\financefeast_api\venv\lib\site-packages\slowapi\middleware.py", line 54, in dispatch
response = limiter._inject_headers(response, request.state.view_rate_limit)
File "D:\programming\financefeast_api\venv\lib\site-packages\slowapi\extension.py", line 381, in _inject_headers
retry_after = parsedate_to_datetime(existing_retry_after_header)
File "C:\Python38\lib\email\utils.py", line 198, in parsedate_to_datetime
*dtuple, tz = _parsedate_tz(data)
TypeError: cannot unpack non-iterable NoneType object
It seems since there is an existing header on the response, a string to datetime conversion is attempted on the response header '“Retry-After”, but being less that 5 characters parsedate_tz returns None and we get the error above.
Question: why are routes that define a limit return double response headers?
Issue Analytics
- State:
- Created 3 years ago
- Comments:8 (2 by maintainers)
Top Results From Across the Web
HTTP header manipulation - Envoy Proxy
Custom request/response headers can be added to a request/response at the weighted cluster, route, virtual host, and/or global route configuration level. See ...
Read more >Amazon API Gateway quotas and important notes
* API Gateway doesn't enforce a quota on concurrent connections. The maximum number of concurrent connections is determined by the rate of new...
Read more >Flask-Limiter 1.5+2.g5eddb74.dirty documentation
The medium route inherits the default limits and adds on a decorated limit of 1 request per second. The ping route will be...
Read more >Rewrite HTTP headers and URL with Application Gateway
These conditions are based on the request properties (request header and server variables). Choose to route the request (select the backend pool) ...
Read more >Route configuration | OpenShift Container Platform 4.9
Creating an HTTP-based route; Configuring route timeouts ... proxy to emit the haproxy hard-stop-after global option, which defines the maximum time allowed ...
Read more >Top Related Medium Post
No results found
Top Related StackOverflow Question
No results found
Troubleshoot Live Code
Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start FreeTop Related Reddit Thread
No results found
Top Related Hackernoon Post
No results found
Top Related Tweet
No results found
Top Related Dev.to Post
No results found
Top Related Hashnode Post
No results found
Top GitHub Comments
I’ve been able to reproduce the issue in a test, but haven’t had time to put together a complete fix. I’m not far off, I’ll try to finish it over the weekend.
Ok @laurentS forked and have a new test suite called test_double_headers that produces double headers, or single headers. Difference is the limiter decorator uses either a callable (produces double headers) or a string (produces single headers).