• Stars
    star
    1,626
  • Rank 28,772 (Top 0.6 %)
  • Language
    JavaScript
  • License
    MIT License
  • Created about 14 years ago
  • Updated over 12 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

[DEPRECATED] Realtime server push with node.js, WebSockets and Comet

#Juggernaut

Juggernaut has been deprecated! Read why here.

Juggernaut gives you a realtime connection between your servers and client browsers. You can literally push data to clients using your web application, which lets you do awesome things like multiplayer gaming, chat, group collaboration and more.

Juggernaut is built on top of Node.js and is super simple and easy to get going.

##Features

  • Node.js server
  • Ruby client
  • Supports the following protocols:
    • WebSocket
    • Adobe Flash Socket
    • ActiveX HTMLFile (IE)
    • Server-Sent Events (Opera)
    • XHR with multipart encoding
    • XHR with long-polling
  • Horizontal scaling
  • Reconnection support
  • SSL support

As you can see, Juggernaut supports a variety of protocols. If one isn't supported by a client, Juggernaut will fallback to one that is.

Supported browsers are:

  • Desktop
    • Internet Explorer >= 5.5
    • Safari >= 3
    • Google Chrome >= 4
    • Firefox >= 3
    • Opera 10.61
  • Mobile
    • iPhone Safari
    • iPad Safari
    • Android WebKit
    • WebOs WebKit

##Requirements

  • Node.js
  • Redis
  • Ruby (optional)

##Setup

###Install Node.js

If you're using the Brew package management system, use that:

brew install node

Or follow the Node build instructions

###Install Redis

If you're using the Brew package, use that:

brew install redis

Or follow the Redis build instructions

###Install Juggernaut

Juggernaut is distributed by npm, you'll need to install that first if you haven't already.

npm install -g juggernaut

###Install the Juggernaut client gem

This step is optional, but if you're planning on using Juggernaut with Ruby, you'll need the gem.

gem install juggernaut

##Running

Start Redis:

redis-server

Start Juggernaut:

juggernaut

That's it! Now go to http://localhost:8080 to see Juggernaut in action.

##Basic usage

Everything in Juggernaut is done within the context of a channel. JavaScript clients can subscribe to a channel which your server can publish to. First, we need to include Juggernaut's application.js file. By default, Juggernaut is hosted on port 8080 - so we can just link to the file there.

<script src="http://localhost:8080/application.js" type="text/javascript" charset="utf-8"></script>

We then need to instantiate the Juggernaut object and subscribe to the channel. As you can see, subscribe takes two arguments, the channel name and a callback.

<script type="text/javascript" charset="utf-8">
  var jug = new Juggernaut;
  jug.subscribe("channel1", function(data){
    console.log("Got data: " + data);
  });
</script>

That's it for the client side. Now, to publish to the channel we'll write some Ruby:

require "juggernaut"
Juggernaut.publish("channel1", "Some data")

You should see the data we sent appear instantly in the open browser window. As well as strings, we can even pass objects, like so:

Juggernaut.publish("channel1", {:some => "data"})

The publish method also takes an array of channels, in case you want to send a message to multiple channels co-currently.

Juggernaut.publish(["channel1", "channel2"], ["foo", "bar"])

That's pretty much the gist of it, the two methods - publish and subscribe. Couldn't be easier than that!

##Flash

Adobe Flash is optional, but it's the default fallback for a lot of browsers until WebSockets are supported. However, Flash needs a XML policy file to be served from port 843, which is restricted. You'll need to run Juggernaut with root privileges in order to open that port.

sudo juggernaut

You'll also need to specify the location of WebSocketMain.swf. Either copy this file (from Juggernaut's public directory) to the root public directory of your application, or specify it's location before instantiating Juggernaut:

window.WEB_SOCKET_SWF_LOCATION = "http://juggaddress:8080/WebSocketMain.swf"

As I mentioned above, using Flash with Juggernaut is optional - you don't have to run the server with root privileges. If Flash isn't available, Juggernaut will use WebSockets (the default), Comet or polling.

##SSL

Juggernaut has SSL support! To activate, just put create a folder called 'keys' in the 'juggernaut' directory, containing your privatekey.pem and certificate.pem files.

>> mkdir keys
>> cd keys
>> openssl genrsa -out privatekey.pem 1024 
>> openssl req -new -key privatekey.pem -out certrequest.csr 
>> openssl x509 -req -in certrequest.csr -signkey privatekey.pem -out certificate.pem

Then, pass the secure option when instantiating Juggernaut in JavaScript:

var juggernaut = new Juggernaut({secure: true})

All Juggernaut's communication will now be encrypted by SSL.

##Scaling

The only centralised (i.e. potential bottle neck) part to Juggernaut is Redis. Redis can support hundreds of thousands writes a second, so it's unlikely that will be an issue.

Scaling is just a case of starting up more Juggernaut Node servers, all sharing the same Redis instance. Put a TCP load balancer in front them, distribute clients with a Round Robin approach, and use sticky sessions.

It's worth noting that the latest WebSocket specification breaks support for a lot of HTTP load balancers, so it's safer just using a TCP one.

##Client Events

Juggernaut's JavaScript client has a few events that you can bind to:

  • connect
  • disconnect
  • reconnect

Juggernaut also triggers data events in the context of an channel. You can bind to that event by just passing a callback to the subscribe function. Here's an example of event binding. We're using jQuery UI to show a popup when the client loses their connection to our server.

var jug = new Juggernaut;

var offline = $("<div></div>")
	.html("The connection has been disconnected! <br /> " + 
	      "Please go back online to use this service.")
	.dialog({
		autoOpen: false,
		modal:    true,
		width:    330,
		resizable: false,
		closeOnEscape: false,
		title: "Connection"
	});

jug.on("connect", function(){ 
  offline.dialog("close");
});

jug.on("disconnect", function(){ 
  offline.dialog("open");
});

// Once we call subscribe, Juggernaut tries to connnect.
jug.subscribe("channel1", function(data){
  console.log("Got data: " + data);
});

##Excluding certain clients

It's a common use case to send messages to every client, except one. For example, this is a common chat scenario:

  • User creates chat message
  • User's client appends the message to the chat log, so the user sees it instantly
  • User's client sends an AJAX request to the server, notifying it of the new chat message
  • The server then publishes the chat message to all relevant clients

Now, the issue above is if the server publishes the chat message back to the original client. In which case, it would get duplicated in the chat logs (as it already been created). We can resolve this issue by recording the client's Juggernaut ID, and then passing it as an :except option when Juggernaut publishes.

You can pass the Juggernaut session ID along with any AJAX requests by hooking into beforeSend, which is triggered by jQuery before sending any AJAX requests. The callback is passed an XMLHttpRequest, which we can use to set a custom header specifying the session ID.

var jug = new Juggernaut;

jQuery.beforeSend(function(xhr){
  xhr.setRequestHeader("X-Session-ID", jug.sessionID);
});

Now, when we publish to a channel, we can pass the :except option, with the current client's session ID.

Juggernaut.publish(
  "/chat",
  params[:body],
  :except => request.headers["X-Session-ID"]
)

Now, the original client won't get the duplicated chat message, even if it's subscribed to the /chat channel.

##Server Events

When a client connects & disconnects, Juggernaut triggers a callback. You can listen to these callbacks from the Ruby client,

Juggernaut.subscribe do |event, data|
  # Use event/data
end

The event is either :subscribe or :unsubscribe. The data variable is just a hash of the client details:

{"channel" => "channel1", "session_id" => "1822913980577141", "meta" => "foo"}

##Metadata

You'll notice there's a meta attribute in the server event example above. Juggernaut lets you attach meta data to the client object, which gets passed along to any server events. For example, you could set User ID meta data - then you would know which user was subscribing/unsubscribing to channels. You could use this information to build a live Roster of online users.

var jug = new Juggernaut;
jug.meta = {user_id: 1};

##Using Juggernaut from Python

You don't have to use Ruby to communicate with Juggernaut. In fact, all that is needed is a Redis adapter. Here we're using Python with redis-py.

import redis
import json

msg = {
  "channels": ["channel1"],
  "data": "foo"
}

r = redis.Redis()
r.publish("juggernaut", json.dumps(msg))

##Using Juggernaut from Node.js

Similar to the Python example, we can use a Node.js Redis adapter to publish to Juggernaut.

var redis   = require("redis");

var msg = {
  "channels": ["channel1"],
  "data": "foo"
};

var client = redis.createClient();
client.publish("juggernaut", JSON.stringify(msg));

##Building a Roster

So, let's take all we've learnt about Juggernaut, and apply it to something practical - a live chat roster. Here's the basic class. We're using SuperModel with the Redis adapter. Any changes to the model will be saved to our Redis data store. We're also associating each Roster record with a user.

class Roster < SuperModel::Base
  include SuperModel::Redis::Model
  include SuperModel::Timestamp::Model

  belongs_to :user
  validates_presence_of :user_id

  indexes :user_id
end

Now let's integrate the Roster class with Juggernaut. We're going to listen to Juggernaut's server events - fetching the user_id out of the events meta data, and calling event_subscribe or event_unsubscribe, depending on the event type.

def self.subscribe
  Juggernaut.subscribe do |event, data|
    user_id = data["meta"] && data["meta"]["user_id"]
    next unless user_id
      
    case event
    when :subscribe
      event_subscribe(user_id)
    when :unsubscribe
      event_unsubscribe(user_id)
    end
  end
end

Let's implement those two methods event_subscribe & event_unsubscribe. We need to take into account they may be called multiple times for a particular user_id, if a User opens multiple browser windows co-currently.

def event_subscribe(user_id)
  record = find_by_user_id(user_id) || self.new(:user_id => user_id)
  record.increment!
end

def event_unsubscribe(user_id)
  record = find_by_user_id(user_id)
  record && record.decrement!
end

We need to add a count attribute to the Roster class, so we can track if a client has completely disconnected from the system. Whenever clients subscribes to a channel, increment! will get called and the count attribute will be incremented, conversly whenever they disconnect from that channel decrement! will get called and count decremented.

attributes :count

def count
  read_attribute(:count) || 0
end

def increment!
  self.count += 1
  save!
end

def decrement!
  self.count -= 1
  self.count > 0 ? save! : destroy
end

When decrement! is called, we check to see if the count is zero, i.e. a client is no longer connected, and destroy the record if necessary. Now, at this point we have a live list of Roster records indicating who's online. We just need to call Roster.subscribe, say in a Rails script file, and Juggernaut events will be processed.

#!/usr/bin/env ruby
require File.expand_path('../../config/environment',  __FILE__)

puts "Starting Roster"
Roster.subscribe 

There's no point, however, in having a live Roster unless we can show that to users - which is the subject of the next section, observing models.

##Observing models

We can create an Juggernaut observer, which will observe some of the models, notifying clients when they're changed.

class JuggernautObserver < ActiveModel::Observer
  observe :roster
  
  def after_create(rec)
    publish(:create, rec)
  end

  def after_update(rec)
    publish(:update, rec)
  end

  def after_destroy(rec)
    publish(:destroy, rec)
  end

  protected
    def publish(type, rec)
      channels = Array(rec.observer_clients).map {|c| "/observer/#{c}" }
      Juggernaut.publish(
        channels, 
        {
          :id     => rec.id, 
          :type   => type, 
          :klass  => rec.class.name,
          :record => rec
        }
      )
    end
end

So, you can see we're calling the publish method whenever a record is created/updated/destroyed. You'll notice that we're calling observer_clients on the updated record. This is a method that application specific, and needs to be implemented on the Roster class. It needs to return an array of user_ids associated with the record.

So, as to the JavaScript side to the observer, we need to subscribe to a observer channel and set a callback. Now, whenever a Roster record is created/destroyed, the process function will be called. We can then update the UI accordingly.

var process = function(msg){
  // msg.klass
  // msg.type
  // msg.id
  // msg.record 
};

var jug = new Juggernaut;
jug.subscribe("/observer/" + user_id, process);

##Full examples

You can see the full examples inside Holla, specifically roster.rb, juggernaut_observer.rb and application.juggernaut.js.

More Repositories

1

monocle

Link and news sharing
Ruby
1,453
star
2

abba

A/B testing framework
Ruby
1,351
star
3

holla

Holla! - Rich JavaScript Application
JavaScript
1,069
star
4

jquery.magicmove

Animate DOM transitions.
JavaScript
644
star
5

bowline

Ruby/JS GUI and Binding framework (deprecated)
Ruby
637
star
6

stylo

Spine/CoffeeScript example GUI designer
JavaScript
525
star
7

saasy

Rails SaaS and SSO solution
Ruby
523
star
8

gfx

CSS3 3D animation library
JavaScript
511
star
9

nestful

Simple Ruby HTTP/REST client with a sane API
Ruby
505
star
10

ace

Sinatra for Node
CoffeeScript
461
star
11

supermodel

Ruby in-memory models
Ruby
367
star
12

acts_as_recommendable

Collaborative Filtering for Rails
Ruby
325
star
13

book-assets

Files for the O'Reilly book JavaScript Web Applications
JavaScript
310
star
14

go

go
Ruby
258
star
15

trevi

An opinionated Sinatra app generator
Ruby
251
star
16

flarevideo

HTML5 & Flash Video Player
JavaScript
243
star
17

spine.todos

A Backbone alternative idea
JavaScript
239
star
18

hermes

Messaging re-invented
Ruby
206
star
19

motivation

New Chrome tab page showing your age
JavaScript
197
star
20

juggernaut_plugin

Realtime Rails
JavaScript
195
star
21

spine.contacts

Spine demo contact manager
CoffeeScript
182
star
22

sprockets-commonjs

Adds CommonJS support to Sprockets
Ruby
179
star
23

macgap-rb

Generator for MacGap
Objective-C
159
star
24

sinatra-blog

A example Sinatra blog
Ruby
157
star
25

headsup

A simple Heads Up display
Ruby
145
star
26

catapult

A Sprockets/Rack build tool
Ruby
139
star
27

remail

RESTful email for Rails
Ruby
138
star
28

spine.rails3

Sample app demonstrating Spine and Rails integration
Ruby
130
star
29

bowline-twitter

Bowline Twitter client
JavaScript
112
star
30

wysiwyg

CoffeeScript
104
star
31

sinatra-pubsub

Push & Streaming for Sinatra.
Ruby
99
star
32

push-mac

Objective-C
94
star
33

stitch-rb

Stitch ported to Ruby
Ruby
93
star
34

dhash

Compare image similarity with a dhash
Ruby
93
star
35

101-school

AI generated courses
TypeScript
92
star
36

ichabod

Headless JavaScript testing with WebKit
JavaScript
85
star
37

colorcanvas

JavaScript
83
star
38

roauth

*Simple* Ruby OAuth library
Ruby
82
star
39

push

Ruby
81
star
40

juggernaut_gem

Realtime Rails
Ruby
79
star
41

spine.mobile

Spine Mobile Framework
CoffeeScript
77
star
42

super.js

Simple JavaScript framework for building RIAs (with jQuery)
JavaScript
64
star
43

oped

Email based diary
Ruby
57
star
44

remail-engine

RESTful email for Rails - see http://github.com/maccman/remail
Python
48
star
45

syncro

Synchronize state across remote clients.
Ruby
47
star
46

spine.mobile.currency

Spine Mobile currency convertor example
CoffeeScript
46
star
47

sourcemap

Ruby library for using Source Maps
Ruby
43
star
48

superapp

JavaScript state machine and class abstraction for building RIAs (deprecated! - use http://github.com/maccman/super.js)
JavaScript
31
star
49

sprockets-source-url

Adds @sourceURL support to Sprockets
Ruby
30
star
50

spine.infinite

Infinite scrolling with Spine & Rails
JavaScript
29
star
51

bowline-desktop

wxWidgets/Ruby/Webkit framework for Bowline apps
C++
28
star
52

ymockup

UI mockups using HTML and CSS
JavaScript
28
star
53

supermodel-js

SuperModel in JavaScript (deprecated! - use http://github.com/maccman/super.js)
JavaScript
26
star
54

zombies

A Facebook/Spine game.
Ruby
24
star
55

quora2

Redesigning Quora's interface, turning it into a JavaScript web application powered by Spine.
JavaScript
21
star
56

spine.mobile.contacts

Example Spine Mobile App
CoffeeScript
20
star
57

humanapi

Ruby
20
star
58

request-profile

API to access autocomplete data
JavaScript
19
star
59

gwo

Rails plugin integrating Google Web Optimizer for AB tests
Ruby
18
star
60

cft

CoffeeScript
17
star
61

jquery.upload.js

Upload files using Ajax
JavaScript
17
star
62

spine.realtime

Realtime Spine app with Rails
Ruby
15
star
63

rbyte

Byte compile Ruby 1.9.1 files and "require" support for loading compiled files.
Ruby
14
star
64

phonegap

Gem for building PhoneGap apps
Shell
13
star
65

segment-hooks

Trigger arbitrary JavaScript from Segment.com events
JavaScript
13
star
66

spine.mobile.workout

Spine Mobile Workouts Example
CoffeeScript
11
star
67

restful_email

AppEngine that provides a RESTful interface to sending emails (decrep - use http://github.com/maccman/remail)
Python
10
star
68

csbook

CoffeeScript
10
star
69

statster

Merb Web Analytics
Ruby
9
star
70

rack-modulr

Use CommonJS modules in your Rack/Rails applications
9
star
71

blossom

Demonstration chat app
JavaScript
9
star
72

dtwitter

Distributed Twitter
9
star
73

canonical

Rails plugin providing helper for canonical URLs
8
star
74

jquery.drop.js

jQuery lib abstracting the drag/drop API
JavaScript
8
star
75

alexmaccaw

Portfolio
Ruby
8
star
76

omniauth-humanapi

OmniAuth strategy for HumanAPI.
Ruby
8
star
77

the-managers-handbook

JavaScript
8
star
78

syncro-js

JavaScript library for Syncro
JavaScript
8
star
79

less-rb

Less using ExecJS
Ruby
7
star
80

spine.tutorials

Spine tutorials (DEPRECATED - use http://spinejs.com)
JavaScript
6
star
81

bowline-bundler

Specialized version of the Bundler gem for Bowline apps
Ruby
6
star
82

serveup

JavaScript
5
star
83

gdata

Recent clone of http://code.google.com/p/gdata-ruby-util (with Ruby 1.9 support)
Ruby
5
star
84

package-jquery

JavaScript
5
star
85

package-jquery-ui

JavaScript
4
star
86

jquery.tmpl

jQuery.tmpl for Hem
JavaScript
4
star
87

node-twitter-stream

Twitter Streaming API Library for Node.js
JavaScript
4
star
88

counterman

Ruby
4
star
89

monocle-assets

Ruby
4
star
90

jlink

jQuery data binding library - bind objects to HTML elements
JavaScript
4
star
91

cloudflare-r2-edge

TypeScript
4
star
92

super.todos

Port of Backbone.js Todos to Super
JavaScript
4
star
93

jeco

jQuery extension to eco
CoffeeScript
3
star
94

invoices

Test Spine app
JavaScript
3
star
95

like-detector

TypeScript
3
star
96

bp-p2p

Browser Plus P2P
Ruby
3
star
97

renoir

Simple Canvas physics engine using Verlet Integration
JavaScript
3
star
98

hnv2

Hacker News V2
CoffeeScript
3
star
99

elb-nginx-vhosts

Shell
2
star
100

socialmod

Ruby/Python/PHP client libs
PHP
2
star