Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for add_middleware() #179

Closed
gellnerm opened this issue Jan 2, 2024 · 8 comments
Closed

Support for add_middleware() #179

gellnerm opened this issue Jan 2, 2024 · 8 comments

Comments

@gellnerm
Copy link

gellnerm commented Jan 2, 2024

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))
@pgjones
Copy link
Owner

pgjones commented Jan 3, 2024

@Kludex I think Starlette is doing the wrong thing here as per django/asgiref#343, what do you think?

@Kludex
Copy link

Kludex commented Jan 3, 2024

I did fix that on the last release: encode/starlette#2352. What Starlette version is being used here?

Also, kind a related, it would be cool if you can check django/asgiref#424. 🙏

EDIT: I just saw there's FastAPI on the traceback - FastAPI still doesn't support the last Starlette version.

@gellnerm
Copy link
Author

gellnerm commented Jan 3, 2024

What Starlette version is being used here?

0.27.0

@pgjones
Copy link
Owner

pgjones commented May 27, 2024

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

@pgjones pgjones closed this as completed May 27, 2024
@dragoangel
Copy link

dragoangel commented May 27, 2024

@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

@pgjones
Copy link
Owner

pgjones commented May 27, 2024

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

@dragoangel
Copy link

dragoangel commented May 27, 2024

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
Copy link

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants