Server Sent Events with Aura and Node.js

You can view the demo app or the source on Github.

Recently I uncovered a relatively fresh and unique HTML5 feature; Server Sent Events.

Note: I will digress from the onset that if you are familiar with Websockets, then you should understand that they are the superior implementation for scalable bidirectional push communication, and not a competiting technology for SSE.

SSE on the other hand can be viewed as more of a "lightweight" alternative, for when using websockets is not a viable option, most likely due to server limitations.

To form a base understanding of SSE, here are the main design characteristics:

  • Everything happens over the HTTP, no special protocol required
  • Keeps open a HTTP/S connection to the server
  • Communication is unidirectional (read only by the client)
  • Different event sources/channels can be fed down the same connection
  • Auto reconnection and event IDs come for free as part of the implementation.i The auto-reconnection is ideal for flaky connections, like mobile devices.

There are some pros and cons of using SSE over websockets. Let's go over the cons first:

Cons:

  • The data source is read only. While this lends itself to actually being more scalable for RO type applications, for RW type app/pages you'll have to revert to POST'ing or long polling type server writes.
  • Depending on the server configuration, the connection may be timed out frequently or free'd up for other users accessing the server, but mitigated by the auto-reconnection as per the EventSource spec.
  • Each SSE source maintains an open connection to the server. This takes away server side resources from other users, such as memory, CPU, NOFILE and lower max_connections type limits of the HTTP server (as opposed to available kernel connections/ports).

Pros:

With that said SSE does hold advantages over Websockets in some scenarios:

  • Doesn't require any special protocol to be supported in the browser or server
  • SSE support can be polyfilled (backported) for older browsers that don't yet implement it.
  • Integrated Cross-site/origin request support (via CORS)
  • SSE can ingeniously benefit from HTTPS with no extra configuration, unlike websockets/WSS, which require certificate setup and implementation on top of TLS.

Enough, shut up and show me the glory of SSE!

OK then, let us wield this technology for the betterment of the internets! If you want to dive straight into the demo app then go here: https://github.com/jdpaton/sse-dash

As prescribed, we'll look at implementing this in Node.js. Here's the first excerpt you should get familiar with:

module.exports.writeSSEHead = function (req, res, cb) {
    res.writeHead(200, {
        "Content-Type": "text/event-stream",
        "Cache-Control": "no-cache",
        "Connection": "keep-alive"
    });
    res.write("retry: 10000\n");
    return cb(req, res);
}

Nothing so unusual, but the first part of the SSE spec is here, specifically the retry line, telling the client, that in the event a disconnect, it should retry after ten seconds. Note the header is text/event-stream so the browser knows it's SSE, we tell the browser not to cache any of the responses (you don't want stale real time data do you? ) and we make sure the connections is kept open by the browser/client via Connection: keep-alive. Straight forward stuff.

Next up is writing a custom event with data:

module.exports.writeSSEData = function (req, res, event, data, cb) {
    var id = (new Date()).toLocaleTimeString();
    res.write("id: " + id + '\n');
    res.write("event: " + event + "\n");
    res.write("data: " + JSON.stringify(data) + "\n\n");
    if (cb) {
        return cb(req, res);
    }
};

The first part here is making sure we send a unique ID with each request, we just use a simple date based one here. The ID is useful so that if a client reconnects later, it will send the Last-Event-ID header in the response, so the server can decide on a delta of which to resume push messages (not implemented in the demo app though).

Note in Apache, Nginx or as here for Node.js you should consider upping the timeout for SSE requests. This is easily set in Node 0.10.x, and for per-request instead of a global socket setting you can do the following (20 min idle):

res.connection.setTimeout(1000 * 60 * 20)

Using these functions in an express.js app to echo the servers date every second might look like the following:

app.get('/stream/date', function(req, res) {

    var event = 'date';

    sse.writeSSEHead(req, res, function(req, res) {
      sse.writeSSEData(req, res, event, new Date(), function(req, res) {

        intervalx = setInterval(function() {
          sse.writeSSEData(req, res, event, new Date());
        }, 1000);

        req.connection.addListener("close", function() {
            clearInterval(intervalx);
        });
      });
    });
});

We then use a new EventSource ( MDN ) native browser object to connect to this endpoint ( full code: here ):

function connect() {
        source = new EventSource("stream/date");

        source.addEventListener("date", function(event) {
          self.updateDate(event.data);
        }, false);

        source.addEventListener("open", function(event) {
          button.text("Disconnect");
          button.click( function(event) {
            source.close();
            button.text("Connect");
            button.click(connect);
            status.text("Connection closed!");
          });
          button_cont.removeClass("warning");
          status.text("Connection open!");
        }, false);

 [...]

The EventSource parameter is the endpoint resource relative the page path, or a full URI. Note that SSE supports cross-domain requests out of the box, via CORS. The date event listener is listening for lines from the server like:

event: data
data: '"2013-07-07T07:04:55.162Z"'

and on each update we simply update our HTML element with the new element - simples!

Note the client code examples here are all using Aura, a pretty cool project mentored by @addyosmani. We can write a Aura widget per SSE endpoint, and use it anywhere on the website without having to do anything - just add a <div>! :

<div data-aura-widget="sse-date"></div>

"Aura is an event-driven architecture for wrapping your code into reusable widgets and extensions that can easily communicate with each other" - aurajs.com

I won't cover Aura in depth here, but the code in the demo app and on material on their site should be enough to get you started. In pursuit of this project you could combine widgets to create customizable dashboards whose components can talk to each other with realtime SSE data, so lots of avenues to explore! I'd highly recommending using Aura for projects like this, so check it out!