Pushing irregular updates w/ HttpServer & Server Sent Events / Websockets

Recently I had the idea to add a web server to a long running computation, which should give essentially updates on the status of the computation to all connected browsers by simply pushing the new data at rather large intervals.
Thinking about some number crunching style computation that runs a week on a cluster, and at certain milestones would want to push a status, or print intermediate critical warnings that the solver starts diverging etc., and with this enabling users to e.g. read/discuss them in a meeting even on a mobile…

Not being much of a web developer, I found HTML5 Server Sent Events (SSE) to perhaps be the feature to make this possible. An alternative could be WebSockets, as @hustf pointed out by commenting my current non-working solution using SSE in this gist.

I guess, the steps to be taken would be:

  • create an http server that handles the basic connections (as in the gist above)
  • create a logging function or IOStream for the computational kernel to “print” to,
  • hook the two things somehow.

And the last two are pretty much where I’m stuck. I just don’t get it how to make that connection.
Google isn’t much help since only providing me with trivial examples…and not written in Julia either.

I’d be happy for any advice or hint towards some Julia code that is accomplishing something similar.


Edit: And I guess one of the issues is HttpServer.run(...) blocking…

Web part

You may use web sockets or even just periodic requests from JavaScript using setInterval(). The advantage of web sockets is that clients will get updates almost immediately, while setInterval() should give compatibility with even oldest browsers.

Logging part

One way to post updates on one side and read them on the other side is to use message queues such as ZMQ. For example, you can add something like this to your computational process:

using ZMQ

ctx = Context()
sock = Socket(ctx, PUB)
ZMQ.bind(sock, "tcp://*:5555")

# do stuff and publish updates to ZeroMQ socket at port 5555
while true
    println("Sending a message")
    send(sock, Message("new update..."))
    sleep(1)
end

and something like this to a web server process:

using ZMQ

ctx = Context()
sock = Socket(ctx, SUB)

ZMQ.subscribe(sock)
connect(sock, "tcp://localhost:5555")

# read the updates from port 5555 and print them to console
while true
    data = unsafe_string(ZMQ.recv(sock))  
    println("Received a message: $data")
end

Try putting these snippets to files, say, pub.jl and sub.jl and running them in 2 different consoles to get the feeling of what’s going on.

Putting it all together

Your web server may then look like this:

using HttpServer
using WebSockets
using ZMQ

wsh = WebSocketHandler() do req,client
    ctx = Context()
    sock = Socket(ctx, SUB)
    
    ZMQ.subscribe(sock)
    connect(sock, "tcp://localhost:5555")
    
    while true
        data = unsafe_string(ZMQ.recv(sock))
        write(client, "Received a message: $data")
    end
end

server = Server(wsh)
run(server,8080)

and on the web page you can do:

var ws = new WebSocket("ws://localhost:8080")
ws.onmessage = function (event) {
  console.log(event.data);  // or fill in a div with messages                                           
}

Note that this is only one way to implement it, you can replace literally any part of this implementation. For example, you cab write updates to a persistent message queue like Kafka or even to a database instead of ZMQ, and then retrieve updates from the last time. Or you can replace web sockets with ordinary HTTP requests, etc.

2 Likes

Cool, many thanks! That’s a really useful approach since it eventually permits me even to embed multiple computations on one website; just like e.g. Travis CI. :slight_smile:

Thanks for the ideas and code! If you are looking for more of a framework, you may find this useful: https://bitbucket.org/jocklawrie/skeleton-webapp.jl

1 Like

I’ll take a closer look into it. Thanks for the tip.