question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

`WebSocketEndpoint`

See original GitHub issue

Provide a class-based websocket handler.

from starlette.endpoints import WebSocketEndpoint


class App(WebSocketEndpoint):
    async def on_connect(self, websocket, **kwargs):
        ...
    async def on_receive(self, websocket, data):
        ...
    async def on_disconnect(self, websocket):
        ...

Issue Analytics

  • State:closed
  • Created 5 years ago
  • Reactions:1
  • Comments:17 (16 by maintainers)

github_iconTop GitHub Comments

3reactions
jordaneremieffcommented, Sep 19, 2018

I’ve started on an implementation for this, it appears to work as expected in my test application. I’m happy to open a PR with tests, documentation, etc. after getting some feedback here.

How does this look so far?

class WebSocketEndpoint:
    def __init__(self, scope: Scope):
        self.scope = scope

    async def __call__(self, receive: Receive, send: Send):
        websocket = WebSocket(self.scope, receive=receive, send=send)
        kwargs = self.scope.get("kwargs", {})
        await self.on_connect(websocket, **kwargs)
        try:
            while True:
                message = await websocket.receive()
                websocket._raise_on_disconnect(message)
                if message["type"] == "websocket.receive":
                    await self.on_receive(websocket, message)
                elif message["type"] == "websocket.disconnect":
                    await self.on_disconnect(websocket)
        except WebSocketDisconnect as exc:
            await self.on_disconnect(websocket)

    async def on_connect(self, websocket, **kwargs):
        """Override to handle an incoming websocket connection"""
        await websocket.accept()

    async def on_receive(self, websocket, message):
        """Override to handle an incoming websocket message"""

    async def on_disconnect(self, websocket):
        """Override to handle a disconnecting websocket"""
        await websocket.close(code=1000)
0reactions
vlori2kcommented, Sep 1, 2020

I was thinking again about how we are passing the WebSocket instance throughout the WebSocketEndpoint instance in the current proposed pattern, and I was wondering if it may be better to store the websocket state on the endpoint instead as @tomchristie initially suggested. This also made me wonder if there is any reason not to inherit from the WebSocket class and expose its methods directly.

Here are two potential ways of implementing that seem to be a bit more flexible based on how I’ve been using it:

class WebSocketEndpoint(WebSocket):

    encoding = None

    async def __call__(self, receive: Receive, send: Send):
        self._receive = receive
        self._send = send
        close_code = 1000

        try:
            while True:
                message = await self.receive()
                if message["type"] == "websocket.connect":
                    await self.on_connect(message)
                elif message["type"] == "websocket.receive":
                    data = await self.decode(message)
                    await self.on_receive(data)
                elif message["type"] == "websocket.disconnect":
                    close_code = message["code"]
                    return
        finally:
            if close_code == 1006:
                raise RuntimeError("WebSocket error, connection closed abnormally")
            await self.on_disconnect(close_code=close_code)

    async def decode(self, message):
        if self.encoding == "text":
            if "text" not in message:
                await self.close(code=1003)
                raise RuntimeError("Expected text websocket messages, but got bytes")
            return message["text"]

        elif self.encoding == "bytes":
            if "bytes" not in message:
                await self.close(code=1003)
                raise RuntimeError("Expected bytes websocket messages, but got text")
            return message["bytes"]

        elif self.encoding == "json":
            if "bytes" not in message:
                await self.close(code=1003)
                raise RuntimeError(
                    "Expected JSON to be transferred as bytes websocket messages, but got text"
                )
            return json.loads(message["bytes"].decode("utf-8"))

        assert (
            self.encoding is None
        ), f"Unsupported 'encoding' attribute {self.encoding}"

        return message["text"] if "text" in message else message["bytes"]

    async def on_connect(self, message):
        """Override to handle an incoming websocket connection"""
        await self.accept()

    async def on_receive(self, data):
        """Override to handle an incoming websocket message"""
        await self.receive(data)

    async def on_disconnect(self, close_code):
        """Override to handle a disconnecting websocket"""
        await self.close(code=close_code)

Alternatively, we could do the same as above except like this:

class WebSocketEndpoint:
    def __init__(self, scope: Scope) -> None:
        self.scope = scope

    async def __call__(self, receive: Receive, send: Send):
        self.websocket = WebSocket(self.scope, receive=receive, send=send)
        ...

    async def on_connect(self, message):
        """Override to handle an incoming websocket connection"""
        await self.websocket.accept()

    async def on_receive(self, data):
        """Override to handle an incoming websocket message"""
        await self.websocket.receive(data)

    async def on_disconnect(self, close_code):
        """Override to handle a disconnecting websocket"""
        await self.websocket.close(code=close_code)

Likely a bit of finessing required. Also, I haven’t been across all of the discussion surrounding the disconnect/close behaviour, so maybe there is something from there that could help inform this as well.

Thoughts?

Does this work? im having trouble with " RuntimeError: Unexpected ASGI message ‘websocket.send’, after sending ‘websocket.close’."

Read more comments on GitHub >

github_iconTop Results From Across the Web

Endpoints - Starlette
The WebSocketEndpoint class is an ASGI application that presents a wrapper around the functionality of a WebSocket instance. The ASGI connection scope is ......
Read more >
Endpoint (Java(TM) EE 7 Specification APIs)
The Web Socket Endpoint represents an object that can handle websocket conversations. Developers may extend this class in order to implement a programmatic ......
Read more >
WebsocketEndpoint (Jakarta Server Fages API documentation)
This web socket server endpoint handles web socket requests coming from <f:websocket> . Since: 2.3; Author: Bauke Scholtz; See Also ...
Read more >
WebSocketEndPoint (CometD :: Java 4.0.9 API)
public class WebSocketEndPoint extends javax.websocket.Endpoint implements javax.websocket.MessageHandler.Whole<String> ...
Read more >
starlette/endpoints.py at master · encode/starlette - GitHub
class WebSocketEndpoint: encoding: typing.Optional[str] = None # May be "text", "bytes", or "json". def __init__(self, scope: Scope, receive: Receive, ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found