sanic
sanic copied to clipboard
Sanic routing breaks when wrapped in ASGI middleware
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
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.
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.
Closing as this does not appear to be a current issue.