Dec 18
2009

Ajax Push (Comet) in Grails using the Atmosphere Plugin

Introduction to Ajax Push

One of the most exciting new web technologies popping up these days is a technique known as ajax push (often called comet). Ajax push allows a web application to get around one of the most fundamental limitations in HTTP, which is that connections are 1-sided. The browser requests information from the server, and the server responds. The server can never initialize a new connection with the browser and send new data.


The simplest way to get around this problem is to use polling. That is, the browser simply asks the server repeatedly if anything new has happened. This solution is problematic for several reasons. First, the client will only receive updates as frequently as it polls. When an event occurs, the browser doesn't see it until the next time it sends a polling request. Secondly, polling generates a lot of traffic to the server and this makes it very difficult to scale.


Fortunately there is a way we can simulate server-initialized events under HTTP using ajax push. The idea is that when the browser sends a request to the server, the server does not respond right away. The server acts as if it's just taking a long time to return a response, when it actuality the server is just holding the response open until it has something new to send to the browser.


There are 2 main ways to implement this server-side pushing. The first is called "long polling". Under Long Polling when the server has some new data to send to the client it send the data and closes the connection. Then, the client reconnects and the same process continues. Long Polling resembles polling in that the browser is sending requests to the server over and over. However, the client is updated instantly when the server has something new to send, and polling only occurs when there is actually new data to be sent.


The second implementation of ajax-push is called "Forever Frame". Under this scheme, the browser opens a hidden iframe and sets the src property of this iframe to point to the server. When the server sees this request it holds the response open. When an event occurs, the server pushes it to the browser but never closes the connection.


Forever Frame is much simpler to implement than Long Polling, but it suffers from some significant downsides. First, it will look like the page is never finished loading, and if the user hits "stop" in their browser the connection will close, which is annoying. Secondly, it is not as rebost as Long Polling since if the connection to the iframe closes for any reason pushing cannot occur. However, since Forever Frame is simpler we will use it in this tutorial.


One final note is that it is not feasible (as far as I know) to implement ajax push elegantly in PHP or Ruby or Python (except for Java implementions like JRuby and Jython). The reason is that their web servers cannot deal with open threads well. Java has Non-blocking IO (NIO) which lets java web servers overcome this difficulty. Also, there is (as far as I know) no concept of a "servletContext" or any sort of in-memory, cross-thread memory store to communicate across requests. For example, if user1 sends a message to user2, you would have to write to a file or a DB to store that information between requests/users which is not as efficient. If I'm wrong about any of this please correct me.

Getting The Atmosphere Plugin

Ok, now let's move on to getting all of this working in Grails. There is an excellent framework written to make ajax push in Java stupidly easy called Atmosphere (https://atmosphere.dev.java.net). Atmosphere abstracts the low-level details of using NIO in your web server to deal with ajax push. Furthermore, it makes the resulting war webserver-agnostic so you can deploy on any java web server and not have to worry about the specific ajax push API used by that webserver - atmosphere just takes care of all of that for you.


Using Atmosphere in Grails is easy. There is a grails-specific plugin available at http://grails.org/plugin/atmosphere. Simply type:

  grails install-plugin atmosphere

This plugin provides a paper-thin wrapper around the atmosphere framework, and also gives you a sample chat application to get you pumped up. At the time of this writing, the grails atmosphere plugin uses atmosphere 0.4.1.


While it's certainly cool that the plugin gives you a working example to mess with (even if it is a glorified ad for the plugin-writer's blog), there are several things that really bug me about the grails atmosphere plugin. First, there's very little documentation (just a single google doc file, written in French, located at: http://docs.google.com/View?id=dfdr5d2x_4fm352bnt).


Second, there's a bunch of little gotchas with the grails plugin. For instance, I can't figure out how to get it to not include the sample ChatController with my application. As it turns out, some very bizarre things happen if you have your own controller named ChatController. Also, there's no way to set-up multiple atmosphere handlers for your applications, or to bind them to any url besides /atmosphere. The problem is that in atmosphere 0.4.1, in order to set up an atmosphere handler you must set the url path both in atmosphere.xml and as a servletMapping in web.xml (See http://n2.nabble.com/AtmosphereHandler-context-root-td3948858.html for more details). However, in the grails plugin the servletMapping in web.xml is hardcoded to /atmosphere. If you want to add a new atmosphereHandler, you will have to manually edit your web.xml template. But if you're doing that anyway, then why did you even install the grails plugin? You may as well just grab the atmosphere libs directly from the atmosphere website and set up everything yourself. If there is some way to add/change the servlet mapping generated by the Grails plugin please point it out to me because I can't figure out how to do it.


So, if you choose to use the grails atmosphere plugin, just make sure that you don't name any of your controllers ChatController, you only have 1 atmosphere handler mapped to /atmosphere, and you don't mind having a random demo chat application registered with your application which won't actually work because you'll be stealing the /atmosphere URL mapping for your own atmosphere handler.


If you don't use the grails plugin you can still use atmosphere in grails without too much trouble. All you need to do is download the jars from the atmosphere site and set-up the web.xml, atmosphere.xml, and context.xml files yourself. You can see samples of how to set up these files on the atmosphere web site. This solution will also let you use a different atmosphere version than 0.4.1. For this tutorial, I'm using the grails plugin since the install is easier, but if you manually install atmosphere everything in this tutorial except the atmosphereConfig.groovy stuff will still apply.

How to Use Atmosphere

Atmosphere revolves around an interface called an AtmosphereHandler. the AtmosphereHandler interface defines 2 methods that must be implemented:

  public void onRequest(AtmosphereResource<HttpServletRequest, HttpServletResponse> event) throws IOException
  public void onStateChange(AtmosphereResourceEvent<HttpServletRequest, HttpServletResponse> event) throws IOException

When a request comes in to a URL mapped to atmosphere, the onRequest method of the atmosphereHandler is called, passing in an AtmosphereResource as a parameter. The AtmosphereResource represents the request itself. To hold the response open (wait until something worth sending to the client occurs), simply call suspend() on the AtmosphereResource that gets passed in.


Once you have the response suspended, the next step is to update the browser when something interesting happens. To push data to a suspended AtmosphereResource atmosphere uses an interface called "Broadcaster". The most important method in the Broadcaster interface is:

  public void broadcast(Object o)

This method simply sends whatever is passed in to all its associated AtmosphereResources. You can either broadcast to all suspended clients by broadcasting to the default broadcaster (you can get this by calling getAtmosphereResource() on an AtmosphereResource passed into onRequest), or you can make you own broadcasters and manually add AtmosphereResources to broadcast to a subset of the suspended clients.


When an AtmosphereResource gets broadcasted to, the onStateChange method of the AtmosphereHandler gets called, passing in an AtmosphereResourceEvent which has a reference to the suspended AtmosphereResource and the message being broadcast. Here you can do whatever you want with the message before sending it out (wrapping it in javascript tags, etc...). Also, if you're performing long polling, this method is where you would resume the suspended response (by calling resume() on the AtmosphereResource).


There is, of course, a lot more to Atmosphere than just this. You can find much more detailed documentation on the atmosphere website or in the atmosphere javadocs (https://atmosphere.dev.java.net/nonav/apidocs/index.html).

Using Atmosphere in Grails

The first thing to do when using Atmosphere in Grails (using the grails atmosphere plugin) is to visit AtmosphereConfig.groovy in the conf folder. Here is where you can define settings for Atmosphere. The grails plugin will turn this file into atmosphere.xml file at runtime. The most important thing to do in this file is to change 'com.odelia.grails.plugins.atmosphere.ChatAtmosphereHandler' to whatever your AtmosphereHandler is. As stated above, if you try to have multiple AtmosphereHandlers, or use a URL other than /atmosphere, the grails plugin will not work.


Now that we have a basic understanding of Atmosphere and ajax push, let's look at how we can apply this to grails. A good starting place is deciphering the AtmosphereHandler that comes with the grails plugin. the (slightly abridged) code is shown below. The full version can be found by poking around in the :


class ChatAtmosphereHandler implements AtmosphereHandler<HttpServletRequest, HttpServletResponse> {

    private final static String BEGIN_SCRIPT_TAG = "<script type='text/javascript'>\n"
    private final static  String END_SCRIPT_TAG = "</script>\n"

    void onRequest(AtmosphereResource<HttpServletRequest, HttpServletResponse> event) throws IOException {
        def request = event.request
        def response = event.response

        switch(request.method.toUpperCase()) {
            case "GET":                
                event.suspend()

                def broadcaster = event.broadcaster
                broadcaster.broadcasterConfig.addFilter(new XSSHtmlFilter())

                break
                
            case "POST":
                response.characterEncoding = "UTF-8"
                String action = request.getParameterValues("action")[0]
                String name = request.getParameterValues("name")[0]

                switch(action) {
                    case "login":
                        request.session.setAttribute("name", name)
                        event.broadcaster.broadcast("System Message**${name} has joined.")
                        response.writer.with {
                            write "success"
                            flush()
                        }
                        break

                    case "post":
                        def message = request.getParameterValues("message")[0]
                        event.broadcaster.broadcast("${name}**${message}")
                        response.writer.with {
                            write "success"
                            flush()
                        }
                        break
                }
        }
    }
    
    void onStateChange(AtmosphereResourceEvent<HttpServletRequest, HttpServletResponse> event) throws IOException {
        def request = event.resource.request
        def response = event.resource.response

        if (!event.message) return

        def e = event.message.toString()

        def name = e
        def message = ""

        if (e.indexOf("**") > 0) {
            name = e.substring(0, e.indexOf("**"))
            message = e.substring(e.indexOf("**")+2)
        }

        def msg = BEGIN_SCRIPT_TAG + toJsonp(name, message) + END_SCRIPT_TAG        

        response.writer.with {
            write msg
            flush()
        }
    }
   
    private String toJsonp(String name, String message) {
        "window.parent.app.update({ name: \"${name}\", message: \"${message}\" });\n"
    }
}

Let's try to go through what's happening in this file. This class implements AtmosphereHandler and implements the onRequest and onStateChange methods. In the onStateChange method, there is a switch based on method type. If the request is a GET request, the response is suspended and an XSS filter is added to the broadcaster. Filters modify the broadcasted message before it reaches the onStateChange method. As you can probably guess, the XSS filter escapes html and javascript which could be harmful. This chat example uses the "Forever Frame" style of ajax push, so the response will never be resumed. When a POST request comes in, the handler broadcasts some stuff to everyone in the chatroom but does not suspend the response.


It should also be noted that the grails plugin also defines a grails controller called "ChatController" which just has an index method to load chatroom html/css/javascript initially. You can see all that stuff by poking around in the atmosphere plugin directory. Once the user's browser loads the page, it points a hidden iframe at '/atmosphere'. This response gets suspended indefinitely (since it's a GET request). Whenever the server pushes some javascript, it executes through this hidden iframe and updates the user's main page. When the user sends a message or logs in, the browser sends a normal XHR post to '/atmosphere' with the parameter "action" set to either "post" or "login".


Moving on to the onStateChange method, we formulate some javascript to send back to the client based on the message string sent duing the onRequest method. The reason the javascript is generated in onStateChange and not in onRequest is that the XSS filter would have stripped it all out! once we have the javascript response set up, we grab the response writer off the passed-in AtmosphereResourceEvent and send out our response to the client. The client's browser executes the javascript as it comes in and the user is crazy impressed with our application. If this were set up to use long-polling we would have called resume() on the AtmosphereResource here too.


This file is just a very loose groovy port of the "Javascript Chat with POJO" example on the atmosphere website. As such, while this works, it's not very "Grails". In fact, it doesn't make use of any grails constructs at all! Let's rewrite this file to be more "Grails", and in the process learn more about how to use Atmosphere in Grails.


First, the ChatAtmosphereHandler should really be a grails controller. It's taking in requests and sending out responses and that's what controllers do. Secondly, the AtmosphereHandler onRequest and onStateChange break the typical grails controller structure, and it's harder to use a lot of the nice methods grails controllers have access to inside of these methods since they occur outside the normal context of a grails request. As such, only requests that get suspended should ever touch the onRequest method. If the response isn't getting suspended, then there's no reason why it should be going through an AtmosphereHandler. Let's make these changes to the above file:

class ChatroomController implements AtmosphereHandler<HttpServletRequest, HttpServletResponse> {

    private final static String BEGIN_SCRIPT_TAG = "<script type='text/javascript'>\n"
    private final static String END_SCRIPT_TAG = "</script>\n"
    
    def index = { }
    
    def post = {
      session.broadcaster.broadcast("${params.name}**${params.message}")
      render "success"
    }
    
    def login = {
      def name = params.name
      session.name = name
      session.broadcaster.broadcast("System Message**${name} has joined.")
      render "success"
    }

    void onRequest(AtmosphereResource<HttpServletRequest, HttpServletResponse> event) throws IOException {
        def request = event.request
        def response = event.response
             
        event.suspend()

        def broadcaster = event.broadcaster
        request.session.broadcaster = broadcaster
        broadcaster.broadcasterConfig.addFilter(new XSSHtmlFilter())                

        }
    }
    
    void onStateChange(AtmosphereResourceEvent<HttpServletRequest, HttpServletResponse> event) throws IOException {
        def request = event.resource.request
        def response = event.resource.response

        if (!event.message) return

        def e = event.message.toString()

        def name = e
        def message = ""

        if (e.indexOf("**") > 0) {
            name = e.substring(0, e.indexOf("**"))
            message = e.substring(e.indexOf("**")+2)
        }

        def msg = BEGIN_SCRIPT_TAG + toJsonp(name, message) + END_SCRIPT_TAG        

        response.writer.with {
            write msg
            flush()
        }
    }
   
    private String toJsonp(String name, String message) {
        "window.parent.app.update({ name: \"${name}\", message: \"${message}\" });\n"
    }
}

Much, much cleaner! By turning this into a Grails controller we were able to respond to requests that don't need to be suspended using the normal grails controller structure. We passed a reference to the Broadcaster in the user's session so we can broadcast stuff without having to enter the onRequest method. This lets us use nice grails methods and variables like render and params and session rather than having to dig that stuff up from the AtmopshereResource in onRequest. Plus, we were able to get rid of that ugly switch statement inside of onRequest. Now, instead of making a POST request to '/atmosphere' with 'action=login' as a parameter, we can just make a normal request to /chatroom/login - much nicer!


This is really just skimming the surface of what you can do with grails + atmosphere. All the docs for the normal atmosphere plugin can still be applied in grails. However, grails does a lot of nice things for you so try to think how you can structure your atmosphere app to make use of grails as much as possible and your life will be much easier!

Oct 18
2009

Automated Twitter Updates with the Ruby Twitter Gem

Twitter has a really nice, simple API and it's extremely easy to write a script to automatically post stuff to a twitter account. We're going to use the twitter ruby gem and cron to post automated messages to a twitter account every hour.

First, let's install the twitter gem.

    sudo gem install twitter
  
Easy enough. Let's also assume we have a twitter account with login 'twitlogin' and password 'twitpass'. Now let's write a ruby script that will post something to twitter every time it's run. Just for the sake of this example let's have it post what time it is.


We'll use HTTPauth to log in to twitter (it's simpler than OAuth, but feel free to use OAuth if you want). Then, once we've logged in, we'll post the time. Anyway, here's the script below:

    require 'rubygems'
    require 'twitter'
    
    httpauth = Twitter::HTTPAuth.new('twitlogin', 'twitpass')
    client = Twitter::Base.new(httpauth)
    message = Time.now.to_s
    client.update(message)
  

You are, of course, welcome to replace 'message' with whatever you actually want to post.


Now for step 2: the cron job. Let's assume we've saved our newly-made ruby script in /home/example/poster.rb. To make a cronjob for this task, type:

  crontab -e
Then, enter the following:
  55 * * * * ruby /home/example/poster.rb

...And you're done! This will run the script every hour on the 55 minute mark. The way cron works is there are 5 time specifiers you can use to specify when you want the command to be run. The time specifiers are set in the order: minute (0 - 59), hour (0 - 23), day of the month (0 - 31), month (1 - 12), day of the week (0 - 6), with * being a wildcard. So, 55 * * * * means the script will be run on the 55th minute of any hour of any day of the month on any month and any day of the week. Or, simply put, every hour on the 55 minute mark.


If you want to see this script in action just take a look at @fortune_yeti (twitter.com/fortune_yeti). This account is running the script seen above except outputting a random fortune quote every hour rather than just the time.


Enjoy!

Oct 01
2009

Making a Smoothly Sliding Div that Moves on Page Sroll

*This is a repost of a tutorial I wrote on the mapyeti blog*

One of the sexier features of mapyeti is the sliding map on the main page. It's actually a really easy effect to implement. For this tutorial we'll be using Prototype and Scriptaculous, although you should be able to adapt this to whichever flavor of javascript you like without too much work.

The Effect

I'm sure you've seen pages that use Javascript to force a part of the page to have a fixed position while scrolling. Horrible Idea. The fixed element is always jittery during the scroll and it's really distracting and just unpleasant in general. A much better way to achieve a similar effect is to wait until the user stops scrolling, then slide the div smoothly to the new position. It's must less distracting, and when you see it happen you get a warm, fuzzy feeling inside :).

Step 1: Building the sliding action

For the sliding action, we want to make our div slide up to the top of the user's browser screen. To Do this, we'll use document.viewport.getScrollOffsets() From Prototype. This function returns how far the user has scrolled in pixels as an array. In our case, we're just interested in the vertical scroll offset, or document.viewport.getScrollOffsets()[1]. Calling this will tell us exactly how far the user has scrolled so we know how far down we have to move our sliding div to maintain it's position on the page.

Next, we'll use the scriptaculous function Effect.move to slide the div down smoothly to it's new position. Let's put it all together:
function slide_that_div(){
  var scroll_offset = document.viewport.getScrollOffsets()[1];
  new Effect.move('sliding_div', { y: scroll_offset, mode: 'absolute' });
}
The first parameter to Effect.move is the id of the div you want to slide (or the div itself if you prefer), and the second parameter is a hash of options. In our case, we're just moving in the y-direction and using absolute positioning. This means that the position we specify will be relative to the top-left corner of the page, rather than the current position of the div.

Step 2: Handling the Scrolling Event

Now we have a function we can call to make our div slide into the correct position on the page, so our next step is to figure out when the user has finished scrolling. There are several ways you can do this. Unfortunately there is no built-in "scrollend" event, so we have to figure out when the user stops scrolling manually. The easiest method is to just poll every half-second or so and see if the scroll offsets are changing. We'll use the document.viewport.getScrollOffsets()[1] method described above to measure the scroll offsets. To do an action at set intervals in javascript we use the setInterval() function. This function takes 2 parameters: a function to call repeatedly and a number representing a period in ms. We're going to want to initialize this timer when our page first loads, so we'll put all this stuff into a document.observe("dom:loaded") event handler. The "dom:loaded" event gets called when all the elements in the page are first loaded.

Putting it all Together we have:
var scrolling = false;
var last_scroll;

document.observe("dom:loaded",function(){
  setInterval(function(){
    var current_scroll = document.viewport.getScrollOffsets()[1];
    if (scrolling){
      if (current_scroll == last_scroll){
        scrolling = false;
        slide_that_div();
      }
    } else if(current_scroll != last_scroll){
      scrolling = true;
    }
  }, 500);
  last_scroll = current_scroll;
});
There are 2 variables we need to keep track of. "scrolling" is a boolean that says whether or not the user is in the process of scrolling. "last_scroll" records the last scroll offset we measured. We compare this to the current scroll offset to determine whether or not the user is scrolling.

What this function is saying is if we weren't scrolling and the scroll offsets don't match, record that the user started scrolling. If the user was scrolling and the scroll offsets match, then the user has stopped scrolling so we should fire off our "move_that_div()" function to slide our div into place.

That's all there is to it! Simple, right? If you want to see this in action just take a look at the map on the mapyeti main page (www.mapyeti.com). Enjoy!