This is the first part of a series about our newest feature: the live debugger! Here is how we used the new Server-Sent Events standard to implement the real-time stream that helps you debug all of the calls going through our servers.

This is the first article in a series about how we built Debugger. To try it out, log in to segment.com, go to a project of yours and choose Debugger.

debugger

Why another spec?

In the web we unfortunately don’t have as many options for streaming network calls as with native apps, where you’d most likely just do raw tcp or udp.

The commonly used techniques are:

  • Polling Ajax / JSONP
  • Websockets
  • Flash
  • Server-Sent Events (SSE)
  • (Gifsockets ;))

Each have their pros and cons, and we could have successfully implemented Debugger with any of those. However many have big downsides, like polling creating too many connections, websockets requiring an HTTP upgrade and Flash being proprietary technology.

In the end we chose SSE because it’s the most simple and straight forward approach, both in concept and implementation, and does just what we need.

Server-Sent Events

I first learned about SSE from @konstantinhaase, who explained to me how Travis CI is using them for their live build updates. I was arguing why not to just use WebSockets, but in comparison WebSockets are a complex monstrosity and only shine when you need full 2 way communication.

All there is to SSE is the browser makes an HTTP request to an HTTP server which responds with lines prefixed by data: and delimited by \n\n. Boosh! There’s no weird HTTP upgrade, the server side can be dead simple and since it’s just HTTP it plays well with your existing stack of firewalls, webservers etc.

Unlike with polling you do still get persistent connections, so if you do auth logic on every request you only have to do that once per client.

An example SSE endpoint would respond like this:

$ curl http://example.com/dates
data: 1394572346452

data: 1394572347457

data: 1394572348463

^C

It’s so simple you’ll wonder why SSE are still so little known.

The code to listen on the client is:

var source = new EventSource('/dates');
source.onmessage = function(e){
  console.log(e.data);
};
// or source.addEventListener('message', function(e){})

And here’s a server written in node.js, writing the current date every second. Don’t forget to set the Content-Type header to text/event-stream.

var http = require('http');

var server = http.createServer(function(req, res){
  if (req.url != '/events') return res.end();
  res.writeHead(200, { 'Content-Type': 'text/event-stream' });
  var id = setInterval(function(){
    res.write('data: ' + Date.now() + '\n\n');
  }, 1000);
  req.on('end', function(){
    clearInterval(id);
  });
});

server.listen(3000);

Advantages

The biggest advantage is how lightweight SSE are: It’s raw http and you can make it work in a few lines of code without using any libraries. For legacy browser support there are polyfills that work with pure JS.

Browser support

None of the Internet Explorers support SSE yet, and mobile Opera and Android don’t either. However, all recent versions of Firefox, Chrome, Safari and Opera do.

For the rest, there’s multiple polyfills. There’s one by Yaffle that’s smart about reconnecting and supports CORS, but also requires a modification to the server. The one by remy seems popular as well and is more lightweight but at the same time less robust.

Tuning reconnections

EventSources reconnect by default, but you can tune the reconnection behavior based on your app’s requirements, stack and expected clients:

  • Send a line of retry: <milliseconds> to configure the client’s reconnection time.
  • Send a line of id: <id> before a data line to associate an unique id with the event. The browser will keep track of that id and in case of a reconnection will add Last-Event-ID as an HTTP header to let the server be smart about what to send the client straight away. For example, in a chat scenario you would send all the messages that happened since the last message the client has seen.

Custom event names

In case you’re sending different kinds of events, like a room join and a message event, SSE let’s you differentiate those in the stream. Send a line of event: <name>\n before a data line to let the EventSource know that’s what you’d like to call that event.

For example:

event: poke
data: "hey!"

Will trigger:

source.addEventListener('poke', function(e){
  console.log('poke: ' + e.data);
});

Deployment

Some software has problems with long living connections, here’s what we had to tweak in our stack:

  • Add the proxy_http_version 1.1; directive to our nginx proxy so it doesn’t close connections too early
  • Send heartbeats every 30s because Amazon ELB kills idle connections after 60s

Helpful libraries

The segmentio/sse component is a nice wrapper for client side subscriptions. Install with component(1).

var sse = require('sse');

sse('/events', function(data){
  // ...
});

If your data source on the server side is a node stream, there’s the segmentio/sse-stream node module, a transform stream that converts to the SSE format:

var sse = require('sse-stream');

http.createServer(function(req, res){
  dateStream().pipe(sse()).pipe(res);
});

In the next installment of this series we’ll look at how live debugger is built upon generators and koa for fun and profit. Stay tuned!