2009
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!
