hypercorn
hypercorn copied to clipboard
Support for add_middleware()
Thank you for making hypercorn.
As per the fastapi documentation the recommended way to add a middleware is to use
app.add_middleware(Middleware)
However, this does not work, at least for the ProxyFixMiddleware. When used this way, this is the exception stack when making a request:
[2024-01-02 12:42:02 +0100] [36653] [ERROR] Error in ASGI Framework Traceback (most recent call last): File "lib/python3.11/site-packages/hypercorn/asyncio/task_group.py", line 27, in _handle await app(scope, receive, send, sync_spawn, call_soon) File "lib/python3.11/site-packages/hypercorn/app_wrappers.py", line 34, in call await self.app(scope, receive, send) File "lib/python3.11/site-packages/fastapi/applications.py", line 1106, in call await super().call(scope, receive, send) File "lib/python3.11/site-packages/starlette/applications.py", line 122, in call await self.middleware_stack(scope, receive, send) File "lib/python3.11/site-packages/starlette/middleware/errors.py", line 184, in call raise exc File "lib/python3.11/site-packages/starlette/middleware/errors.py", line 162, in call await self.app(scope, receive, _send) File "lib/python3.11/site-packages/hypercorn/middleware/proxy_fix.py", line 22, in call scope = deepcopy(scope) ^^^^^^^^^^^^^^^ File "lib/python3.11/copy.py", line 146, in deepcopy y = copier(x, memo) ^^^^^^^^^^^^^^^ File "lib/python3.11/copy.py", line 231, in _deepcopy_dict y[deepcopy(key, memo)] = deepcopy(value, memo) ^^^^^^^^^^^^^^^^^^^^^ File "lib/python3.11/copy.py", line 172, in deepcopy y = _reconstruct(x, memo, *rv) ^^^^^^^^^^^^^^^^^^^^^^^^^^ File "lib/python3.11/copy.py", line 271, in _reconstruct state = deepcopy(state, memo) ^^^^^^^^^^^^^^^^^^^^^ File "lib/python3.11/copy.py", line 146, in deepcopy y = copier(x, memo) ^^^^^^^^^^^^^^^ File "lib/python3.11/copy.py", line 231, in _deepcopy_dict y[deepcopy(key, memo)] = deepcopy(value, memo) ^^^^^^^^^^^^^^^^^^^^^ File "lib/python3.11/copy.py", line 172, in deepcopy y = _reconstruct(x, memo, *rv) ^^^^^^^^^^^^^^^^^^^^^^^^^^ File "lib/python3.11/copy.py", line 272, in _reconstruct if hasattr(y, 'setstate'): ^^^^^^^^^^^^^^^^^^^^^^^^^^ File "lib/python3.11/site-packages/starlette/datastructures.py", line 702, in getattr return self._state[key] ^^^^^^^^^^^ File "lib/python3.11/site-packages/starlette/datastructures.py", line 702, in getattr return self._state[key] ^^^^^^^^^^^ File "lib/python3.11/site-packages/starlette/datastructures.py", line 702, in getattr return self._state[key] ^^^^^^^^^^^ [Previous line repeated 959 more times] File "lib/python3.11/site-packages/starlette/datastructures.py", line 700, in getattr def getattr(self, key: typing.Any) -> typing.Any:
File "lib/python3.11/site-packages/starlette/datastructures.py", line 700, in getattr def getattr(self, key: typing.Any) -> typing.Any:
RecursionError: maximum recursion depth exceeded
Minimal example:
import asyncio
from fastapi import FastAPI, Request
from hypercorn.asyncio import serve
from hypercorn.config import Config
from hypercorn.middleware import ProxyFixMiddleware
app = FastAPI()
app.add_middleware(ProxyFixMiddleware, mode='modern', trusted_hops=1)
@app.get('/test1/{test}')
def test1(request: Request, test: str): # @UnusedVariable
return {'dd': 1}
if __name__ == '__main__':
config = Config()
config.bind = ['127.0.0.1:8000', '[::1]:8000']
asyncio.run(serve(app, config))
@kludex I think Starlette is doing the wrong thing here as per https://github.com/django/asgiref/issues/343, what do you think?
I did fix that on the last release: https://github.com/encode/starlette/pull/2352. What Starlette version is being used here?
Also, kind a related, it would be cool if you can check https://github.com/django/asgiref/issues/424. 🙏
EDIT: I just saw there's FastAPI on the traceback - FastAPI still doesn't support the last Starlette version.
What Starlette version is being used here?
0.27.0
Should be fixed now given enough time has passed for release updates.
@pgjones well for me this still the case with python:3.10-slim and next requirements.txt:
hypercorn~=0.16
asgi-correlation-id~=4.3
starlette~=0.37.0
jinja2~=3.1
like:
from asgi_correlation_id import CorrelationIdMiddleware, correlation_id
from hypercorn.middleware import ProxyFixMiddleware
from starlette.applications import Starlette
from starlette.middleware import Middleware
from starlette.middleware.gzip import GZipMiddleware
proxy_hops = int(os.getenv('PROXY_HOPS', '0'))
proxy_mode = os.getenv('PROXY_MODE', 'legacy')
middleware = [
Middleware(CorrelationIdMiddleware, header_name='X-Request-ID'),
Middleware(GZipMiddleware, minimum_size=1000)
]
if proxy_hops > 0:
middleware.append(Middleware(ProxyFixMiddleware, mode=proxy_mode, trusted_hops=proxy_hops))
app = Starlette(middleware=middleware)
app.mount('/static', StaticFiles(directory = 'statics'), name = 'static')
templates = Jinja2Templates(directory = 'templates')
...
@app.exception_handler(500)
async def server_error(request, exc):
"""
Return an HTTP 500 page.
"""
template = "500.html"
context = {
'request': request,
'title': 'Server Error',
'company': company
}
headers = {
'X-Request-ID': str(correlation_id.get() or '')
}
return templates.TemplateResponse(template, context, status_code = 500, headers=headers)
Other middleware's works just fine, and if I would set PROXY_HOPS=0 issue disappears as I skip adding ProxyFixMiddleware.
- zero proxies mean we don't need to load
ProxyFixMiddleware
This is likely a bug with the ProxyFixMiddleware that is fixed in 0.17.0, just released.
Just rebuild with hypercorn~=0.17 and still issue is same:
[2024-05-27 20:06:27 +0000] [13] [ERROR] Error in ASGI Framework
Traceback (most recent call last):
File "/usr/local/lib/python3.10/site-packages/hypercorn/asyncio/task_group.py", line 27, in _handle
await app(scope, receive, send, sync_spawn, call_soon)
File "/usr/local/lib/python3.10/site-packages/hypercorn/app_wrappers.py", line 34, in __call__
await self.app(scope, receive, send)
File "/usr/local/lib/python3.10/site-packages/starlette/applications.py", line 123, in __call__
await self.middleware_stack(scope, receive, send)
File "/usr/local/lib/python3.10/site-packages/starlette/middleware/errors.py", line 186, in __call__
raise exc
File "/usr/local/lib/python3.10/site-packages/starlette/middleware/errors.py", line 164, in __call__
await self.app(scope, receive, _send)
File "/usr/local/lib/python3.10/site-packages/starlette_exporter/middleware.py", line 285, in __call__
await self.app(scope, receive, send)
File "/usr/local/lib/python3.10/site-packages/asgi_correlation_id/middleware.py", line 90, in __call__
await self.app(scope, receive, handle_outgoing_request)
File "/usr/local/lib/python3.10/site-packages/starlette/middleware/gzip.py", line 26, in __call__
await self.app(scope, receive, send)
File "/usr/local/lib/python3.10/site-packages/hypercorn/middleware/proxy_fix.py", line 23, in __call__
scope = deepcopy(scope)
File "/usr/local/lib/python3.10/copy.py", line 146, in deepcopy
y = copier(x, memo)
File "/usr/local/lib/python3.10/copy.py", line 231, in _deepcopy_dict
y[deepcopy(key, memo)] = deepcopy(value, memo)
File "/usr/local/lib/python3.10/copy.py", line 172, in deepcopy
y = _reconstruct(x, memo, *rv)
File "/usr/local/lib/python3.10/copy.py", line 271, in _reconstruct
state = deepcopy(state, memo)
File "/usr/local/lib/python3.10/copy.py", line 146, in deepcopy
y = copier(x, memo)
File "/usr/local/lib/python3.10/copy.py", line 231, in _deepcopy_dict
y[deepcopy(key, memo)] = deepcopy(value, memo)
File "/usr/local/lib/python3.10/copy.py", line 172, in deepcopy
y = _reconstruct(x, memo, *rv)
File "/usr/local/lib/python3.10/copy.py", line 272, in _reconstruct
if hasattr(y, '__setstate__'):
File "/usr/local/lib/python3.10/site-packages/starlette/datastructures.py", line 699, in __getattr__
return self._state[key]
File "/usr/local/lib/python3.10/site-packages/starlette/datastructures.py", line 699, in __getattr__
return self._state[key]
File "/usr/local/lib/python3.10/site-packages/starlette/datastructures.py", line 699, in __getattr__
return self._state[key]
[Previous line repeated 966 more times]
RecursionError: maximum recursion depth exceeded
Can you please re-open issue as it not yet solved?