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

Hello everyone

I'm investigating Server-Sent Events and am wondering how to implement the backend in OpenACS/Naviserver. I'm looking at this simple example from Mozilla written in PHP https://github.com/mdn/dom-examples/blob/master/server-sent-events/sse.php and there are a few things I'm not quite sure how to implement in OpenACS.

Instead of the "echo" and "flush" commands, in Naviserver we have "ns_write" - this seems relatively straightforward but I would like to hear if there is a better way?

The one that really has me puzzled is this check if the client aborted the connection:

if ( connection_aborted() ) break;

I don't see any equivalent API in Naviserver - any suggestions?

many thanks

Brian

Collapse
Posted by Gustaf Neumann on
A quick look gives me the impression that this is a script which checks every second, if there is some data, and forwards this data to the web client, who is keeping this connection open. is this correct?

If so, this is similar what OpenACS chat does, and in some aspects a poor man replacement to web sockets. OpenACS xowiki chat, which in turn uses the message relay [2], which implements different push technologies.

In general, you cannot use "ns_write", since this writes plain text to the socket, causing troubles . The generic replacement is "ns_connchan" [3], which works the same way via HTTP and HTTPs, and which is used as a delivery method by the message relay, or in the websocket module [4], or in reverse proxy [5], etc.

When one writes to a client that has disappeared, "ns_connchan write" will return an error; one can set callback which indicate errors as well (see "when" option of "ns_connchan callback")

-gn

[1] https://github.com/openacs/xowiki/blob/oacs-5-10/tcl/chat-procs.tcl#L11
[2] https://github.com/openacs/xotcl-core/blob/oacs-5-10/tcl/message-relay-procs.tcl
[3] https://naviserver.sourceforge.io/n/naviserver/files/ns_connchan.html
[4] https://bitbucket.org/naviserver/websocket/src/master/
[5] https://bitbucket.org/naviserver/revproxy/src/master/

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

Collapse
Posted by Gustaf Neumann on
Brian,

how went your experiments with Server-Sent Events? Could you use this code? Should we consider some API?

-g

Collapse
Posted by Brian Fenton on
Hi Gustaf

yes, it's been going very well - your code was a huge help, and needed almost no changes for my application. I'm making slow progress writing a front end using Vue.js but it's a great learning experience.

Many thanks again
Brian

Collapse
Posted by Malte Sussdorff on
Thanks Gustaf for this. We will experiment with this later this year, replacing our polling for "notifications" into SSE as described here.
Collapse
Posted by Gustaf Neumann on
I have added a slightly more advanced SSE example to the Cookbook [1] used to report from background activities (an ns_job) to the client. Maybe someone finds this useful...

all the best
-g
[1] https://openacs.org/xowiki/sse