hypercorn icon indicating copy to clipboard operation
hypercorn copied to clipboard

Support for add_middleware()

Open gellnerm opened this issue 1 year ago • 3 comments

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

gellnerm avatar Jan 02 '24 11:01 gellnerm

@kludex I think Starlette is doing the wrong thing here as per https://github.com/django/asgiref/issues/343, what do you think?

pgjones avatar Jan 03 '24 09:01 pgjones

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.

Kludex avatar Jan 03 '24 10:01 Kludex

What Starlette version is being used here?

0.27.0

gellnerm avatar Jan 03 '24 12:01 gellnerm

Should be fixed now given enough time has passed for release updates.

pgjones avatar May 27 '24 16:05 pgjones

@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

dragoangel avatar May 27 '24 19:05 dragoangel

This is likely a bug with the ProxyFixMiddleware that is fixed in 0.17.0, just released.

pgjones avatar May 27 '24 20:05 pgjones

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

dragoangel avatar May 27 '24 20:05 dragoangel

Can you please re-open issue as it not yet solved?

dragoangel avatar May 27 '24 20:05 dragoangel