Robin Bramley is a hands-on Architect who has spent the last decade working with Java, mobile & Open Source across sectors including Financial Services & High Growth / start-ups. Prior to that he helped UK Police Forces with MIS /reporting & intelligence systems. He has contributed to a wide variety of Open Source projects including adding Open ID support to Spring Security. Robin is a DZone MVB and is not an employee of DZone and has posted 24 posts at DZone. You can read more from them at their website. View Full User Profile

Using Browser Push in Grails

11.22.2011
| 9407 views |
  • submit to reddit

Browser Push is the collective term for techniques that allow a server to send asynchronous data updates in near real time to a browser. This article provides an overview of browser push and then provides a sample of Grails usage by extending the example project from the ‘Using JMS in Grails‘ article in the June 2011 edition to send event-driven updates to the browser.

This article originally appeared in the July 2011 edition of GroovyMag.

When do you need real time data?

There are many application scenarios where the user requires information to be delivered in near real time. Some examples from projects I’ve worked on:

  • emergency services control room systems
  • price information in a swaps trading platform
  • call centre operators

Now some of these may be better suited to a desktop client (e.g. Griffon) where you can receive JMS directly. However for this article we’ll assume the use of a browser-based application with JavaScript client side event handling.

How does browser Push work?

There are a number of different techniques used for getting data to the browser in near real time.

Polling

Firstly this isn’t really push, but some systems just poll the server frequently e.g. every second. This has the advantage of working with every browser and server combination. As Figure 1 illustrates, there can be some lag before the server event reaches the client depending on the polling frequency; there may also be some requests that are pure overhead. However polling might be more efficient in some scenarios where there are a high volume of frequent updates.

Figure 1: Polling request-response

Long polling

This isn’t true push but is a good substitute. The server won’t respond to a request unless it has data for the client. Upon receipt of data, or a timeout, the client will make a new request.

Figure 2 illustrates the request-response behaviour, and when server events reach the client (including in the event of a timeout).

Figure 2: Long polling

Streaming

In this case the server leaves the response open, meaning the browser stays in the page loading state and can receive updates (Figure 3). This approach may be achieved by hidden iframes, multipart responses to XMLHttpRequest etc. but is often vulnerable to timeouts and inconsistent support between browsers and proxy servers.

Figure 3: HTTP Streaming

HTML5

The HTML5 specification includes two mechanisms: Server Sent Events and WebSockets.

Server Sent Events has a JavaScript API called EventSource and works over traditional HTTP. Server Sent Events open a unidirectional channel from server to client. The main difference between Server-Sent Events and long polling is that SSEs are handled directly by the browser and the user simply has to listen for messages.

WebSockets incorporate full-duplex communication channels so they have been receiving more attention (though this isn’t always good as some browsers have now disabled WebSockets by default due to security concerns). Figure 4 illustrates the request-response behaviour for WebSockets.

Figure 4: WebSocket event propagation

What about Comet / Reverse AJAX?

Comet is used as an umbrella term covering long polling and streaming.

Is that the same as Continuations?

Continuations was the name used by the original Jetty Comet implementation; the Servlet 3.0 specification added asynchronous support that brings a standard API to consolidate the fragmented native container implementations.

And the Bayeux pub/sub protocol?

This was created by the Dojo Foundation (with input from Jetty developers) and is named after the Bayeux tapestry which contains a comet (believed to be Halley’s). It is a specification defining a publish/subscribe mechanism for asynchronous messages primarily over HTTP that is independent of any implementation programming language and separates the transport negotiation from the messaging.

What are the core challenges?

The main problem for browser push stems from the 2 connection limit in the HTTP/1.1 specification (RFC2616) for an HTTP client communicating with a specific HTTP server.

What Grails plugins are available?

The two main plugins are Atmosphere and CometD which utilize the Java projects of the same names. At the time of writing the Atmosphere plugin was more mature and most recently updated (version 0.4.0 supports Grails 1.3.5+ vs. CometD 0.2.2 supporting Grails 1.2.1+) – so this is used for the sample project.

Note that CometD is built on top of the Atmosphere project adding Bayeux protocol support.

Atmosphere fundamentals

Atmosphere describes itself as a portable AjaxPush/Comet and WebSocket Framework. It has a JQuery Plugin client component and provides server components that can run inside any container removing container-specific implementation dependencies from you application. Atmosphere can also be clustered (e.g. using JGroups) but that is beyond the scope of this article.

Atmosphere has a number of modules available, but within Grails we’ll be utilizing the core Atmosphere Runtime. The main component of this module is an AtmosphereHandler which can be used to suspend, resume and broadcast. The action of broadcasting involves distributing an event to one or many suspended responses. The suspended response can then decide to discard the event or send it back to the browser.

Warning: You should understand whether Atmosphere will block a Thread per suspended connection for your target container due to potential performance / capacity implications.

Atmosphere jQuery plugin

The Atmosphere jQuery plugin provides cross-browser support for the ability to subscribe to and publish messages from client-side JavaScript. It has support for different transport modes, chiefly websocket, long-polling, streaming and polling; where websocket effectively works as an ‘auto-detect’ mode and falls back appropriately if the browser doesn’t yet support WebSockets – this means that Atmosphere will choose the best available mode for your browser.

Whilst it provides publish / subscribe functionality, we’ll only focus on the latter.

Grails Atmosphere plugin

The Grails Atmosphere Plugin provides a number of key features:

  • Ability to write handlers using Grails services
  • Automatic generation of configuration files
  • Taglib for including JavaScript resources
  • StratosphereServlet extension to the AtmosphereServlet to register statically configured handlers

Practical example overview

In the June issue we built an application that queued messages for persistence. When a user created a new message, it was placed on a JMS queue and the user was redirected to the list view with a flash message to inform them their message was queued for persistence.

Whilst this was functional, it required the user to refresh their browser to update the list. For usability, it would be much nicer if there was an event-driven update of the page after a message has been created – that is our goal for this exercise.

Figure 5 shows the data flow of the revised sample project using Hohpe EIP notation.

Figure 5: Example data flow

As usual the full source code for the sample Grails project is available on GitHub, the code accompanying this article is at https://github.com/rbramley/GroovyMagJMS

Controller

This is only undergoing a minor change to remove the flash.message from the save operation.

MessageStoreService

A JMS topic will be used for sending an asynchronous event from the MessageStoreService when a message is persisted. The message sending requires def jmsService so that the Spring container auto-wiring can inject our dependency.

We’ll publish the event to the ‘msgevent’ topic using jmsService.send(topic:'msgevent',[id:messageInstance.id, body:messageInstance.body]) once the message instance is persisted.

We’ll also introduce a 2 second sleep prior to persistence to ensure that the message is only delivered via the ‘reverse AJAX’ route.

AtmosphereService

This implements our handler so that we can suspend the connection (Listing 1) and subsequently resume it having invoked the callback with the data (Listing 2).

As Listing 3 shows, the service is also message-driven by subscribing to the JMS ‘msgevent’ topic, converting the map message to JSON and broadcasting it to Atmosphere.

Critically, the service declares an atmosphere mapping which is also used by the client-side JavaScript:

static atmosphere = [mapping:'/atmosphere/messages']

def onRequest = { event ->
   // We should only have GET requests here
   log.info "onRequest, method: ${event.request.method}"
 
   // Mark this connection as suspended.
   event.suspend()
}

Listing 1: Suspending a request

def onStateChange = { event ->
  if (event.message) {
    log.info "onStateChange, message: ${event.message}"
 
    if (event.isSuspended()) {
      event.resource.response.writer.with {
        write "parent.callback('${event.message}');"
        flush()
       }
      event.resume()
    }
  }
}

 

Listing 2: Resuming a connection

static exposes = ['jms']
 
@Subscriber(topic='msgevent')
def onEvent(msg) {
  def payload = msg
  if(msg instanceof Map) {
    // convert map messages to JSON
    payload = msg.encodeAsJSON()
  }
 
  // broadcast to the atmosphere
  broadcaster['/atmosphere/messages'].broadcast(payload)
 
  return null
}

 

Listing 3: Topic message handling

Front end

The front end for the sample is provided by a generated list view, and the head element of this file has been augmented with the addition of the Atmosphere plugin resources taglib <atmosphere:resources/>.

Listing 4 shows the JavaScript (using jQuery) to register the subscription to the Atmosphere handler along with a callback function. Note that the URI would normally be derived from the JavaScript window.location but that has been omitted for simplicity.

var location = 'http://localhost:8080/GroovyMagJMS/atmosphere/messages';
$.atmosphere.subscribe(location, callback, $.atmosphere.request = {transport: 'websocket'});

 

Listing 4: Atmosphere jQuery Plugin subscription

Listing 5 shows the callback function that will parse the JSON of a successful 200 response and append a new row to the message list table (the HTML has been kept very simplistic for clarity).
The callback also handles the case where the JSON parsing fails as Atmosphere sends ‘junk’ data to the server to establish the connection.

function callback(response) {
  if (response.status == 200) {
    var data = response.responseBody;
    if (data.length > 0) {
      try {
        var msgObj = jQuery.parseJSON(data);
        if (msgObj.id > 0) {
          var row = '<tr><td>' + msgObj.id + '</td><td>' + msgObj.body + '</td><td></td></tr>'
          $('tbody').append(row);
        }
      } catch (e) {
        // Atmosphere sends commented out data to WebKit based browsers
      }
    }
  }
}

 

Listing 5: Callback code

Listings 4 and 5 are contained with a <script type="text/javascript"> block and a jQuery $(document).ready(function(){ ... });

Configuration

The sample project hasn’t customized the grails-app/conf/AtmosphereConfig.groovy – you can use this file to pass init-param options to the AtmosphereServlet (see http://atmosphere.java.net/nonav/apidocs/org/atmosphere/cpr/AtmosphereServlet.html).

The Grails Atmosphere Plugin also creates web-app/META-INF/context.xml and web-app/WEB-INF/context.xml – these files are included for portability across servlet containers (e.g. Tomcat uses the META-INF/context.xml and JBoss uses WEB-INF/context.xml).

The plugin also creates some additional XML files and establishes a SiteMesh exclusion at compilation time – this is documented on the plugin wiki: https://bitbucket.org/bgoetzmann/grails-atmosphere-plugin/wiki/Home

In action

We’ll start the application using grails run-app and when then go to the message list at http://localhost:8080/GroovyMagJMS/message/list we’ll see an empty list (Figure 6).

Figure 6: Empty message list

Figure 6: Empty message list

Selecting the ‘New Message’ menu option from Figure 6, will take us to the Create Message form as shown in Figure 7. If we enter a body value and the click on ‘Create’, we will be redirected to the message list (initially empty as per Figure 6 and subsequently updated as per Figure 8).

Figure 7: Create Message form

Figure 8: Dynamically updated message list

If we choose to create another message, the message list when dynamically updated will appear as per Figure 9. In this case, the first record was retrieved from the database (as distinguished by the hyperlinked Id and the presence of the Date Created value).

Figure 9: Second pass message list update

As Figure 10 shows, you can also try this with multiple browsers open on http://localhost:8080/GroovyMagJMS/message/list and observe the subscribers receiving their updates.

Figure 10: Multiple consumers using different browsers

As shown in Figure 11, if you modify the transport setting in Listing 4 from ‘websocket’ to ‘long-polling’ and try it in Chrome you will see the page appears to be endlessly loading, however IE 8 now behaves correctly.

Figure 11: Long-polling browsers

This experiment leads us to the revised version of the client subscription code from Listing 4. Listing 6 now specifies the transport as ‘websocket’ with a fallback transport of ‘long-polling’.

var location = 'http://localhost:8080/GroovyMagJMS/atmosphere/messages';
$.atmosphere.subscribe(location, callback, $.atmosphere.request = {transport: 'websocket', fallbackTransport: 'long-polling'});

 

Listing 6: Client-side subscription with fallback

Summary

We’ve seen why we might need browser push, how it works, and how to implement it in Grails using one of the available plugins and client-side JavaScript.

However note that this example hasn’t tried to tackle authentication as this is typically dependent upon the security framework being utilized. As a next step for those that have authentication requirements, the CometD wiki provides a how-to at http://cometd.org/documentation/2.x/howtos/authentication

 

From http://leanjavaengineering.wordpress.com/2011/11/18/grails-push/

Published at DZone with permission of Robin Bramley, author and DZone MVB.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Tags:

Comments

Robert Craft replied on Thu, 2012/01/26 - 5:34am

I got my AtmosphereHanlder working as a GrailsController but when I use dependency injection for a grails service the service is always null in the onRequest method. Have you tried to use a Grails Service inside the onRequest method when the handler is a Controller.

Spring Framework

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.