Make `code` and maybe `reason` of received websocket disconnect event accessible to application
Currently the code number of the websocket.disconnect event is discarded in ASGIWebsocketConnection making it inaccessible to the application.
It is currently not distinguishable whether
- the client closed the connection and for what reason,
- the server closed the connection, for example for shutdown, or
- the task was cancelled, i.e.
CancelledErroris risen, for some other reason (distinction is probably not relevant for most applications).
It would be helpful to distinguish these cases in application code and also read the code value that was given (which would handle case 1 and case 2).
I saw that ASGI currently does not yet specify the reason value for the received disconnect (see related https://github.com/django/asgiref/issues/234). I personally do not need the reason it but I noticed it to be missing in my tests.
Hacky workaround and first thoughts
Currently I hack this feature into my application by using a custom asgi_websocket_class value for my app:
class ASGIWebsocketConnectionWithDisconnectEvent(ASGIWebsocketConnection):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.scope["disconnect_event"] = None
async def handle_messages(self, receive) -> None:
while True:
event = await receive()
if event["type"] == "websocket.receive":
message = event.get("bytes") or event["text"]
await websocket_received.send_async(message)
await self.queue.put(message)
elif event["type"] == "websocket.disconnect":
self.scope["disconnect_event"] = event
return
app = Quart(__name__)
app.asgi_websocket_class = ASGIWebsocketConnectionWithDisconnectEvent
Something like that, i.e. in general checking for the websocket to be closed on a CancelledError, would work for me. Modifying the scope dict is only a workaround of course. My implementation does not cover the third case above. To avoid the except+if pattern
try:
...
except asyncio.CancelledError:
if websocket_closed:
...
a dedicated exception type risen on send and receive seems nice on first sight. On the other hand this will then only be risen on awaits on the websocket's send or receive whereas the cancellation strategy will stop more awaits. What one prefers is probably application dependent.