Jugglite

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.

Installation

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 use jugglite as rack middleware in development and as a standalone binary behind nginx in production.

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 0.0.0.0 --port 3000 --max-conns 1024

As Rack middleware (development)

Add it to your config.ru 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 (production)

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;
  server_name example.com;
  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
    console.log(e.data);
    // If you JSON encoded the message
    msg = jQuery.parseJSON(e.data);
    }, 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.new
redis.publish('yourchannelname', 'This is a message')
# You may want to JSON encode your data
redis.publish('yourchannelname', {hello: 'world', number: 47}.to_json)

Performance

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

Contributing

  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

License

Jugglite is licensed under the MIT license.