sanic icon indicating copy to clipboard operation
sanic copied to clipboard

Sanic routing breaks when wrapped in ASGI middleware

Open RobbeSneyders opened this issue 2 years ago • 2 comments

I have a simple Sanic app:

main.py

from sanic import Sanic
from sanic.response import json


from starlette.exceptions import ExceptionMiddleware


app = Sanic('test')


@app.route('/test')
async def test(request):
    value = request.args.get('int')

    if value == '1':
        value = int(value)

    return json({'int': value})


wrapped_app = ExceptionMiddleware(app)

When I run the Sanic app and send a request, it works:

> uvicorn main:app
...
> curl -X 'GET' 'http://127.0.0.1:8000/test?int=1' 
{"int":1}

However when I wrap the app in ASGI middleware, run the app, and send a request, I get the following error:

> uvicorn main:app
...
> curl -X 'GET' 'http://127.0.0.1:8000/test?int=1' 
INFO:     127.0.0.1:38994 - "GET /test?int=1 HTTP/1.1" 500 Internal Server Error
ERROR:    Exception in ASGI application
Traceback (most recent call last):
  File "/home/robbe/workspace/oss/connexion/venv/lib/python3.8/site-packages/anyio/streams/memory.py", line 81, in receive
    return self.receive_nowait()
  File "/home/robbe/workspace/oss/connexion/venv/lib/python3.8/site-packages/anyio/streams/memory.py", line 76, in receive_nowait
    raise WouldBlock
anyio.WouldBlock

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/home/robbe/workspace/oss/connexion/connexion/middleware/routing.py", line 104, in call_next
    message = await recv_stream.receive()
  File "/home/robbe/workspace/oss/connexion/venv/lib/python3.8/site-packages/anyio/streams/memory.py", line 101, in receive
    raise EndOfStream
anyio.EndOfStream

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/home/robbe/workspace/oss/connexion/venv/lib/python3.8/site-packages/uvicorn/protocols/http/httptools_impl.py", line 372, in run_asgi
    result = await app(self.scope, self.receive, self.send)
  File "/home/robbe/workspace/oss/connexion/venv/lib/python3.8/site-packages/uvicorn/middleware/proxy_headers.py", line 75, in __call__
    return await self.app(scope, receive, send)
  File "/home/robbe/workspace/oss/connexion/connexion/middleware/main.py", line 109, in __call__
    await self.app(scope, receive, send)
  File "/home/robbe/workspace/oss/connexion/venv/lib/python3.8/site-packages/starlette/exceptions.py", line 87, in __call__
    raise exc
  File "/home/robbe/workspace/oss/connexion/venv/lib/python3.8/site-packages/starlette/exceptions.py", line 76, in __call__
    await self.app(scope, receive, sender)
  File "/home/robbe/workspace/oss/connexion/connexion/middleware/swagger_ui.py", line 38, in __call__
    await self.router(scope, receive, send)
  File "/home/robbe/workspace/oss/connexion/venv/lib/python3.8/site-packages/starlette/routing.py", line 659, in __call__
    await route.handle(scope, receive, send)
  File "/home/robbe/workspace/oss/connexion/venv/lib/python3.8/site-packages/starlette/routing.py", line 411, in handle
    await self.app(scope, receive, send)
  File "/home/robbe/workspace/oss/connexion/venv/lib/python3.8/site-packages/starlette/routing.py", line 688, in __call__
    await self.default(scope, receive, send)
  File "/home/robbe/workspace/oss/connexion/connexion/middleware/routing.py", line 80, in __call__
    await self.router(scope, receive, send)
  File "/home/robbe/workspace/oss/connexion/venv/lib/python3.8/site-packages/starlette/routing.py", line 659, in __call__
    await route.handle(scope, receive, send)
  File "/home/robbe/workspace/oss/connexion/venv/lib/python3.8/site-packages/starlette/routing.py", line 411, in handle
    await self.app(scope, receive, send)
  File "/home/robbe/workspace/oss/connexion/venv/lib/python3.8/site-packages/starlette/routing.py", line 659, in __call__
    await route.handle(scope, receive, send)
  File "/home/robbe/workspace/oss/connexion/venv/lib/python3.8/site-packages/starlette/routing.py", line 259, in handle
    await self.app(scope, receive, send)
  File "/home/robbe/workspace/oss/connexion/venv/lib/python3.8/site-packages/starlette/routing.py", line 61, in app
    response = await func(request)
  File "/home/robbe/workspace/oss/connexion/connexion/middleware/routing.py", line 24, in call_next_callback
    return await _call_next_fn.get()(operation)
  File "/home/robbe/workspace/oss/connexion/connexion/middleware/routing.py", line 135, in attach_operation_and_call_next
    return await call_next(request, task_group)
  File "/home/robbe/workspace/oss/connexion/connexion/middleware/routing.py", line 107, in call_next
    raise app_exc
  File "/home/robbe/workspace/oss/connexion/connexion/middleware/routing.py", line 97, in coro
    await self.app(scope, request.receive, send_stream.send)
  File "/home/robbe/workspace/oss/connexion/connexion/middleware/base.py", line 71, in __call__
    response = await self.dispatch_func(request, operation, call_next)
  File "/home/robbe/workspace/oss/connexion/connexion/middleware/test.py", line 13, in dispatch
    response = await call_next(request)
  File "/home/robbe/workspace/oss/connexion/connexion/middleware/base.py", line 48, in call_next
    raise app_exc
  File "/home/robbe/workspace/oss/connexion/connexion/middleware/base.py", line 38, in coro
    await self.app(scope, request.receive, send_stream.send)
  File "/home/robbe/workspace/oss/connexion/venv/lib/python3.8/site-packages/sanic/app.py", line 1735, in __call__
    await asgi_app()
  File "/home/robbe/workspace/oss/connexion/venv/lib/python3.8/site-packages/sanic/asgi.py", line 223, in __call__
    await self.sanic_app.handle_exception(self.request, e)
  File "/home/robbe/workspace/oss/connexion/venv/lib/python3.8/site-packages/sanic/app.py", line 736, in handle_exception
    await self.dispatch(
  File "/home/robbe/workspace/oss/connexion/venv/lib/python3.8/site-packages/sanic/signals.py", line 191, in dispatch
    return await dispatch
  File "/home/robbe/workspace/oss/connexion/venv/lib/python3.8/site-packages/sanic/signals.py", line 130, in _dispatch
    group, handlers, params = self.get(event, condition=condition)
  File "/home/robbe/workspace/oss/connexion/venv/lib/python3.8/site-packages/sanic/signals.py", line 92, in get
    group, param_basket = self.find_route(
TypeError: 'NoneType' object is not callable

Context This issue was introduced in Sanic==21.3.0. Combined with the stack trace, this makes me suspect that this is an issue in the new router.

I've tried to apply the same middleware to a Starlette, Quart, and Flask (with WsgiToAsgi) app, and they all work fine, which makes me suspect that this is a Sanic issue, not a Starlette issue.

Sanic is the only framework that returns a transfer-encoding: chunked header, and does not do this on versions before 21.3.0, which makes me suspect that this is related to the issue. Probably due to the MemoryObjectReceiveStream that the Starlette middleware passes as receive argument. The find_route definition in sanic_routing is unfortunately a bit too obscure for me to dive any deeper.

Environment:

  • sanic==v21.12.1
  • starlette==0.18.0

RobbeSneyders avatar Mar 01 '22 21:03 RobbeSneyders

Hi @RobbeSneyders Looks like you're actually seeing two different errors here:

First, the MemoryObjectReceiveStream issue that you've described. As far as I'm aware, Sanic-in-ASGI-mode was not tested with Starlette middleware, so support for that will need to be added in a future version.

Secondly, when handling that error, it looks like there is a bug in the app.handle_error() method, when operating in ASGI mode. The bug is hit in the signal router, because it looks like "Touchup" was never called on the app. I don't remember how that is supposed to be handled in AGSI-mode, I'll look into it.

ashleysommer avatar Mar 02 '22 00:03 ashleysommer

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. If this is incorrect, please respond with an update. Thank you for your contributions.

stale[bot] avatar Jun 12 '22 21:06 stale[bot]

Closing as this does not appear to be a current issue.

ahopkins avatar Aug 28 '22 07:08 ahopkins