In Part 1 of this article we explored the concepts of the Asynchronous Web and how it can revolutionize the way we interact with web applications, and ultimately each other. We gained an understanding of the low-level long polling mechanism, and explored some of the intricacies of managing long-lived HTTP connections associated with it. We also determined that the Java EE specification process is lagging in both Servlet-based Asynchronous Request Processing (ARP), and at the presentation layer programming model. We posed a number of questions that need answers, and we will now examine an approach to delivering Asynchronous Web capabilities through extensions to existing Java EE technologies. While the following discussion is conceptual in nature, concrete implementations of these concepts have been achieved in the ICEfaces open source project.
Let's start with the programming model
Regardless of the intricacies of the underlying mechanism that supports asynchronous push, the programming model that the developer is exposed to should be intuitive and natural to use, or it won't be used at all. JavaServer Faces Technology (JSF) is the most natural place to start, as it provides a standards-based request processing lifecycle as a foundation for the programing model. This lifecycle begins with a request, from which values are applied and validated, the model is updated, the application is invoked, and finally the response is rendered. This lifecycle is client initiated, but for push we need to be able to trigger an update based on some server-initiated mechanism, as illustrated below.
From the developers perspective, you simply want trigger points in your application to request a render when it is necessary to push new presentation and rely on the underlying mechanism to handle the details. We will now examine some of these details.
Incremental updates required
The first thing that must be avoided when pushing updates is doing a full page refresh with every server-initiated render request, as this would be completely disruptive to the user experience. While the JSF 2.0 specification includes incremental rendering capabilities, stock JSF 1.2 provides no such mechanism, so an extension is required to achieve it. Ajax techniques have been used successfully to provide incremental updates, and can be combined with server-initiated push rendering to achieve the desired capabilities. In some cases additional markup in the page is used to indicate what elements of the page need to be updated under various conditions, but this can dramatically increase the burden on the developer to achieve efficient and effect incremental page updates. ICEfaces provides an incremental update mechanism based on a technique call Direct-to-DOM rendering where the framework determines precisely the set of incremental changes required for an update. The major advantage of this approach is that no developer intervention is required to achieve proper incremental rendering of the push updates. The basic push mechanism with incremental updates is illustrated below.
Need a request context
While conceptually straightforward, forcing the RenderResponse phase to run outside a normal request-initiated lifecycle poses a challenge. Specifically, the JSF lifecycle maintains a FacesContext object containing the state associated with the current request, and it is necessary to create a synthetic request context that the JSF lifecycle can execute with. This synthetic context can be created programmatically, as it is in ICEfaces 1.x, but constructing the necessary state is quite involved. Integration with other middleware like Seam or Spring Web Flow introduces further complications, as those technologies expect specific state in the request context that must also be synthesized. An alternate approach is to have the push mechanism alert the client, using the blocking connection, and then have the client make a request to fetch the updates. This is somewhat less efficient as it requires an additional request, but allows the JSF lifecycle to build the request context naturally. This mechanism, which is illustrated below, is implemented in ICEfaces 2.0.
Avoid Rendering Mayhem
One can imagine a complex system with multiple trigger points for push generating render requests across large numbers of clients. The JSF RenderResponse phase is computationally the most expensive part of the lifecycle, so scalability of the implementation will be compromised when excessive rendering is performed. It is necessary to strictly manage the rendering process, coalescing render requests and maximizing throughput. Session group management is also an important aspect of managing the push rendering mechanism. Typically, groups of clients that share state will be impacted by the same push triggers. Being able to organize these clients into groups, and have trigger points generate render requests across groups can greatly simplify the developer's task of implementing trigger points.
Browser connection sharing
As discussed in Part 1 of this article, maintaining separate blocking connections for multiple views onto the same web application or multiple applications deployed to the same DNS domain, so it is necessary to share a single connection across the views. In order to share the blocking connection, some global state must be available to each view (browser window or tab) in the same domain. Cookies provide a reliable cross-browser mechanism for state sharing, and are leveraged in the solution described here. Various strategies could be considered, but one of the simplest mechanisms is to assign a master view that handles the blocking connection for all views. The first view instance will set a cookie indicating that it is the master, and all incoming push update responses will be handled by this view. A second cookie can then be used to provide view-specific updates, which slave views can then poll for updates and apply them as they become available. The master/slave logic must support handing off control when the current master view is disposed of. This can also be achieved with a cookie that is polled by all slaves to determine when a new master is required. When the master view is destroyed the cookie can be set to indicate so, and another view can pick up as master.
Server connection sharing
We will now turn our attention to the server side of the blocking connection. Within a single domain you could have multiple applications deployed, or multiple portlets spanning multiple web applications, and will need some sort of central management of the shared blocking connection. A Servlet can be used to handle the blocking connection and communicate with the various applications and portlets to receive push updates and pass them back to the browser over the blocking connection. In ICEfaces, the Push Server does precisely this.
Add a little ARP
Our architecture now includes a central Push Server to manage the blocking connection, but as we discussed in Part 1 of this article, the standard Servlet implementation will exhibit thread-level scalability issues, as each session requires a thread to manage the blocking connection. The Servlet 3.0 specification includes provisions for ARP, so a ubiquitous solution is on the horizon, but in the here and now we have to deal with proprietary solutions across the spectrum of applications servers. This means that you need an environment-specific implementations of the Push Server, or you need a mechanism to automatically detect native ARP support and adapt accordingly. The following open source application servers provide ARP APIs:
- Apache Tomcat 6 Comet Processor
- Sun Glassfish Grizzly Plugin
- Jetty 6 Continuations
Bring it all together
Now bring all of these details together and we have a robust architecture that handles all of the intricacies associated with push, as illustrated below. Couple this with a straightforward programming model based on JSF, and you are well positioned to deliver on the promises of the Asynchronous Web today.
Back to the original question
Can Java EE deliver the Asynchronous Web? Throughout the preceding discussion we have managed to answer the question affirmatively, but it is clear that the Java EE standards are lagging in a number of areas. We have also seen that a robust solution tends to be rather involved, so look to industry-proven approaches that allow you to focus on application development, not low-level push infrastructure development. After all, it is the applications themselves that will revolutionize the web. Also, look to the evolving Java EE standards activities where pressure is being applied to incorporate asynchronous push capabilities. As push becomes table stakes in modern web applications, the standards will catch up - just maybe not fast enough for most of us.