#websocket with channels route not working

1 messages · Page 1 of 1 (latest)

lone mesa
#
class EmiSimulatorWebServer:
    def __init__(...):
        self._channels = ChannelsPlugin(backend=MemoryChannelsBackend(history=1), channels=[t.short for t in targets])
        self._litestar = Litestar(
            [self.handler],
            static_files_config=[StaticFilesConfig(path="/static", directories=[self.get_static_path()])],
            plugins=[self._channels],
        )
        # where bind address is ('0.0.0.0', 5555)
        self._server = EmbeddedServer(EmbeddedConfig(self._litestar, *self._bind_address))

    @websocket("/api/v1/ws")
    async def handler(self, socket: WebSocket, channels: ChannelsPlugin) -> None:
        if socket.client is None:
            self.logger.info("Not accepting web socket connection with `None` client")
            return
        self.logger.info(f"Accepting web socket connection from {socket.client}")
        all_channels = [t.short for t in self.targets]
        await socket.accept()
        async with channels.subscribe(all_channels) as subscriber:
            await channels.put_subscriber_history(subscriber, all_channels, limit=1)
            async with subscriber.run_in_background(socket.send_text):
                self.logger.info(f"Sent BACnet target status to {socket.client}; waiting for events..")
                received = {"type": "websocket.connect"}
                while received["type"] != "websocket.disconnect":
                    received: WebSocketReceiveMessage = await socket.receive()
                    self.logger.info(f"Received BACnet event from {socket.client} of {received['text']}")
                    if received["type"] == "websocket.receive":
                        await self._handle_bacnet_event(socket.client, received["text"])

I can't seem get a connection when connecting to ws://127.0.0.1:5555/api/v1/ws Am I missing something?

rigid mantle
lone mesa
#

Litestar doesn't seem to be emitting any errors in console, so not that I am aware of

rigid mantle
#

is it possible for you to give a minimal example so one can copy paste to replicate the same issue?

lone mesa
#

I can try and extract something more minimal from what I have here.. I will need 5 mins

rigid mantle
#

take your time, where is websocket from?

lone mesa
#
from litestar import Litestar, Response, WebSocket, get, post, websocket
from litestar.channels import ChannelsPlugin
from litestar.channels.backends.memory import MemoryChannelsBackend
from litestar.datastructures import Address
from litestar.exceptions import ClientException
from litestar.response import Redirect
from litestar.static_files import StaticFilesConfig
from litestar.types import WebSocketReceiveMessage
from pydantic import BaseModel, Field
from pydantic.alias_generators import to_camel
from uvicorn import Config, Server
rigid mantle
#

thanks, let me try running this

lone mesa
#

Seeing this INFO: ('127.0.0.1', 52672) - "WebSocket /ws" 403 in your stripped down example which does INFO level logging .. I was only showing WARNING and above in our full code

rigid mantle
#

I atleast get this

INFO:     ('127.0.0.1', 50798) - "WebSocket /ws/api/v1/" 403
INFO:     connection rejected (403 Forbidden)
INFO:     connection closed
lone mesa
#

yeah. why is it rejected? is the handler not being recognized?

rigid mantle
#

let me check

lone mesa
#

perhaps it doesn't like a method being used as a handler?

rigid mantle
lone mesa
#

that was my next thing to try. it means a fair amount of boilerplate to pass around some dependencies so I was hoping to avoid it

#

I bring up my case because it could be an issue with support for methods. I expected some sort of error if that was the case though

#

The WebsocketListener seems to have an on_receive() callback but how do I integrate that with channels? Do I

        async with channels.subscribe(all_channels) as subscriber:
            await channels.put_subscriber_history(subscriber, all_channels, limit=1)

in the on_accept() callback?

rigid mantle
lone mesa
#
@websocket_listener("/", connection_accept_handler=accept_connection)
def handler(data: str) -> str:
    return data

This seems inappropriate for my use-case as well as I need to push stuff to the websocket even if I am not receiving anything

#

so I guess what I do is create my own "accept_connection" and start an asyncio task there?

#

The docs are really poor for my use case.. If I can figure this out I am probably going to do a MR with some improvements

rigid mantle
#

let me understand what exactly you did, in the mean time you might get other responses

lone mesa
#

to help explain some bits of the code / context better:

EnteliTargets are "targets" in a 3rd party system (BACnet system which is connected up to some hospital equipment) Essentially they are boolean values that could change at any time. If they change we need to communicate that through websocket. Additionally, the user can change the values and we receive those actions through the websocket.

#

two-way sync + control

#

each EnteliTarget is a "Channel"

rigid mantle
#

it does seem to be self

lone mesa
#

so there is an issue with using a method?

rigid mantle
#

seems like it

#

also async with channels.subscribe(BACNET_TARGETS) as subscriber the usage for that has been changed

rigid mantle
#
from litestar import Litestar, WebSocket, websocket
from litestar.channels import ChannelsPlugin
from litestar.channels.backends.memory import MemoryChannelsBackend
from litestar.datastructures import Address
from litestar.types import WebSocketReceiveMessage

BACNET_TARGETS = ["BO1101", "BO1102", "BO1103", "BO1104", "BO1201", "BO1202", "BO1203", "BO1204"]


class Something:
    @websocket("/ws")
    async def handler(socket: WebSocket, channels: ChannelsPlugin) -> None:
        if socket.client is None:
            print("Not accepting web socket connection with `None` client")
            return
        print(f"Accepting web socket connection from {socket.client}")
        await socket.accept()
        async with channels.start_subscription(BACNET_TARGETS) as subscriber:
            await channels.put_subscriber_history(subscriber, BACNET_TARGETS, limit=1)
            async with subscriber.run_in_background(socket.send_text):
                print(f"Sent BACnet target status to {socket.client}; waiting for events..")
                received = {"type": "websocket.connect"}
                while received["type"] != "websocket.disconnect":
                    received: WebSocketReceiveMessage = await socket.receive()
                    print(f"Received BACnet event from {socket.client} of {received['text']}")
                    if received["type"] == "websocket.receive":
                        await _handle_bacnet_event(socket.client, received["text"])

async def _handle_bacnet_event(client: Address, event_json: str) -> None:
    print(f"fake handling bacnet event from {client} of {event_json}")

app = Litestar([Something().handler], plugins=[ChannelsPlugin(backend=MemoryChannelsBackend(history=1), channels=BACNET_TARGETS)])
#

that is a minimal example 🙂

#

if you add self you can see it does indeed break

#

without self

Accepting web socket connection from Address(host='127.0.0.1', port=50897)
INFO:     ('127.0.0.1', 50897) - "WebSocket /ws" [accepted]
Sent BACnet target status to Address(host='127.0.0.1', port=50897); waiting for events..
INFO:     connection open
Received BACnet event from Address(host='127.0.0.1', port=50897) of ok
fake handling bacnet event from Address(host='127.0.0.1', port=50897) of ok
lone mesa
#

thanks for the simplification.. I already trimmed so much bulk from what I had. The self was important for other reasons that were trimmed.

#

the instance is managed by an orchestrator in another thread

#

and it needs to be able to be torn down, reconfigured and started based on external changes..

rigid mantle
#

the issue you are facing is replicable with just this

lone mesa
#

Alc: understood. And thanks for doing the extra pass to clean up what I had

rigid mantle
#

I will let a maintainer comment on if self / method usage is a bug

rigid mantle
lone mesa
#

do you want to create an issue or would you prefer I do it?

rigid mantle
#

we have a bot 🙂

daring mulchBOT
rigid mantle
#

feel free to add stuff there

#

whats your GH?

#

so I can tag you

lone mesa
#

skewty

rigid mantle
#

heya, I have done some edits

#

if you want to make edits to the post let me know, you must be able to leave a comment anyways

#

so I removed a lot of stuff

#

your only issue is its a method

#

or at least thats that example leads me to believe

lone mesa
#

yeah. I am trying with a @staticmethod and dependency injection

#

NOTE: in your example you didn't include self parameter to method and didn't specify either @staticmethod or @classmethod

rigid mantle
#

yes I mentioned adding self causes it to break

#

it is just to have a "running" example, not to have a "proper" example

bold tangle
violet acornBOT
#

litestar/channels/plugin.py lines 117 to 126

if self._arbitrary_channels_allowed:
    path = self._handler_root_path + "{channel_name:str}"
    route_handlers = [WebsocketRouteHandler(path)(self._ws_handler_func)]
else:
    route_handlers = [
        WebsocketRouteHandler(self._handler_root_path + channel_name)(
            self._create_ws_handler_func(channel_name)
        )
        for channel_name in self._channels
    ]