Server-Sent Events: The simplest realtime browser spec
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.
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.
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 nn
. 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 text/event-stream
.
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.
EventSource
s 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.
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:
Will trigger:
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
The segmentio/sse component is a nice wrapper for client side subscriptions. Install with component(1).
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:
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!
Our annual look at how attitudes, preferences, and experiences with personalization have evolved over the past year.