Juggernaut: "Server Push" - works in 95% of all browsers!

Posted by Dan Grigsby
on Wednesday, June 27

Juggernaut is a clever “server push” solution for Rails that’ll work in nearly all browsers.

With Juggernaut, your server can push updates into an already rendered page. Push a useful alternative to Ajax style polling, especially when your application is event-based with an update cycle that is irregularly/unpredictably timed.

Chat is the most common example of using push, though it’s easy to imagine other collaborative applications where this would work.

I’ve spent the evening pouring over the code; the Juggernaut site and README contain useful documentation to get you started; I’ll use this post to describe how Juggernaut actually works behind the scene.

Juggernaut Internals

Juggernaut’s implementation is clever, works in better than ninety five percent of all browsers, and is both small-enough and straightforward-enough to be approachable/understandable to most Rails programmers.

Juggernaut is made up of three main elements: (1) in-browser/client side elements, (2) a stand alone push server and (3) Rails elements to tie everything together.

In-Browser/Client Side Elements (media/juggernaut.as ; media/juggernaut.js)

Juggernaut works by embedding a Flash object into pages that receive push messages. It accomplishes this by establishing an long-lived outbound TCP connection to the oush server, which lives on a separate port than the web server.

Unlike a browser initiated HTTP connection that is closed after the page renders, the Flash initiated TCP connection remains open, waiting for messages. (Long-lived HTTP connections don’t work well with Rails because of Rails’ oft-discussed single-threaded implementation; this approach side-steps that potential problem.)

The push server sends message down the already established connection. Messages are strings, often a rendered RJS partial, containing JavaScript that is eval’d in the page. So, borrowing an example from the README, you could use Prototype’s Insertion object you could update the “chat_data” div with the following message string:


new Insertion.Top('chat_data', '<li>#{input_data}</li>');

The Flash component is tiny – the ActionScript is only 38 lines!—and really straight forward: it simply opens the TCP connection (using ActionScript’s XMLSocket object to connect to host/port that are passed in as arguments to in the the with host and port params passed in in the enclosing HTML) and connects an event handler to hand off messages that it receives to a JavaScript method in the browser that runs eval on the message string.

Modern browsers, designed for HTTP/1.1, limit the number of connections to a host. IE, for example, won’t allow more than 2 simultaneous connections. By using keep-alive to allow a single connection to handle multiple requests browsers are able to avoid the time/overhead of connection setup and takedown. Using the Flash VM to handle the connections is handy in this regard because it doesn’t use one of your previous few connections.

Stand Alone Push Server (media/push_server)

Conceptually, the push server is straightforward: it receives messages (usually from the web server) and forwards it to the clients.

Messages are generated based on events that happen “elsewhere” (from the perspective of the client receiving the message). For example: we’re both in a chat room; you enter a message that is delivered to the web server using Ajax. The web server receives the message from you, which should ultimately be displayed in my browser, and forwards it to the push server for delivery to me in my browser.

The push server receives messages serialized to JSON over a standard TCP socket (that it opens for listening at startup) and not via DRB. Presumably this is to more easily allow non-Ruby processes to send messages, though there are implications with session that I’ll discuss below in the Broadcast/Channels section.

Rails Components (lib/juggernaut.rb ; lib/juggernaut_helper.rb)

Juggernaut’s Rails components tie everything together.

Form helpers instantiate the Flash object with the appropriate host/port parameters and include the necessary JavaScripts to evaluate events. Using the helper is a one-line non-event:


  <%= listen_to_juggernaut_channels :chat_channel %>

A couple of Juggernaut class methods make relaying messages easy. For example this code…


Juggernaut.send_data(data, channels])

...opens a new socket to the push server, passes the message on, and closes/cleans-up.

Broadcast/Channels

When creating messages, you choose whether to send them to a single user (via the Juggernaut.send_to method) or to broadcast them (via the Juggernaut.send_data method).

Single user message recipients are identified by a unique id. Unless otherwise specified, this is set to session.session_id. If you plan to create messages outside of the Rails framework, it’s probably handier to use another unique ID can be easily associated with the user; using Rails internal session identifier outside of Rails seems more complicated than necessary. The ID column from the user table seems like a natural choice.

For broadcast, Juggernaut uses a “channel” metaphor. The HTML helpers pass the channels that the page/user subscribe to the Flash object, which in turn sends them to the push server when it establishes its connection.

Sessions and Security

The push server uses a “shared secret” string to restrict unauthorized users from pushing messages out to end users.

Client access control is optional. If turned on, it works at a connection level (i.e., you can either connect to the push server or not). It’s implemented in a clever way: when the Flash player connects to the push server it includes its Rails session.session_id when it sets up the connection. The push server then makes an HTTP request to an authenticated page within the Rails app using this session id. If it succeeds (and gets a HTTP response code of 200) then the user is considered authenticated.

Some TODOs

From looking at the code, it appears that if the client’s connection to the push server is lost that it won’t automatically reconnect. There is a “num_tries” configuration directive for setting up the number of times to try to reconnect after a failure, but it’s unused in the Flash. Similarly, the “num_secs” directive, which sets the number of seconds to try between delivery attemts, is unused.

Conclusion

All in all, it’s a very elegant solution to an interesting problem.

AJAX partial DOM posting with link_to_remote

Posted by Casey
on Tuesday, April 17

One of my favorite little AJAX tricks is posting only a few fields of the DOM with link_to_remote.

The real world application of this is when you have a composite object that needs to be created in the process of creating your main composing object. It can also be helpful when you have a dynamic interface with hidden divs within one main form and you don’t not want to pass the whole form via xhr (it could be a multipart request with a file selector).

Here is how to submit only one DOM element’s children form inputs:

1.Define the DOM element you want to submit
1
2
3
4
5
6
7
8
9
10
11
12
<div id="remote_message" style="display:none"></div>

<div id="create_input">
        <label>Movie name</label>
        <input type="text" name="create_movie_name" id="create_movie_name" value="" />
</div>

<%= link_to_remote "Create movie", 
        {:url=>'/movie/create',  :submit=>"create_input",  
                :success=>'handleCreateSuccess(request);',  
                :failure=>'handleCreateFailure(request);' }, 
        {:id=>'create_movie_link_id'} %>

The important thing to note is the :submit=>‘create_input’ which defines the DOM element whose children will be serialized and passed to the controller on submit. Also notice we haven’t defined an HTML form. Prototype is smart enough to serialize any DOM structure, not only forms. This could just as well be inside a form, but only the DOM element we specify will be sent to the server.

2. A standard controller to handle the post
1
2
3
4
5
6
7
8
9
class MovieController < ApplicationController

  def create
    return unless request.xhr? && request.post?
    # do stuff
    render :inline => "'#{params[:create_movie_name]}' was created"
  end

end
3. Handle the response with javascript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<script type="text/javascript">
//<![CDATA[
        function handleCreateSuccess(request) {
                showMessage("Success! " + request.responseText);
        }
        function handleCreateFailure(request) {
                showMessage("Problems creating that movie: <br/>" + request.responseText);
        }
        function showMessage(message) {
                $('remote_message').show();
                $('remote_message').innerHTML = message;
                new Effect.Highlight('remote_message');
                new Effect.Fade('remote_message', {duration: 10})
        }
//]]>
</script>
And that’s it. Of course you don’t need to use link_to_remote, you could just use prototype's Ajax.Request()
1
2
3
4
5
new Ajax.Request('/movie/create', 
    {asynchronous:true, evalScripts:true, 
        onFailure:function(request){handleCreateFailure(request);},
        onSuccess:function(request){handleCreateSuccess(request);}, 
        parameters:Form.serialize('create_input')}); 

Using this technique you are no longer forced to do full form submissions when you only want a small bit of data which will lead to a more dynamic and ‘snappy’ user experience.