Lightweight Server Sent Events server
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Failed to load latest commit information.


Build Status

Jugglite is a replacement for the incredible Juggernaut by Maccman. It uses Server Sent Events to push events from your application to the client's browser. It uses Redis for publish/subscribe and Thin + EventMachine to run an evented server that can handle 10K+ concurrent connections.


Add this line to your application's Gemfile:

gem 'jugglite'

And then execute:

$ bundle

Or install it yourself as:

$ gem install jugglite

Server Usage

I used to use Jugglite as rack middleware in development and as a standalone binary behind nginx in production. Nowadays I run my rails application using thin in production so I can mount Jugglite in the routes.rb file.

Inside Rails's routes.rb

This only works with an EventMachine based webserver that supports rack's async.callback. I have only tested this in production with Thin, but it might work with Rainbows or Puma.

This setup is great because it allows you to do channel authorization on a per request basis.

# config/routes.rb
# ...

@allowed_channels = ->(request) {
  user_id = request.session['user_id']
  user_id ? ['broadcast', "player_#{user_id}"] : []

@on_register = ->(connection) {
  # Store something in
  # connection.request holds the Rack::Request

@on_unregister = ->(connection) {
  # Called when the connection is dropped or closed.
  # You can access the set in the +on_register+ call
  # Or use the connect.request.

get 'stream', to:, namespace: "app:#{Rails.env}:", allowed_channels: @allowed_channels, on_register: @on_register, on_unregister: @on_unregister)

# ...

Stand-alone binary

Jugglite comes with a binary. This binary runs a thin server that listens on redis for application messages and passes it along to all connected clients.

You can run the binary from any terminal like this (these options are the defaults):

jugglite --address --port 3000 --max-conns 1024

As Rack middleware

Add it to your file and make sure your application runs using Thin:

require ::File.expand_path('../config/environment',  __FILE__)
# Embed Jugglite when running in development
use Jugglite::App, path: '/stream', namespace: 'myapp:' if ENV['RACK_ENV'] == 'development'
run MyRails::Application

Behind Nginx

NOTE: because the html5 SSE implementation requires the connection to have the same hostname and port, you'll need to add a reverse proxy in front of your app and jugglite.

This is an example nginx configuration with Unicorn and Jugglite. Make sure you set proxy_buffering off; in your Nginx configuration.

# Start jugglite with: jugglite --socket /tmp/jugglite.sock
upstream jugglite-example {
  server unix:/tmp/jugglite.sock fail_timeout=0;

# Start unicorn with: unicorn --listen /tmp/unicorn.sock --config-file unicorn_conf.rb
upstream unicorn-example {
  server unix:/tmp/unicorn.sock fail_timeout=0;

server {
  listen [::]:80 deferred;
  root /var/www/example/current/public;

  # Let Nginx serve assets statically
  location ^~ /assets/ {
    gzip_static on;
    expires max;
    add_header Cache-Control public;

  # Forward /stream to Jugglite and set proxy_buffering off
  location ^~ /stream {
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $http_host;
    proxy_redirect off;
    proxy_buffering off;
    proxy_pass http://jugglite-example;

  # Forward all other requests to Unicorn
  try_files $uri/index.html $uri @unicorn;
  location @unicorn {
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $http_host;
    proxy_redirect off;
    proxy_pass http://unicorn-example;

Client Usage

Use the browser's native Server-Sent Events implementation:

  es = new EventSource('/stream?channel=yourchannelname');

  es.addEventListener('message', function(e) {
    // Do something with the data
    // If you JSON encoded the message
    msg = jQuery.parseJSON(;
    }, false);

  es.onopen = function(e) {
    // Connection was opened.

  es.onerror = function(e) {
    if (e.readyState == EventSource.CLOSED) {
      // Connection was closed.
    } else {
      // Some other error?

To support older browsers, use Remy's excellent Pollyfill. It does revert to ajax long polling for browsers without a native EventSource implementation. Supports almost every old browser (even IE7).

Sending messages

Use your favorite Redis client to simply publish messages to the channel your clients are subscribing to:

redis =
redis.publish('yourchannelname', 'This is a message')
# You may want to JSON encode your data
redis.publish('yourchannelname', {hello: 'world', number: 47}.to_json)


It's been tested on a local machine with the spec/benchmark/max_connections.rb spec up to 16K concurrent connections.


  1. Fork it
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create new Pull Request


Jugglite is licensed under the MIT license.