Enterprise Comet: Awaken the Grizzly!

There's a common misconception among many end users, consumers, and developers that AJAX is the ultimate solution for the Web and that it can provide all the same functionality as a rich desktop solution. Sure, AJAX can cover most of our expectations for a rich client, mimicking functionality provided by a desktop application, but there's still one area that has yet to be fully integrated ­ scalable server-initiated message delivery.

With server-initiated message delivery, all end users of a particular application are simultaneously notified of any changes to the application state, e.g., at the stock exchange a stock is dropping fast and the trading application has to inform all traders about the sudden change in price.

Server-Initiated Message Delivery
Let's use the trading application as an example for server-initiated message delivery. Each broker has his own preferences in stocks and bonds and requires instant notification of changes in the market. A desktop client for a distributed enterprise application typically registers interest in specific kinds of server messages so the server can notify the desktop client when they occur. This lets the desktop application efficiently use the network on a need-to-know basis instead of having the client actively ask for information.

This approach won't work for a traditional Web-based AJAX application since the server can't initiate a direct connection to the browser because of browser security, firewalls, and Web proxies.

Sure, it's possible with AJAX to "notify" a Web client that a change has occurred on the server using a technique called "polling." By creating a Web application that polls every now and then the end user believes that he's been notified by the server, when in fact it's repeatedly asking for updates like any child asking for candy ­ Can I get it now? Can I get it now? Can I get it now? You get the picture.

Of course, this impacts network bandwidth since there's traffic for each polling request even when no updates are available from the server. We need a way for Web clients to register interest in certain types of messages and then let the server initiate delivery of those messages, pushing them to each browser.

There's a Twist
OK, OK, OK, there is a twist ­ you can't actually "push" messages to a Web-based AJAX application unless you maintain an open connection from the client to the server. However, a thread is kept alive on the server for each open connection so messages can be delivered to the browser immediately.

The fact that you'd even try to maintain an open connection per user to a server would have heads rolling down the corridors in most IT departments. Just imagine thousands, or even hundreds of thousands of connections, using the thread and process pooling models provided by most Web Servers today, keeping each thread alive on the server just to be able to send a message to the client ­ it doesn't scale! Let's come back to that one later.

HTTP Connection Limitations
Today most frameworks leveraging AJAX are using the XMLHttpRequest object, which allocates an HTTP connection for the duration of the request. The HTTP 1.1 specification recommends that browsers support a maximum of two open connections to the hosting server. This presents a problem for highly interactive AJAX Web applications, especially on Microsoft Internet Explorer, which enforces this recommendation.

If there are more than two AJAX frameworks or components using the XMLHttpRequest on the same Web page then there will probably be contention for the two open connections, causing requests to queue. The result will be blocked and ineffective communication, defeating the main purpose of having AJAX on the Web page. There's a need to share server communications over two HTTP connections at most.

Next-Generation Web Communication
The main reason a Web server allocates one thread per connection is because it expects the request to be highly active and short-lived. However, maintaining an open connection to the server is extremely long-lived and activity is mostly dormant. We also need a way to meaningfully share server communications to address the browser connection limit. Thankfully, there are several projects in process addressing the limitations of traditional AJAX Web applications. Let's have a look at some of these projects.

Comet - The Never-Ending Request
First we need a way to create message-driven Web applications that require the server to notify the client about server-side events. This is where Comet comes in as described by Wikipedia:
Comet is a programming technique that enables Web servers to send data to the client without having any need for the client to request it.

Comet provides the means for the server to initiate a response to a client request, and later send a message to that client using the same response ­ for example, in the browser a message will "just" appear. Comet also provides clients with a way to register interest in specific types of messages. When the server publishes a message, it's delivered only to clients that previously registered interest in that kind of message.

Comet doesn't solve everything; in fact it creates some new problems. Using Comet means lots of outstanding requests, ideally one per client, and introduces similar scalability issues at the server as the AJAX polling technique. Keeping a separate thread allocated for each open request will exhaust the server's resources, so for this model to work properly, the Web server has to handle multiple requests without allocating one thread per request.

Asynchronous Request Processing
The idea behind Asynchronous Request Processing (ARP) is to manage incoming servlet requests asynchronously. Rather than committing a separate Java thread to synchronously process each servlet request, a single thread leverages Java NIO to detect when new information needs to be written to the response of any outstanding request. This technique provides a huge scalability win for the Comet use case, giving excellent performance even when there are a large number of outstanding, mostly dormant, requests at the server.

Although there's no standard defined for ARP several teams and projects are providing ARP solutions, such as Jetty Continuations and Grizzly. We take our hats off to these visionary teams providing everyday developers with ARP and Comet solutions.

Note: There's hope that ARP will be standard in the Servlet 3.0 and Java EE 6 specifications.

The Comet Messaging Protocol
With Comet and ARP we now have the means to build message-driven Web applications but, as mentioned earlier, W3C recommends at most two active connections from the browser to the Web server at any one time. Therefore, we'd prefer to have a single active connection being used for Comet notifications, leaving the other connection free to download images, CSS, and JavaScript or communicate with the server. This requires a way to manage multiple logical channels of information over a single shared Comet notification response.


The Bayeux protocol, named for the Bayeux Tapestry with its picture of Halley's comet, provides a solution to this problem. Bayeux formalizes the exact protocol that clients should use to initiate the Comet connection and subscribe and unsubscribe from hierarchically named Bayeux channels.

/stocks
/stocks/ORCL
/stocks/MSFT
/bonds

When a message is delivered to a channel at the server, it's only published to those clients that have previously subscribed to that channel. Due to the hierarchical naming scheme, messages sent to a channel named /stocks/ORCL will also be delivered to clients subscribed to the /stocks channel.

Dojo and Bayeux
The Bayeux protocol is an effort by the Dojo Foundation to simplify the use of Comet. Bayeux development is hosted as a sibling project to the Dojo Toolkit, which already includes client-side Bayeux support in JavaScript.

OK, we might be jumping ahead here, but we feel that Bayeux is likely to become the de facto standard for Comet messaging because the Dojo Toolkit has gotten tremendous backing from the developer community and because the protocol is language-neutral. Bayeux has a pluggable transport layer so any agreed wire syntax can be used to marshal messages between the client and server, or vice versa and there are already implementations in multiple languages.

ARP and Comet Architecture
Our sample solution, the trading application, requires sophisticated integration with external business systems, such as business services updating the stock prices.

The Trading Application
Our message-driven distributed enterprise application needs an ARP solution for the Web client, so we're going to use Grizzly as our server-side ARP and Comet runtime implementation. Grizzly provides ARP support in Java and is currently snoozing under the covers of Project Glassfish, version 2. (http://glassfish.dev.java.net). For our sample application we also used the Dojo Toolkit (http://dojotoolkit.org/), which already includes client-side Bayeux support in JavaScript.

Note: When this was written, there was no server-side Bayeux protocol support in Grizzly so for this article we used our own Bayeux servlet. We intend to convert this to a Bayeux weblet and make it available for download from the Weblets Project (http://weblets.dev.java.net).

We'll use a simple UI to demonstrate the trading application's server-initiated message-delivery mechanism. The UI contains three buttons each capable of initiating a request to publish a stock price update message. The server will immediately notify all end users who subscribed to the same stock. The page source for this simple trading application is in Listing 1.

The page contains standard Dojo Toolkit initialization via the dojo.js library and imported the required Dojo packages for dojo.io.cometd and dojo.event.topic:

function setupComet() {
    cometd.init({}, "/comet-war/bayeux");
    cometd.subscribe("/stocks");
    dojo.event.topic.subscribe("/cometd/stocks/ORCL","onStockUpdate");
    dojo.event.topic.subscribe("/cometd/stocks/SUNW","onStockUpdate");
}

When the page loads, we initialize the Dojo cometd support with the URL to the BayeuxServlet, "/comet-war/bayeux." In this sample we then subscribe directly to the "/stocks" Bayeux channel, as well as the local event topics "/cometd/stocks/ORCL" and "/cometd/stocks/SUNW." These two local event topic subscriptions could easily be done by the end user after the application is launched, but for now we can treat them as default subscriptions.

Let's have a look at the three buttons; each one in the body is configured to publish an event to a specific Bayeux channel at the server:

    <button onclick="cometd.publish(Œ/stocks/ORCL',
       {Œname':'ORCL', Œprice': 19.75});" >SUNW 19.75
    </button>

When a message is published to the server on a Bayeux channel, such as "/stocks/ORCL," then it's delivered to all clients subscribed to the "/stocks/ORCL" channel or the "/stocks" channel. If a message is sent to /bonds then it's delivered to all clients subscribed to /bonds. In this example, the trading application only gets messages for /stocks. This server-side filtering ensures that unwanted messages aren't sent to clients.

When a message is delivered to a client, the Dojo event system delivers it to any locally registered subscribers in the page. By default, local event topics are named by prefixing the Bayeux channel name with "/cometd." For example, if a message is received from the server via the Bayeux channel "/stocks/ORCL" then it's published to local subscribers using the topic name "/cometd/stocks/ORCL." Local event topic subscription is shown in the setupComet() JavaScript function:

function setupComet() {
    cometd.init({}, "/comet-war/bayeux");
    cometd.subscribe("/stocks");
    dojo.event.topic.subscribe("/cometd/stocks/ORCL","onStockUpdate");
    dojo.event.topic.subscribe("/cometd/stocks/SUNW","onStockUpdate"); }

Figure 1 shows two clients running the code used in the page source sample.

In the second set of screen shots shown in Figure 2 we've clicked on all three buttons in the application running in Firefox and since we're using Comet this causes both clients to be updated with new information.

As you can see, there are only two messages displayed in each client.


The Dojo event system delivers the messages to the locally registered subscribers in the page. In this case Microsoft's stock hasn't been displayed because we didn't subscribe to /cometd/stocks/MSFT locally. Notably, if we changed the cometd subscription from /stocks to /stocks/ORCL and /stocks/SUNW then the observed behavior would be the same, but the /stocks/MSFT message would be filtered at the server, not locally at the browser.

The Internal Process
The Grizzly Comet implementation provides a CometEngine that's used to create a CometContext to which clients can register a handler. The handler is responsible for initiating the response and notifying each client about new messages that match the client's channel subscriptions. All handlers registered to the same CometContext will be simultaneously notified when a new event is delivered to the CometContext.

In our trading application, all Bayeux requests go to a BayeuxServlet at /bayeux in a Web application with context root /comet-war.

1.  Initial Request
This is a regular request to have the page rendered in the Web client, HTML, and Dojo Toolkit JavaScript including Bayeux protocol support.

2.  Bayeux Handshake Request
In our trading application Bayeux lets us create one channel per stock.

We're still only using one HTTP connection and we can use Bayeux to listen for events targeting a particular area of our application ­ in this case a stock. If we didn't have Bayeux we'd either have to invent our own publish and subscribe protocol, or else go back to polling the server because the browser connection limitations would prevent us from having more than two open HTTP connections to get the Comet notifications.

Request URI = /comet-war/bayeux
Request Payload = handshake message

The Bayeux handshake request sends a message to the /meta/handshake channel to indicate possible connection types and gets a response from the server indicating supported connection types, their recommended configuration, and a client identifier that's used for subsequent communication with the BayeuxServlet. In this case we use an iframe connection type.

3.  Bayeux Connect Request
Since we're using an ARP server each request will register a key containing information about the request. This will let the ARP release resources by releasing threads currently not used.

Request URI = /comet-war/bayeux
Request Payload = connect message

It sends a message to /meta/connect channel to begin the long-lived Comet request. Using Grizzly APIs from within the BayeuxServlet, the connect request causes the client-specific CometHandler to be registered with this trading application's CometContext.

If the server-side CometHandler times out while the trading application is still active, the client sends a Bayeux reconnect request to establish a new long-lived Comet notification request.

4.  Bayeux Subscribe Request
In our application we subscribe to the /stocks channel to get browser notifications for all stocks.

Request URI = /comet-war/bayeux
Request Payload = subscribe message

The Bayeux subscribe request sends a message to the /meta/subscribe channel, subscribing the client to the /stocks channel. Using Grizzly APIs from within the BayeuxServlet, each subscribe request causes the list of per-client subscriptions to be updated for this CometContext. If the long-lived connect request is still open then this subscribe request is sent to the server using the remaining available browser connection.

5.  ORCL Stock Price Changes
The Oracle stock price changes and a message is published to the /stocks/ORCL Bayeux channel with an application-defined structure, e.g., { Œname':'ORCL', Œprice': 19.75 }.

Using Grizzly, we get a CometContext for this application and send a message including channel name. All CometHandlers registered for this CometContext will get this message, but only those subscribed to the /stocks/ORCL or /stocks channel will actually propagate the message to the browser. If the long-lived connect request is still open then this unsubscribe request is sent to the server using the remaining available browser connection.

6.  Bayeux Unsubscribe Request
There's also a way to unsubscribe from channels and with a more sophisticated trading application the end user would be able to unsubscribe from getting updates for a stock, for example, Sun Microsystems (SUNW).

Request URI = /comet-war/bayeux
Request Payload = unsubscribe message

A message would be sent to the /meta/unsubscribe channel, to unsubscribe from /stocks/SUNW channel. Using Grizzly APIs from within the BayeuxServlet, the unsubscribe request updates the list of subscribed channels for this client on this CometContext.

Conclusion
Enterprise Comet is definitely the real deal. There's still work to be done before we have a complete Enterprise solution for developing message-driven Web 2.0 applications, but with the techniques and technologies above we've made a good start. Not only that, but the Dojo Toolkit also includes a rich set of DHTML widgets, and client-side Bayeux protocol support, which provides a great foundation for building Comet-based Web applications.

The major thing missing now is a way to realize Comet in the context of Java EE for technologies such as JSF, JMS, and EJB 3. More about this in our next article, which will show how to create a Comet-based JavaServer Faces component.

© 2008 SYS-CON Media