#WebSocket implementation does not send messages

1 messages Β· Page 1 of 1 (latest)

stable spade
#

For some reason I cannot get Litestar to send messages via websocket, I struggled for hours already.

  1. Please open the demo.html in your browser
  2. Start (the working) FastAPI implementation via uvicorn api2:app --reload
  3. Start Litestar implementation via uvicorn api3:app --reload

I'm aware that websocket is not injected via dependencies = { 'websocket': WebSocket } in the docs, but it throws a warning otherwise.

paper zinc
#

You are misunderstanding websocket listeners

stable spade
#

looks like it πŸ™‚

paper zinc
#

websocket listeners are a high level websocket abstraction. You seem to have ported the code over from FastAPI's low level websocket implementation. If you need to interact with the socket directly, you'll have to use a websocket handler (which you'll also find documented in the documentation I shared πŸ™‚)

#

These treat a WebSocket handler like any other route handler: as a callable that takes in incoming data in an already pre-processed form and returns data to be serialized and sent over the connection.

stable spade
#

I read the docs for 3h now... hehe

#

and I know that this kind of exception handling is bad, that is just a demo

paper zinc
#

It masks an important error though πŸ˜‰

#

Your dependency injection doesn't work and does not do what you think it does

#

Which is quite relevant here I think

stable spade
#

I wonder why I have to inject it anyways, it is not done that way in the docs

paper zinc
paper zinc
paper zinc
#

This is not what you are trying to do

#

Hence why it doesn't work

stable spade
#

I just did it cause if this:

litestar.exceptions.http_exceptions.ValidationException: 400: Missing required query parameter 'websocket' for path /test

#

not because I tried everything πŸ™‚

paper zinc
#

Right. But what made you think adding websocket would inject a WebSocket? That's not mentioned anywhere in the docs either πŸ˜‰

stable spade
#

it says this is a WebSocket instance!?

paper zinc
#

the WebSocket instance can be injected into the handler function via the socket argument:

#

socket. Not websocket. And it doesn't say anything about adding a dependency

stable spade
#

got it, haha... sorry I was blind

paper zinc
#

Even if you did that correctly, the websocket listener will not do what you want it to do, because you're misunderstanding what it's supposed to do

#

It's designed to interact with websockets in an even driven manner

#

i.e. a message comes in, you send a response back

#

If you need control over the socket, e.g. to proactively push data to the client without having received anything prior, you'll have to use the low-level websocket handler

stable spade
#

yeah, I actual need to send updates every 500ms

paper zinc
#
import time
import asyncio
from litestar import Litestar, WebSocket, websocket

@websocket('/test')
async def test(socket: WebSocket):
    await websocket.accept()
    while True:
        await websocket.send_json(time.time())
    await asyncio.sleep(0.5)
#

Just change your websocket_listener to a websocketand adjust your parameters. Everything will work as expected

#

They include a websocket integration, which offers things like connection management and broadcasting

#

In most cases, I go for this instead of dealing with the WS directly

stable spade
#

understood, so this channels are actual made for pushing data?

paper zinc
#

Yes. With a lot of helpful features on top. It's general purpose, so you could use it to push/broadcast over e.g. server-sent events or any other method, but it comes with a websocket integration

stable spade
#

this seems to work now

import time

import asyncio

from litestar import Litestar, websocket


@websocket('/test')
async def test(socket : websocket) -> None:
    await socket.accept()

    while True:
        await socket.send_json(time.time())
        await asyncio.sleep(0.5)

app = Litestar(
[
    test
], debug = True)
#

but I give the channels a shot

#

so I can have one endpoint with multiple channels, right

paper zinc
# paper zinc Yes. With a lot of helpful features on top. It's general purpose, so you could u...

A while ago I wrote an article on how to implement a basic chat with it, that includes feature such broadcasting to many clients, different rooms and a history: https://blog.litestar.dev/creating-a-websockets-chat-in-just-30-lines-with-litestar-2ca0f3767a47

#

It might be a bit outdated when it comes to some APIs (I really should update it I guess πŸ™‚), but the general principle hasn't changed

stable spade
#

yeah, I found that... but to be honest, it looked a bit too complicated

stable spade
#

for what I was looking for

paper zinc
#

Well, it can be used to avoid a whole bunch of boilerplate. If you don't need all of those features to begin with, it maybe not be useful for you

#

For me there's 2 key factors in deciding if something needs it:

  1. Does it need broadcasting - possibly distributed among multiple app instances / servers (because keeping track of all the connections is a lot of work)
  2. Does it need to persist history?
    If none of those apply, then you won't benefit that much
stable spade
#

ok

#

that is actual a good advice

#

just let me wrap things up what went wrong on my side

#
  1. I came from the FastAPI side of things and was confused by the namings
  2. I thought dependency injection is based on the class name - not on the argument name
  3. I did not understand why websocket_listener had the listener suffix - so it is just for pull, not for push
  4. the lack of the vanilla websocket example (that finally works for me) in the docs completed my journey
#

I messed up, but thanks for helping me out πŸ˜„

paper zinc
#

I think 4) is something we can fix. Actually, I'm pretty sure this section missing from the documentation is an issue, because it definitely did exists. Not sure where it went πŸ‘€

#

As for 2): Did you find our documentation lacking there, or were you just used to the way things are done in FastAPI?

stable spade
#

I was actual just ignorant and wondered why would someone add the listener suffix for just websocket, hehe

#

connected dots that I should not have

#
@websocket('/test')
async def test(socket : websocket) -> None:
    await socket.accept()

    while True:
        await socket.send_json(time.time())
        await asyncio.sleep(0.5)
@stream('/test', every = 0.5)
async def test() -> None:
    return time.time()
#

what do you think of an api like that?

paper zinc
#

Seems quite niche an easy enough to implement

#

I don't think we'd want to add something like that to the framework. A bit out of scope

stable spade
#

no problem, I can live with 5 lines of code

paper zinc
#

A better abstraction on top of websockets that allows to be driven by some sort of external event would be nice though

#

Maybe something like

async def generator(socket: WebSocket) -> AsyncGenerator[float, None, None]:
  while True:
    yield time.time()
    await asyncio.sleep(.5)


@websocket_callback("/", callback_generator=generator)
def test(data: float) -> float:
  return data
stable spade
#

yeah

paper zinc
#

So in this example, the test handler would be invoked every time generator yields

stable spade
#

but callback is quite generic for a name πŸ™‚

paper zinc
#

Well it's just an idea. Open for better suggestions πŸ˜‰

#

Maybe @websocket_stream("/", stream=...)?

stable spade
#

stream or cast, yeah

paper zinc
#

Hm. I'm actually not sure if this needs to be its own handler.
An API like this would work just as well:

@websocket("/")
async def handler(socket: WebSocket) -> None:
 await websocket_stream(socket=socket, stream=generator())
#

Thanks for the input though, I think something like this would be a great addition

stable spade
#

I can see you are a professional

#
  1. saying NO first
  2. thinking about it later
#

hehe

#

I actual do it the same way

paper zinc
#

Well that's what you have to do, don't you? Otherwise you're quickly drowning in feature requests and commitments πŸ˜„

#

Also makes management happier. "Remember the thing we said we couldn't do? We found a way to do it! Isn't that awesome?!"

frank kestrel
#

under promise and over deliver πŸ˜‰

paper zinc
stable spade
#

you rock

#

thanks πŸ™

#

there is a typo: oprt=opt,

#

probably testing fails cause of this

paper zinc
#

Feel free to leave a review though

#

PR is still missing docs and a few tests

stable spade
#

got it

#

only thing I would suggest is a non sequential assertion

#
assert ws.receive_text(timeout=0.1) == "foo"
assert ws.receive_text(timeout=0.1) == "bar"```
#

just to make sure the timing is respected

paper zinc
#

That won't work because the testing WS client is actually synchronous under the hood 😬

stable spade
#

oh, haha

paper zinc
#

It's not a real websocket. It just implements the ASGI websocket spec

stable spade
#

you mean the test client?

paper zinc
#

Yup

stable spade
#

got it

paper zinc
#

It's just assumed the servers do the right thing πŸ˜›

stable spade
#

instead of timeout I suggest interval or every

#

it is repeating, correct?

paper zinc
#

No. It really is a timeout. After timeout seconds, the test client won't try to receive anymore and raise an exception

#

Internally there's basically just a queue that the test client waits on when you do socket.receive

stable spade
#

I mixed it up with the real API, good

paper zinc
#

I know where the WS docs went! πŸ˜›

#

We should definitely link to that in the WebSockets section