HTTP.jl Websockets- help getting started

Looking for some help getting started with websockets in HTTP.jl. For some reason I can’t seem to grasp the design and intended usage. For example, I can look at this Python example:

import websocket
ws = websocket.WebSocket()
ws.connect("ws://echo.websocket.events")
ws.send("Hello, Server")
>>19
print(ws.recv())
>>echo.websocket.events sponsored by Lob.com
ws.close()

This makes perfect sense to me. The websocket is available as ws and you connect, then can send and receive and finally close.

In the HTTP.jl implementation I have no idea how to access a connection that I create. The example given in the docs is:

# simple websocket client
WebSockets.open("ws://websocket.org") do ws
    # we can iterate the websocket
    # where each iteration yields a received message
    # iteration finishes when the websocket is closed
    for msg in ws
        # do stuff with msg
        # send back message as String, Vector{UInt8}, or iterable of either
        send(ws, resp)
    end
end

But this seems to be a blocking infinite loop, REPL is locked and I have to kill it to do anything. Beyond that, the socket itself is not accessible anywhere except within the scope of the do block. What if I wanted to do some other process outside this block and then have the websocket send a new message?

I think I’m just missing the design intent as I can’t seem to figure out a way to work with this setup. I’ve tried to find some real examples but the only things I can fid are of the # do stuff with msg # send back message as String, Vector{UInt8}, or iterable of either variety. Again, being within the blocking loop makes it so I can’t do that unless I already know what I want to do before initiating the connection.

Any guidance or working examples would be greatly appreciated.

1 Like

The equivalent of your Python snippet would probably be something along the lines of:

julia> using HTTP

julia> WebSockets.open("ws://echo.websocket.events") do ws
           WebSockets.send(ws, "Hello, Server")
           @show WebSockets.receive(ws)
       end;
WebSockets.receive(ws) = "echo.websocket.events sponsored by Lob.com"
  • open will
    1. connect to the server,
    2. execute the anonymous function you give it, passing the websocket as the ws argument, and
    3. ensure the socket is closed in the end
  • send and receive are self-explanatory I think

You’re right that we don’t provide the same interface as python. It was a design decision to try and help users use websockets correctly and simplify usage. The WebSockets.open call is a single call to manage the lifetime of a websocket, ensuring the connection is made and closed appropriately. I agree that it could be nice to have a more interactive-friendly version where you call connect/close explicitly and that wouldn’t be very hard to add, so feel free to open an issue on the HTTP.jl repo (or even better, make a PR!).

1 Like

Thanks for the input. I dug through the source a little and came up with this:

function rawws(url,headers =[])
    headers = [
        "Upgrade" => "websocket",
        "Connection" => "Upgrade",
        "Sec-WebSocket-Key" => base64encode(rand(Random.RandomDevice(), UInt8, 16)),
        "Sec-WebSocket-Version" => "13",
        headers...
    ]
    r = HTTP.openraw("GET",url,headers)[1]

    ws = WebSockets.WebSocket(r)
    return ws
end

This is just constructed from parts and pieces of the HTTP.jl package and essentially gives a raw web socket that you have to manage manually. I’m using this with SurrealDB and so far it is working well. I see the intent and benefit of having the full lifecycle of the WS managed and ensuring that it is closed, but also having a persistent socket has been handy for prototyping and troubleshooting.

I’d love to make this available for anyone else who might find it useful, but I’m not sure of a few things:

  1. Is this a reasonable solution? (Works for me, but does it make sense beyond my use case?)
  2. If yes, I don’t know how to submit a PR, but willing to learn. Any resources for a newbie?
2 Likes

Would you give us an example of how you would use this? Do you do something like

ws = rawws(url)
@async begin
    for msg in ws
        handler(msg)
    end
end
HTTP.send(ws, some_message)

Does this work?

1 Like

Ok I just tested this. It appears that it does indeed work. This is fantastic.