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

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