ASGI Flow Error on websockets unidirectional pong while awaiting response
Antonyesk601 opened this issue · comments
Python Version: 3.10 and 3.11
granian version: 1.4.1
granian interface: asgi
FastAPI: 0.111.0
If an empty pong is sent from client as a keepalive heartbeat granian prints out a warning
[WARNING] Unsupported websocket message received Pong([])
If the pong is sent during awaiting input, granian raises ASGI flow error even when client is connected and that error can (and should) be ignored. The same code works without any modification using uvicorn
server.py
from fastapi import WebSocket, FastAPI
app = FastAPI()
@app.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket):
await websocket.accept()
data = await websocket.receive_bytes()
await websocket.send_bytes(b"Message text was:"+data)
client.py
import websockets
import asyncio
async def main():
uri = "ws://localhost:8000/ws"
async with websockets.connect(uri) as websocket:
await websocket.pong(b'')
await asyncio.sleep(1)
await websocket.send(b'')
await asyncio.sleep(1)
asyncio.get_event_loop().run_until_complete(main())
Uvicorn trace when running with uvicorn server:app --host 0.0.0.0 --log-level debug
INFO: Started server process [118726]
INFO: Waiting for application startup.
INFO: Application startup complete.
INFO: Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)
DEBUG: = connection is CONNECTING
DEBUG: < GET /ws HTTP/1.1
DEBUG: < host: localhost:8000
DEBUG: < upgrade: websocket
DEBUG: < connection: Upgrade
DEBUG: < sec-websocket-key: Mj9N4bzT1G8E9fJ2o5w79w==
DEBUG: < sec-websocket-version: 13
DEBUG: < sec-websocket-extensions: permessage-deflate; client_max_window_bits
DEBUG: < user-agent: Python/3.10 websockets/12.0
INFO: ('127.0.0.1', 49668) - "WebSocket /ws" [accepted]
DEBUG: > HTTP/1.1 101 Switching Protocols
DEBUG: > Upgrade: websocket
DEBUG: > Connection: Upgrade
DEBUG: > Sec-WebSocket-Accept: BOjhd7UWFOiyW5vplYQicSPzVTw=
DEBUG: > Sec-WebSocket-Extensions: permessage-deflate
DEBUG: > date: Thu, 06 Jun 2024 16:55:24 GMT
DEBUG: > server: uvicorn
INFO: connection open
DEBUG: = connection is OPEN
DEBUG: < PONG '' [0 bytes]
DEBUG: < BINARY [0 bytes]
DEBUG: > BINARY 4d 65 73 73 61 67 65 20 74 65 78 74 20 77 61 73 3a [17 bytes]
DEBUG: < CLOSE 1000 (OK) [2 bytes]
DEBUG: = connection is CLOSING
DEBUG: > CLOSE 1000 (OK) [2 bytes]
DEBUG: x half-closing TCP connection
DEBUG: = connection is CLOSED
INFO: connection closed
granian trace when running with granian --interface asgi server:app
[INFO] Starting granian (main PID: 118874)
[INFO] Listening at: http://127.0.0.1:8000
[INFO] Spawning worker-1 with pid: 118875
[INFO] Started worker-1
[INFO] Started worker-1 runtime-1
[WARNING] Unsupported websocket message received Pong([])
[ERROR] Application callable raised an exception
Traceback (most recent call last):
File "/opt/conda/lib/python3.10/site-packages/granian/_futures.py", line 4, in future_watcher
await inner(watcher.scope, watcher.proto)
File "/opt/conda/lib/python3.10/site-packages/fastapi/applications.py", line 1054, in __call__
await super().__call__(scope, receive, send)
File "/opt/conda/lib/python3.10/site-packages/starlette/applications.py", line 123, in __call__
await self.middleware_stack(scope, receive, send)
File "/opt/conda/lib/python3.10/site-packages/starlette/middleware/errors.py", line 151, in __call__
await self.app(scope, receive, send)
File "/opt/conda/lib/python3.10/site-packages/starlette/middleware/exceptions.py", line 65, in __call__
await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
File "/opt/conda/lib/python3.10/site-packages/starlette/_exception_handler.py", line 64, in wrapped_app
raise exc
File "/opt/conda/lib/python3.10/site-packages/starlette/_exception_handler.py", line 53, in wrapped_app
await app(scope, receive, sender)
File "/opt/conda/lib/python3.10/site-packages/starlette/routing.py", line 756, in __call__
await self.middleware_stack(scope, receive, send)
File "/opt/conda/lib/python3.10/site-packages/starlette/routing.py", line 776, in app
await route.handle(scope, receive, send)
File "/opt/conda/lib/python3.10/site-packages/starlette/routing.py", line 373, in handle
await self.app(scope, receive, send)
File "/opt/conda/lib/python3.10/site-packages/starlette/routing.py", line 96, in app
await wrap_app_handling_exceptions(app, session)(scope, receive, send)
File "/opt/conda/lib/python3.10/site-packages/starlette/_exception_handler.py", line 64, in wrapped_app
raise exc
File "/opt/conda/lib/python3.10/site-packages/starlette/_exception_handler.py", line 53, in wrapped_app
await app(scope, receive, sender)
File "/opt/conda/lib/python3.10/site-packages/starlette/routing.py", line 94, in app
await func(session)
File "/opt/conda/lib/python3.10/site-packages/fastapi/routing.py", line 348, in app
await dependant.call(**values)
File "/home/antony/granianError/server.py", line 7, in websocket_endpoint
data = await websocket.receive_bytes()
File "/opt/conda/lib/python3.10/site-packages/starlette/websockets.py", line 146, in receive_bytes
message = await self.receive()
File "/opt/conda/lib/python3.10/site-packages/starlette/websockets.py", line 49, in receive
message = await self._receive()
RuntimeError: ASGI flow error
[WARNING] Unsupported websocket message received Pong([])
[ERROR] Application callable raised an exception
Traceback (most recent call last):
File "/opt/conda/lib/python3.10/site-packages/granian/_futures.py", line 4, in future_watcher
await inner(watcher.scope, watcher.proto)
File "/opt/conda/lib/python3.10/site-packages/fastapi/applications.py", line 1054, in __call__
await super().__call__(scope, receive, send)
File "/opt/conda/lib/python3.10/site-packages/starlette/applications.py", line 123, in __call__
await self.middleware_stack(scope, receive, send)
File "/opt/conda/lib/python3.10/site-packages/starlette/middleware/errors.py", line 151, in __call__
await self.app(scope, receive, send)
File "/opt/conda/lib/python3.10/site-packages/starlette/middleware/exceptions.py", line 65, in __call__
await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
File "/opt/conda/lib/python3.10/site-packages/starlette/_exception_handler.py", line 64, in wrapped_app
raise exc
File "/opt/conda/lib/python3.10/site-packages/starlette/_exception_handler.py", line 53, in wrapped_app
await app(scope, receive, sender)
File "/opt/conda/lib/python3.10/site-packages/starlette/routing.py", line 756, in __call__
await self.middleware_stack(scope, receive, send)
File "/opt/conda/lib/python3.10/site-packages/starlette/routing.py", line 776, in app
await route.handle(scope, receive, send)
File "/opt/conda/lib/python3.10/site-packages/starlette/routing.py", line 373, in handle
await self.app(scope, receive, send)
File "/opt/conda/lib/python3.10/site-packages/starlette/routing.py", line 96, in app
await wrap_app_handling_exceptions(app, session)(scope, receive, send)
File "/opt/conda/lib/python3.10/site-packages/starlette/_exception_handler.py", line 64, in wrapped_app
raise exc
File "/opt/conda/lib/python3.10/site-packages/starlette/_exception_handler.py", line 53, in wrapped_app
await app(scope, receive, sender)
File "/opt/conda/lib/python3.10/site-packages/starlette/routing.py", line 94, in app
await func(session)
File "/opt/conda/lib/python3.10/site-packages/fastapi/routing.py", line 348, in app
await dependant.call(**values)
File "/home/antony/granianError/server.py", line 7, in websocket_endpoint
data = await websocket.receive_bytes()
File "/opt/conda/lib/python3.10/site-packages/starlette/websockets.py", line 146, in receive_bytes
message = await self.receive()
File "/opt/conda/lib/python3.10/site-packages/starlette/websockets.py", line 49, in receive
message = await self._receive()
RuntimeError: ASGI flow error
[WARNING] Unsupported websocket message received Pong([])
[ERROR] Application callable raised an exception
Traceback (most recent call last):
File "/opt/conda/lib/python3.10/site-packages/granian/_futures.py", line 4, in future_watcher
await inner(watcher.scope, watcher.proto)
File "/opt/conda/lib/python3.10/site-packages/fastapi/applications.py", line 1054, in __call__
await super().__call__(scope, receive, send)
File "/opt/conda/lib/python3.10/site-packages/starlette/applications.py", line 123, in __call__
await self.middleware_stack(scope, receive, send)
File "/opt/conda/lib/python3.10/site-packages/starlette/middleware/errors.py", line 151, in __call__
await self.app(scope, receive, send)
File "/opt/conda/lib/python3.10/site-packages/starlette/middleware/exceptions.py", line 65, in __call__
await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
File "/opt/conda/lib/python3.10/site-packages/starlette/_exception_handler.py", line 64, in wrapped_app
raise exc
File "/opt/conda/lib/python3.10/site-packages/starlette/_exception_handler.py", line 53, in wrapped_app
await app(scope, receive, sender)
File "/opt/conda/lib/python3.10/site-packages/starlette/routing.py", line 756, in __call__
await self.middleware_stack(scope, receive, send)
File "/opt/conda/lib/python3.10/site-packages/starlette/routing.py", line 776, in app
await route.handle(scope, receive, send)
File "/opt/conda/lib/python3.10/site-packages/starlette/routing.py", line 373, in handle
await self.app(scope, receive, send)
File "/opt/conda/lib/python3.10/site-packages/starlette/routing.py", line 96, in app
await wrap_app_handling_exceptions(app, session)(scope, receive, send)
File "/opt/conda/lib/python3.10/site-packages/starlette/_exception_handler.py", line 64, in wrapped_app
raise exc
File "/opt/conda/lib/python3.10/site-packages/starlette/_exception_handler.py", line 53, in wrapped_app
await app(scope, receive, sender)
File "/opt/conda/lib/python3.10/site-packages/starlette/routing.py", line 94, in app
await func(session)
File "/opt/conda/lib/python3.10/site-packages/fastapi/routing.py", line 348, in app
await dependant.call(**values)
File "/home/antony/granianError/server.py", line 7, in websocket_endpoint
data = await websocket.receive_bytes()
File "/opt/conda/lib/python3.10/site-packages/starlette/websockets.py", line 146, in receive_bytes
message = await self.receive()
File "/opt/conda/lib/python3.10/site-packages/starlette/websockets.py", line 49, in receive
message = await self._receive()
RuntimeError: ASGI flow error
[INFO] Shutting down granian
[INFO] Stopping worker-1 runtime-1
[INFO] Stopping worker-1
Workaround:
if server is running the receive message in a loop, put it in a try-except and check connection state if both websocket.client_state and websocket.application_state are connected then just continue with your normal loop with just a slight hiccup
Faced this when working with a .Net websockets client on Windows
@Antonyesk601 I agree Granian should ignore that, but why are you sending pongs without receiving pings?
I bet if you send pings everything is just fine.
Sending pings is absolutely fine
I wasn't intentionally doing so but the unidirectional pongs seem to be the default behaviour in .Net clients (a user of my api just happened to be using that) and kept reporting that his client keeps disconnecting and apparently thats how .Net works :"
@Antonyesk601 👍 got it. Gonna issue a patch version which discards pongs in the following days.