Server-Sent Events

by Remy Sharp.

Tweet

We’ve already had a glimpse at Server-Sent Events (also known as EventSource, and I’ll switch between the two to keep you on your toes) in my Methods of Communication article from last year. In this article, I want to delve in to more detail about the SSE API, demonstrate its features, and even show you how to polyfill browsers that lack EventSource support.

Server-Sent Events are real-time events emitted by the server and received by the browser. They’re similar to WebSockets in that they happen in real time, but they’re very much a one-way communication method from the server.

These events are similar to ordinary JavaScript events that occur in the browser — like click events — except we can control the name of the event and the data associated with it.

All the code for this article is available on github, and a live demo is available online.

† Is it Server-Sent Events or EventSource? Well, they both work. Server-Sent Events is the name of the API and specification. EventSource is the name of the JavaScript object you’re instantiating. It’s a bit like Ajax and XHR, where XHR refers to the XMLHttpRequest object…kinda.

Two notes: a) the uptime for this example is, I’m afraid, usually rather low — good for my server, bad for you. If you test the demo locally it will give you more interesting figures. b) IE6 isn’t supported in any of this article.

Possible Applications #

A few simple examples of applications that could make use of Server-Sent Events:

  • A real-time chart of streaming stock prices
  • Real-time news coverage of an important event (posting links, tweets, and images)
  • A live Twitter wall fed by Twitter’s streaming API
  • A monitor for server statistics like uptime, health, and running processes

We’ll use the server monitor for this article’s examples. If this application were to be used in the wild, we could also check the EventSource‘s connection state to indicate when there’s a potential problem connecting to the server.

Overview of the API #

The client-side API is rather simple, and it hands-down beats the insane hacks required to get real-time events to the browser back in the bad old days.

The main points of interest:

  • new EventSource(url) — this creates our EventSource object, which immediately starts listening for events on the given URL.
  • readyState — as with XHR, we have a readyState for the EventSource that tells us if we’re connecting (0), open (1), or closed (2).
  • onopen, onmessage — two events that we can listen for on the new EventSource object. By default, the message event will fire when new messages are received, unless the server explicitly sets the event type.
  • addEventListener — not only can we listen for the default message event, but we can also listen for custom messages using the addEventListener on the EventSource object, just as if we were listening for a click event.
  • event.data — as with most messaging APIs, the contents of the message reside in the data property of the event object. This is a string, so if we want to pass around an object, we need to encode and decode it with JSON.
  • close — closes the connection from the client side.

In the future, EventSource will also support CORS using an options argument to the EventSource object: { withCredentials: true }. But at the time of writing, no stable release includes this property.

Simple Example #

Our simple web app will notify us of server status messages — things like the load average, number of currently connected users, and most CPU-intensive processes. If I were using this application in anger, I’d probably build server modules that emit specific event types when they cross specific thresholds, so that I’m only notified when something gets to warning level.

This snippet of JavaScript connects to our server, listens for messages, and handles the data that comes with the messages:

var source = new EventSource('/stats');
source.onopen = function () {
  connectionOpen(true);
};

source.onerror = function () {
  connectionOpen(false);
};

source.addEventListener('connections', updateConnections, false);
source.addEventListener('requests', updateRequests, false);
source.addEventListener('uptime', updateUptime, false);

source.onmessage = function (event) {
  // a message without a type was fired
};

Properties of Server-Sent Events #

Server-Sent Events are more than just a one-way web socket. They have some unique features:

  • The connection stream is from the server and read-only. This suits lots of applications, some examples of which I listed above.
  • They use regular HTTP requests for the persistent connection, not a special protocol, which means we can polyfill using vanilla JavaScript.
  • If the connection drops, the EventSource fires an error event and automatically tries to reconnect. The server can also control the timeout before the client tries to reconnect.
  • Clients can send a unique ID with messages. When a client tries to reconnect after a dropped connection, it will send the last known ID. Then the server can see that the client missed n messages and send the backlog of missed messages on reconnect.

Message Format #

A simple message doesn’t require much:

data: this is a simple message
<blank line>

Note that the end of a message is indicated by a blank line (obviously not the literal characters <blank line>).

For a message with multiple lines:

data: this is line one
data: and this is line two

You can send message IDs to be used if the connection is dropped:

id: 33
data: this is line one
data: this is line two

You can even send multiple messages in a single response so long as you separate the messages by blank lines:

id: 34
data: Remy is awesome

id: 35
data: Bruce is stinky

And you can specify your own event types (the above messages will all trigger the message event):

id: 36
event: price
data: 103.34

id: 37
event: news
data: Bruce sells his collection of replica bananas

You don’t have to worry about this structure on the client side. It only applies to the server, which I’ll touch on next.

Typical Server #

I’m not going to give a full walkthrough of the server-side code, since this is an HTML5 web site spacer But there are a few important and simple features that you need to know to build the server (you’ll need this part anyway if you’re going to use EventSource).

I’ve included all the files for this demo on GitHub for you to peruse at your own leisure, and I’ve also deployed a live example of this code.

Ideally, you should use a server that has an event loop. This means you should not use Apache, but instead use a platform such as Node.js (which I’ve used) or Twisted for Python.

Key properties:

  1. You can only accept EventSource requests if the HTTP request says it can accept the event-stream MIME type.
  2. You need to maintain a list of all the connected users in order to emit new events.
  3. You should listen for dropped connections and remove them from the list of connected users.
  4. You should optionally maintain a history of messages so that reconnecting clients can catch up on missed messages.

Here’s a sample of my Node.js based server. It’s using Connect (a simple webby framework for Node). When it receives a request for /stats, it calls the following function. I’ve commented the code so you can follow along:

function stats(request, response) {
  // only response to an event-stream if the request 
  // actually accepts an event-stream
  if (request.headers.accept == 'text/event-stream') {

    // send the header to tell the client that we're going
    // to be streaming content down the connection
    response.writeHead(200, {
      'content-type': 'text/event-stream',
      'cache-control': 'no-cache',
      'connection': 'keep-alive'
    });

    // support the polyfill - we'll come on to this later
    if (request.headers['x-requested-with'] == 'XMLHttpRequest') {
      response.xhr = null;
    }

    // if there was a lastEventId sent in the header, send
    // the history of messages we've already stored up
    if (request.headers['last-event-id']) {
      var id = parseInt(request.headers['last-event-id']);
      for (var i = 0; i < history.length; i++) {
        if (history[i].id >= id) {
          sendSSE(response, history[i].id, history[i].event, history[i].message);
        }
      }
    } else {
      // if the client didn't send a lastEventId, it's the
      // first time they've come to our service, so send an
      // initial empty message with no id - this will reset
      // their id counter too.
      response.write('id\n\n');
    }

    // cache their connection - the response is where we write
    // to send messages
    connections.push(response);

    // send a broadcast message to all connected clients with
    // the total number of connections we have.
    broadcast('connections', connections.length);

    // if the connection closes to the client, remove them
    // from the connections array.
    request.on('close', function () {
      removeConnection(response);
    });
  } else {
    // if the client doesn't accept event-stream mime type,
    // send them the regular index.html page - you could do
    // anything here, including sending the client an error.
    response.writeHead(302, { location: "/index.html" });
    response.end();
  }
}

The important trick on the server is to ensure you don’t close the connection to the EventSource object. If you do, it will of course handle the closed connection, fire an error event, and try to reconnect. So you’ll just want to maintain the persistent connection.

Polyfills and Tweaks to the Server #

There are two good polyfills I know of, and though I prefer the one I wrote, I’ll still lay them both out for you.

Yaffle’s Polyfill #

The first is one by Yaffle (on github), available on github/Yaffle/eventsource.

The cons:

  • It doesn’t send the accepts header, and
  • It completely replaces the EventSource object, so even if your browser supports EventSource natively, this script will replace it. But there’s a good reason for that.

The pros:

  • It maintains a persistent connection (whereas the one we’re using doesn’t), and
  • More interestingly, it supports CORS (which I imagine is why it replaces the native EventSource).

These two pros are quite compelling. But when I was testing, I couldn’t get it working in IE7 (which was my minimum browser target), so that might be a blocker for you…or not.

Remy’s Polyfill #

The second is my own, available on github.com/remy/polyfills.

The cons:

  • It uses polling, so once a small group of messages come down, it re-establishes the connection, which could lead to significant overhead (though less so on a Node-based server). You have to add about 4 extra lines to your server code.

The pros:

  • It doesn’t replace the native EventSource object (but that also implies that, for now, it won’t support CORS), and
  • It supports IE7.

Retrospectively, I might choose Yaffle’s polyfill over mine in the future if I wasn’t bothered about IE7 support.

Using the Polyfill #

By including the EventSource.js JavaScript library before my client-side code, I just need a couple of small changes to my server-side code. Other than that, everything on the client side works without any changes (as a polyfill should work).

When posting the server’s reply to the client, instead of keeping the connection open, I include the following in my server when it’s finished writing the response:

// send the data (event type, id, message, etc)
response.write(data);

// if our response contains our custom xhr property, then...
if (response.hasOwnProperty('xhr')) {
  // clear any previous timers using the xhr prop as the value
  clearTimeout(response.xhr);

  // now set a timer for 1/4 second (abritrary number) that
  // then closes the connection and removes itself from the
  // connection array.
  // The delay is in place so that a burst of messages can 
  // go out on the same connection *before* it's closed.
  response.xhr = setTimeout(function () {
    response.end();
    removeConnection(response);
  }, 250);
}

Bugs? #

The error event should always fire when the readyState changes, assuming you didn’t explicitly call the close method. This works nearly all the time, but in writing this article, I found a few edge cases where it doesn’t fire. In Chrome, if I put my machine to sleep and then woke it back up, it would close the connection but not fire the event, therefore never triggering a reconnection. As I said, this is an edge case and I’ll file a bug against it, so I don’t expect it to hang around for long.

Why Not Use WebSockets? #

There are two reasons I’d advocate using EventSource over WebSockets, as they’re currently the two contenders for sending real-time events to the browser.

The first is that EventSource (as we saw earlier) works over regular HTTP and can therefore be replicated entirely using JavaScript if it’s not available natively. That means that we can polyfill browsers without support, like IE9.

The second is probably more important: you should always use the right technology for the job. If your real-time data is sourced from your web site, and the user doesn’t interact in real-time, it’s likely you need Server-Sent Events.

I recently saw a cool demo of snowflakes drifting down a web site. Each snowflake is a tweet based around the Christmas theme — like if someone mentions a particular Christmas-y term, it’s sucked in to the snowflake. Don’t get me wrong, I know this is a demo, and it’s very cool (if you wrote it, this is me sending you hugs), but it’s based on WebSockets. I’d suggest this demo should be based on EventSource since all the data is read-only and the user doesn’t interact with it at all.

The point: evaluate the technology against your problem, and aim to get good fit.

22 Responses on the article “Server-Sent Events”

Motyar saysspacer

Great!! HTML5 is really nice, I can see a lot of possibilities.
Not sure about few things. see this link says:-

On the server-side, the script (“updates.cgi” in this case) sends messages in the following form, with the text/event-stream MIME type:
data: This is the first message.

Not sure How to write the API to “send” data, is it possible with PHP.

Help needed!!

Remy Sharp saysspacer

@Motyar – it’s pretty straight forward to create a server with PHP, but you have to keep in mind that if PHP connection remains open (and you’re running a regular LAMP setup) then your server will eventually exhaust all of it’s connections.

That aside, a simple PHP server can be seen over at Google’s HTML5 Rocks resource: www.html5rocks.com/en/tutorials/eventsource/basics/#toc-server-code

Another important thing to note – which kinda defeats the points – is that apache and PHP will *close* the connection after the request. EventSource has no problem with this and re-opens the connection – but you’re effectively switching to Ajax polling because you’re using the wrong server side tech.

Hope that helps.

Motyar saysspacer

Thanks @remy!!

BigAB saysspacer

I came for the strange things Remy and Bruce say:

If I were using this application in anger…
Bruce sells his collection of replica bananas

…I left satisfied.

porneL saysspacer

Whys:

* it’s much simpler to implement and debug. Protocol is dead simple and it’s all text. You don’t need huge server-side library or 3rd party service, you can just print the lines yourself.

* it’s faster to connect (it’s just a regular HTTP request. No handshake or protocol negotiation) and protocol has less overhead (messages are not divided into frames/packets)

* it’s HTTP-compatible, so it works with standard proxies (including nginx, Varnish, etc.), “corporate firewalls”, etc. Most 3G mobile networks force proxies on users, and WebSockets won’t work there without SSL.

* browser handles resuming and error recovery automatically for you (and browser can do it well with awareness of the network interface, etc). That’s a surprisingly hard thing to get right from JS, and SSE gives that with 0 lines of code.

* it can switch between permanent connection and polling at any time, so you can change strategy depending on your server load (e.g. if you run into connection limit, just drop some clients, they’ll reconnect in couple of seconds).

Mr C saysspacer

you should not use Apache, but instead use a platform such as Node.js

if you are already forced down the route of node.js, you’d be daft not to use socket.io (which supports older browsers right out of the box). three lines of code and you have yourself a websocket server with ajax polling as a fallback.

seems like SSE is a solution looking for a problem that someone else solved already.

Remy Sharp saysspacer

@Mr C – node is the example server I chose, you can use Juggernaut for Ruby, Twisted for python or Jakarta (I *think*) for Java.

Equally I’d encourage you to consider the problem before just throwing Socket.IO at it. If you want bi-directional – then WebSockets and Socket.IO is perfect.

If you want one way, Server Sent Events are perfect too. As you pointed out Socket.IO defers to polling. SSE is *not* polling by default – it automatically switches to polling if the connection is dropped. @porneL points out some excellent pros in favour of SSE too.

But I’ll say it again: consider the problem, then apply the right solution to it. If SSE is being twisted to fit your problem – then clearly you’re going down the wrong avenue – and vice versa.

Mr C saysspacer

If you want one way, Server Sent Events are perfect too

sure feels like momentum is elsewhere though – twitter returns links to this article but little else. you yourself were writing about websockets two years ago and SSE was available back then too.

Remy Sharp saysspacer

@Mr C – I don’t hold favour over either technology. I think both a very exciting. For example, as I write, I’m working on a project that actually need sever sent events – but because I must go cross domain, and using Ajax + brs + postMessage + SSE is quite a long rope to jump, I’m electing to use WebSockets.

It all goes back to the problem and the right fit for a solution.

Kai Hendry saysspacer

Last time I checked, SSE didn’t work on Android, so I was left with having to use socket.io IIRC. But I’m checking the demo on my Nexus S 2.3.6 and it works. Wow. spacer Perhaps I was confused with another browser’s issue.

Be better if it plotted connected clients overtime instead of load. spacer

I wish node was easier to host. I really don’t like it not running off :80, purely because it’s an eyesore. How do you manage the nodejs app.js process? In a /etc/init.d script? Upstart?

Renting a VPS purely to run node sounds crazy.

Wondering why you used connect over express.

Nonetheless, great stuff. Keep it up!!

Zakaria saysspacer

Is there changes in web browser support since your last year article? (Opera 11, Safari, Mobile Safari and Chrome)

Nick Payne saysspacer

Fascinating stuff – as the author of said snowflake demo to whom you’re sending hugs, I have to hold my hands up and admit that this is the first time I’ve ever even heard of SSE. Definitely something to investigate further for those occasions where bidirectional support isn’t needed (I was going to say something like “though I can’t imagine many use-cases for that”, but that would sound a bit daft given my demo spacer .

Nick

Remy Sharp saysspacer

@Kai Hendry – SSE isn’t supported in Android 2.2 IIRC. But that was the reason why I could polyfill in using JavaScript and keep the properties of SSE rather than switching to polling. Yaffle’s polyfill might even be a better choice since it manages to retain the connection entirely whereas mine uses long polling.

Also you mention the port number. Node *does* run on port 80, it just happens that the server I’m pointing you to is running multiple applications on it so I have them spread across multiple ports. It’s very easy to proxy in Node too (I just didn’t want to register a new domain just for this example). Here’s some examples I’ve written that run on port 80: jsconsole.com testyoursockets.com (sockets currently broken!) inliner.leftlogic.com and so on.

Check out free hosting solutions like Heroku, nodester and the like.

Remy Sharp saysspacer

@Nick Payne – sorry for pointing you out – but I figured the pingback should (hopefully) get your attention that I’m sending you hugs for the code spacer

I was in the exact same position – I discovered native real-time tech a few years ago in the form of WebSockets, then over time discovered Server-Sent Events – and as I keep banging on, it’s using the right tool for the job. Anyway – nice work spacer

Nick Payne saysspacer

@Remy not a problem at all – no apology required, and thanks for the mention (and the article!)

Alice Wonder saysspacer

I would appreciate some feedback as to whether or not server side events is the proper tool to what I am trying to accomplish.

I have not written a single line of code yet, and I probably won’t for at least a few weeks, I have other unrelated stuff to button up first. But I’m thinking about it now.

My server environment is CentOS 6.x running Apache, PostgreSQL, PHP (custom build, not stock) – I would not have any problems installing a different than apache http server to handle the SSE part, in fact, it might be nice to finally play with node.js. For my application, I don’t think database is going to be needed.

What I am trying to build is a group chat client. Many would see that as bi-directional and thus web sockets, but my idea is to push the chat to the requesting clients regardless of whether or not they have participated or even picked a user name to participate with.

You visit the chat page and start seeing responses as they happen from anyone in the room. You wish to participate, you choose a name and XMLHttpRequest checks it’s availability and if available your php session updated and you are good to go, if you choose to participate anything you write is sent by XMLHttpRequest and then shows up (for you and anyone else) when the server sends it as an SSE.

I’d probably use php under apache to handle the username/login aspect and receive the XMLHttpRequest for clients sending the event but use node.js to send the chat posts as SSE.

Am I barking up the wrong tree wanting to use SSE for this?

Kai Hendry saysspacer

@Alice AFAIK Apache and Node don’t place nicely, so if I were you I would dive into Socket IO and build your app out from there.

Alice Bevan-McGregor saysspacer

I prefer the Nginx Push Module (profile link). It’s more of a long-poll similar to IMAP IDLE, with a protocol specification that’s extremely clear on alternate implementations. E.g. I have one that integrates with my own C10K, HTTP/1.1 compliant, pure-Python web server.

https://github.com/slact/nginx_http_push_module/blob/master/protocol.txt is the protocol RFC, less than 200 lines.

https://github.com/slact/nginx_http_push_module/issues/43#issuecomment-3421412 describes one of the ways I’m using it.

Alice Bevan-McGregor saysspacer

@Kai You’re assuming I’m using Apache (nope, Nginx everywhere), Node (nope, Python everywhere), and need bidirectional low-latency communication (nope, could care less about client->server latency), and that my appS aren’t already stable and in production with their current Nginx/Python/Push architecture working great.

Thanks for the suggestion, though! spacer

Alice Wonder saysspacer

@KAI – My plan is to use Apache for everything EXCEPT the server sent events. Those would be sent by node.js either running on a different port or a different subdomain.

Apache with php would manage everything else, including users sending messages to the group chat via XMLHttpRequest (parsing them for smileys, expanding shortened URLs like bit.ly, etc.) and then when ready to be sent to everyone connected, it would put the message in a cue to be eaten by the node.js server that sends them as a SSE.

I could just do the whole thing in node.js but I prefer php programming and want the ability to have chat name match the username associated with the php session that is being used to access other parts of the web site. Using Apache/PHP for everything except sending of the SSE makes sense to me. Assuming SSE is the appropriate tech for plugin-less group chat.

Alice Bevan-McGregor saysspacer

Too many Alice’s here! spacer

dhanesh mane saysspacer

hey thanks a lot, its really very nice article.

Join the discussion.

Click here to cancel reply.

Some HTML is ok

You can use these tags:
<a href="" title="">
<abbr title="">
<b>
<blockquote cite="">
<cite>
<del datetime="">
<em>
<i>
<q cite="">
<strong>

You can also use <code>, and remember to use &lt; and &gt; for brackets.