Skip to content

Abrupt error on closing the websocket connection #111

@leonardbiofi

Description

@leonardbiofi

Description

Using django channels for the websocket server, I have a custom middleware for authentication.

I followed django channels recommendation to set up such custom middleware:

# /ws/authenticate.py
from utils.auth import authenticate
from asgiref.sync import sync_to_async


class QueryAuthMiddleware:
    """
    Custom middleware (insecure) that takes user IDs from the query string.
    """

    def __init__(self, app):
        # Store the ASGI application we were passed
        self.app = app

    async def __call__(self, scope, receive, send):
        # Look up user from query string (you should also do things like
        # checking if it is a valid user ID, or if scope["user"] is already
        # populated).

        (_, token) = scope["query_string"].split(b"=")

        try:
            user, kc_user = await sync_to_async(authenticate, thread_sensitive=True)(token=token.decode("utf-8"))
        except:
            user = None

        scope["user"] = user
        # print("QUERY_STRING", scope["query_string"])

        # scope['user'] = await get_user(int(scope["query_string"]))
        return await self.app(scope, receive, send)
# asgi.py
application = ProtocolTypeRouter({
    "http": django_asgi_app,
    # Just HTTP for now. (We can add other protocols later.)
    "websocket": AllowedHostsOriginValidator(
        QueryAuthMiddleware(
            URLRouter(urlpatterns_ws))
    ),
})

On the client side I have a react application in which I am initializing a WebsockerProvider using yjs library.

When I call wsProvider.close() the connection get abruptly closed on the server side which results in an error:

 Traceback (most recent call last):
middleman   |   File "/usr/local/lib/python3.10/site-packages/uvicorn/protocols/websockets/websockets_impl.py", line 243, in run_asgi
middleman   |     result = await self.app(self.scope, self.asgi_receive, self.asgi_send)  # type: ignore[func-returns-value]
middleman   |   File "/usr/local/lib/python3.10/site-packages/uvicorn/middleware/proxy_headers.py", line 60, in __call__
middleman   |     return await self.app(scope, receive, send)
middleman   |   File "/usr/local/lib/python3.10/site-packages/channels/routing.py", line 48, in __call__
middleman   |     return await application(scope, receive, send)
middleman   |   File "/usr/local/lib/python3.10/site-packages/channels/security/websocket.py", line 37, in __call__
middleman   |     return await self.application(scope, receive, send)
middleman   |   File "/opt/app/ws/authentication.py", line 30, in __call__
middleman   |     return await self.app(scope, receive, send)
middleman   |   File "/usr/local/lib/python3.10/site-packages/channels/routing.py", line 118, in __call__
middleman   |     return await application(
middleman   |   File "/usr/local/lib/python3.10/site-packages/channels/consumer.py", line 95, in app
middleman   |     return await consumer(scope, receive, send)
middleman   |   File "/usr/local/lib/python3.10/site-packages/channels/consumer.py", line 58, in __call__
middleman   |     await await_many_dispatch(
middleman   |   File "/usr/local/lib/python3.10/site-packages/channels/utils.py", line 50, in await_many_dispatch
middleman   |     await dispatch(result)
middleman   |   File "/usr/local/lib/python3.10/site-packages/channels/consumer.py", line 74, in dispatch
middleman   |     await handler(message)
middleman   |   File "/usr/local/lib/python3.10/site-packages/pycrdt_websocket/django_channels_consumer.py", line 209, in send_message
middleman   |     await self.send(bytes_data=message_wrapper["message"])
middleman   |   File "/usr/local/lib/python3.10/site-packages/channels/generic/websocket.py", line 221, in send
middleman   |     await super().send({"type": "websocket.send", "bytes": bytes_data})
middleman   |   File "/usr/local/lib/python3.10/site-packages/channels/consumer.py", line 82, in send
middleman   |     await self.base_send(message)
middleman   |   File "/usr/local/lib/python3.10/site-packages/uvicorn/protocols/websockets/websockets_impl.py", line 359, in asgi_send
middleman   |     raise RuntimeError(msg % message_type)
middleman   | RuntimeError: Unexpected ASGI message 'websocket.send', after sending 'websocket.close' or response already completed.

It seems that the Authentication middleware is causing the error, and I have the impression that it crashed because pycrdt-websocket is still trying to update the awareness or the yDoc on the server side

Reproduce

To reproduce the behaviour create a simple django application,
install django channels configured with daphne and uvicorn as specified in their documentation.

Create a websocket provider on the client side, and it will results in an abrupt closing of the websocket

Expected behavior

I expect that the websocket on server side close smootly without causing any error. When I call wsProvider.close() on the client side the websocket should not try to do another authentication call on the Django Auth middleware.

Context

  • Operating System and version: Ubuntu 22.04
  • Browser and version: Chromium latest version
  • Jupyter Server version: pycrdt-websocket latest
Browser Output
Paste the output from your browser Javascript console here, if applicable.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions