Killing a library
A programming library's main mission should be to make itself redundant, and with that in mind that I'm announcing the deprecation of Juggernaut.
Juggernaut was one of the first solutions to pushing data in realtime to clients; at first it was using Flash XMLSockets, and then it moved to WebSockets when they were widely available. This sort of technology has lots of interesting applications, such as chat, realtime collaboration and games.
It was also my first open source library, written back when I was still in high school. At the time, it had a fair amount of press and gained some popularity. Indeed releasing Juggernaut led directly to my first job, dropping out of school and moving to London.
So why am I killing Juggernaut? Well, it's because of the little known HTML5 Server-Sent Events (SSEs). This is a browser API that lets you keep open a socket to your server, subscribing to a stream of updates. It's perfect for the use-case Juggernaut was created for, and what's more can be implemented in a few lines of Ruby. In other words, it makes Juggernaut completely redundant.
On the client side, all that is required is instantiation of a EventSource
object, which you can pass an endpoint. For example:
var source = new EventSource('/stream');
source.addEventListener('message', function(e){
console.log('Received a message:', e.data);
});
When the stream connects, the open event is triggered, and whenever a new message is received, the message event triggers.
Sinatra actually includes an example chat using Server-Sent events. Consuming EventSource
connections is as simple as responding to clients with a text/event-stream
content type and keeping the connection open:
get '/stream', :provides => 'text/event-stream' do
stream :keep_open do |out|
connections << out
out.callback { connections.delete(out) }
end
end
Transmitting messages to clients is just as straightforward. Frankly, I wish the people behind WebSockets overly-complex protocol had taken a similar approach.
post '/' do
connections.each { |out| out << "data: #{params[:msg]}\n\n" }
204 # response without entity body
end
In other words, all messages start with data:
, then the message body and finally a double new line. In addition to data messages, there are a variety of other supported ones, such as a retry timeout and setting an event name. See the specification for more details.
Browser support for SSEs is actually pretty good. Chrome, Safari, Firefox and Opera all support them, with IE being the usual black sheep of the family. However, there are a variety of polyfills out there, so adding support for older browsers is trivial.
Juggernaut seamlessly integrated into backends like Rails and Flask using Redis' pubsub, and we can do the same integration with SSEs. Here's an example of transmitting all events on a pubsub channel to clients, and here's one where clients can subscribe to specific private channels.
So six years after Juggernaut was first released, it has now been made redundant by functionality that is baked into browsers. What's more, SSE support can be implemented in a couple of lines of Ruby. These are exciting times indeed!