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.
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
Server-Sent Events (SSE)
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.
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:
It’s so simple you’ll wonder why SSE are still so little known.
The code to listen on the client is:
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
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.
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.
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
dataline 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-IDas 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
messageevent, 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.
Some software has problems with long living connections, here’s what we had to tweak in our stack:
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
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: