Server-Sent Event Notifications with HTML5

Server-Sent Events (SSE) have long been in the shadow of the much more often talked about WebSockets API. While WebSockets can be thought of as the “TCP for the web”, then SSE is best described as the “HTML5 replacement for Comet“. Question is, do we really need both?

WebSockets allow bi-directional communication and to do so, they piggyback on the original HTTP connection and “upgrade” to the WebSocket protocol. By comparison, and as the name implies, SSE is strictly a server push protocol. Additionally, messages are encoded in UTF-8 and are newline delimited. In other words, the bet is that most applications primarily need to push data to the client, which means that we can simplify the API and the implementation - that is where SSE comes in. Still skeptical? I was. Let’s take a look under the hood.

EventSource Client API

Unlike a WebSocket connection, SSE requires no additional HTTP handshakes or “protocol upgrades”. For all intents and purposes, an SSE channel is simply a long-lived, streaming HTTP connection. This alone means that you can implement an SSE service on top of any streaming capable HTTP server.

On the browser side, the EventSource API is now supported by Chrome, Firefox 6+, Safari and Opera. To start receiving server notifications, we simply open a connection to an SSE URI and setup our callbacks:

var source = new EventSource ( '/my/sse_endpoint' ); source . addEventListener ( 'message' , function ( e ) { console . log ( e . data ); }, false ); source . addEventListener ( 'user_login' , function ( e ) { console . log ( e . data ); }, false ); source . addEventListener ( 'open' , function ( e ) { // Connection was opened. }, false ); source . addEventListener ( 'error' , function ( e ) { if ( e . eventPhase == EventSource . CLOSED ) { // Connection was closed. } }, false );

The API is very simple: create an EventSource connection, define your connection open, close, and message callbacks, and you are off to the races. However, the SSE API also provides a few more built-in features. In the example above we added a “user_login” listener - turns out, we can tag our messages! Likewise, we can provide message ID’s, and the SSE source will even automatically reconnect for you if the connection is dropped.

Simple SSE Server with Goliath

The client API is nice and simple, but where the SSE spec really shines is in how simple it makes the server implementation. Whereas to power a WebSocket service you will likely need an entirely different backend, an SSE endpoint can be implemented by any streaming capable HTTP web server. As an example, let’s create a Goliath powered SSE app and deploy it to Heroku:

require 'goliath' class SSE < Goliath :: API use Rack :: Static , :urls => [ "/index.html" ] , :root => Goliath :: Application . app_path ( "public" ) def response ( env ) EM . add_periodic_timer ( 1 ) { env . stream_send ( "data:hello # #{ rand ( 100 ) }



" ) } EM . add_periodic_timer ( 3 ) do env . stream_send ( [ "event:signup" , "data:signup event # #{ rand ( 100 ) }



" ]. join ( "

" )) end streaming_response ( 200 , { 'Content-Type' => 'text/event-stream' }) end end

Our Goliath server responds to requests to /events by returning a 200 OK response, and then starts emitting newline delimited messages every couple of seconds - we’re streaming plaintext messages directly into the HTTP connection! Additionally, Goliath also serves a static index file, which opens an EventSource connection to our /events endpoint (see the full gist here). With that, we can add a Procfile and a Gemfile, and push it out to Heroku: a live SSE demo powered by Goliath.

SSE vs. WebSockets

Technically speaking, the WebSockets spec is a superset of SSE and it is easy to wonder why we need both. Having said that, it is also hard not to like the simplicity of the EventSource API - both on the client, and especially on the server. Chances are, many apps won’t actually need the bi-directional capabilities of WebSockets, and hence can benefit a great deal from the simplified setup and deployment enabled by SSE. Definitely an option to keep in mind.