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

Make code and maybe reason of received websocket disconnect event accessible to application #281

Open
jannschu opened this issue Oct 7, 2023 · 0 comments

Comments

@jannschu
Copy link

jannschu commented Oct 7, 2023

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. CancelledError is 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 django/asgiref#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.

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

1 participant