question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Global limit and routes setting a limit cause double response headers

See original GitHub issue

A 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:open
  • Created 3 years ago
  • Comments:8 (2 by maintainers)

github_iconTop GitHub Comments

1reaction
laurentScommented, Feb 11, 2021

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.

0reactions
papapumpnzcommented, Mar 16, 2021

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).

Read more comments on GitHub >

github_iconTop 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 >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found