Forum OpenACS Development: Re: Implementing Server-Sent Event backend in OpenACS

Collapse
Posted by Brian Fenton on
Thanks Gustaf

this script is the one from the official Mozilla documentation on Server Sent Events so I thought it would a good starting point. https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events

SSEs seem to be a good fit for my particular use case, web sockets are probably a bit too much for my needs.

Thanks for pointing out "ns_connchan" - I will see if I can make use of that.

all the best
Brian

Collapse
Posted by Gustaf Neumann on

Dear Brian,

I've looked into it and tried to come with a fairly simple example. The question you have not addressed is, where the events are coming from that you want to distribute. In the example below, the events are triggered from a scheduled procedure. There is certainly much more which can be done: Define a small API, and/or integrate with the message-redirector, etc. etc.

Hope you find this simple example useful!
-g

Subscriber. This script is called by the EventSource() call from the HTML page (see below). Place it e.g. into /www/timer-sse.tcl

 #
 # A sample SSE subscriber
 #
 # Detach the channel from the connection thread and make it usable in the background.
 # The channel is added to the nsv-array of the SSE subscribers
 #

 set channel [ns_connchan detach]
 ns_connchan write $channel [append _ \
                                "HTTP/1.1 200 OK\r\n" \
                                "Cache-Control: no-cache\r\n" \
                                "Content-type: text/event-stream\r\n" \
                                "\r\n"]

 #
 # Send some initial data (not needed)
 #
 ns_connchan write $channel "data: Hi There!\n\n"
 ns_connchan write $channel "data: Start time is: [clock format [clock seconds]]\n\n"

 #
 # Let background task know about us
 
 nsv_set sse_subscribers $channel 1

Background task checking for subscribers and delivering messages to them.

Place this e.g. into some *-init.tcl file such this is registered during startup:

    # initialize nsv array
    nsv_set sse_subscribers {} {}

    #
    # Background task: check every 5 seconds for subscribers and tell them the time.
    #
    ns_schedule_proc -thread 5s {
        foreach subscriber [nsv_array names sse_subscribers] {
            if {$subscriber eq ""} continue
            try {
                ns_connchan write $subscriber "data: A voice from the background says: it is now [clock format [clock seconds]]!\n\n"
            } on ok {result} {
                # Everyhing was fine.
            } on error {errorMsg} {
                ns_log notice "SSE SEND failed: '$errorMsg'"
                ns_connchan close $subscriber
                nsv_unset sse_subscribers $subscriber
            }
        }
    }

Sample page for testing (remove the space before "script>"):

<!DOCTYPE html>
<html>
<meta content="utf-8" http-equiv="encoding">
<title>Timer SSE demo</title>
<body>

<h1>Getting server updates</h1>
<div id="result"></div>

< script>
if(typeof(EventSource) !== "undefined") {
  var source = new EventSource("/timer-sse.tcl");
  source.onmessage = function(event) {
    document.getElementById("result").innerHTML += event.data + "<br>";
  };
} else {
  document.getElementById("result").innerHTML = "Sorry, your browser does not support server-sent events...";
}
</script>

</body>
</html>
Collapse
Posted by Brian Fenton on
Gustaf, THANK YOU!

That's is incredibly useful, and your help has been really above and beyond my expectation. I'm very grateful.

regards
Brian