Take control of the servlet environment, Part 1

Invisibly extend the functionality of the servlet API

The ubiquitous servlet flourishes in all areas of Web development. Authors devote entire books to servlets. Almost every major Web server and application server supports them, if not directly, then through a plethora of third-party plug-ins. An integral part of the Java 2 Platform, Enterprise Edition (J2EE) framework, they provide the foundation for JavaServer Pages. If you are a Java fanatic, and your project has a Web interface, you use servlets.

Servlets are a simple concept, and the Java Servlet API reflects that. As Figure 1 illustrates, a Request object encapsulates all the data that the client passes to the server; a Response object encapsulates all the data that the server passes to the client. And a Session object stores any stateful data that needs to persist between the handshakes. That's all there is to a servlet ... almost.

Figure 1. The flow of information in a servlet

Sun's Java Servlet API does an excellent job of hiding all the dirty work from the programmer, leaving the servlet engine to manage it instead. However, the programmer occasionally needs to peek behind the curtain and twiddle the knobs. Sometimes the programmer wants to override the engine's functionality. This series will cover these issues. Over the next few months, we will explain how you can take control of your servlet environment. We will also exploit that control and demonstrate some useful real-world tricks that can increase the performance and scalability of your projects.

TEXTBOX: TEXTBOX_HEAD: Take control of the servlet environment: Read the whole series!

Most servlet engines store the user's session data in memory. This prevents you from implementing a truly load-balanced farm of Web servers because each visitor must be sticky to the server that assigned him or her a session. Once a load-balancing mechanism directs a visitor to a specific server, it keeps sending that visitor back to the same server; thus, the visitor becomes stuck to a single server, rather than being rotated or balanced across all servers. To solve this problem, do you need to migrate to another servlet engine vendor or purchase a third-party solution? Absolutely not. We'll explain why in this series.

Have you ever run into the cookie subdomain bug? For example, if you access a cookie-writing page at http://drum.rudiment.net/, then hit the same page using the address http://www.drum.rudiment.net/, you will have two cookies with the exact same name. When you return to http://drum.rudiment.net/ and the page requests the cookie that it originally wrote, it's a toss-up as to which version the engine will return. We will crack that problem as well in this series of articles.

In Part 1, we will introduce the foundation for inserting an invisible layer of logic between the servlet engine and your servlet code. This layer will use simple and common design patterns and open the doors to many clever and powerful enhancements.

Even though Java is a young language, it already features numerous legacy issues. To avoid confusion, we must point out that the code discussed in these articles was written for, and compiled and tested with, the following API versions:

  • JDK 1.1
  • JDBC 1.22
  • Servlet 2.1.1

Wrappers

The secret to taking control of the servlet environment is to wrap the API. Essentially, you build an invisible wall between your servlets and the servlet engine. Because it exposes (or implements) the same API, the wrapper is invisible to the servlet and the engine.

For the purposes of this article, we will refer to the set of wrapper classes as RSEF (the rudimental servlet extension framework). This title has little meaning other than the fact that the package structure contains the word rudiment. Mainly, the acronym will eliminate the need for us to compose clever pronouns each time we refer to the framework.

From the programmer's point of view, RSEF is virtually transparent. The only RSEF-specific class that the programmer must know about is HttpServlet. In order to take advantage of RSEF, extend the RSEF HttpServlet like so:

public class MyServlet extends net.rudiment.servlet.HttpServlet

Normally, your servlet would extend the HttpServlet in the javax.servlet.http package rather than the one in net.rudiment.servlet. But the RSEF version extends the HttpServlet in javax.servlet.http and thus sandwiches itself between your servlet and the engine. This bootstraps RSEF without cluttering your code with proprietary API calls.

Figure 2. The framework sandwiches itself between your servlets and the engine

Under the covers

Once in place, the RSEF can work its magic. The first and fundamental task of RSEF is to wrap the core servlet objects. As stated above, the core objects are the Request, the Response, and the Session. So RSEF needs to have a RequestWrapper, a ResponseWrapper, and a SessionWrapper.

Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides (the so-called Gang of Four), in their book Design Patterns (see Resources), define this behavior as the Decorator pattern. Their definition of the this pattern reads, "Attach additional responsibilities to an object dynamically." The Decorator pattern derives its strength by aggregating the target object (dynamic) rather than inheriting (static). When your servlet interacts with a Request, Response, or Session, it will actually talk to the respective wrapper classes. However, since the wrapper classes implement the same interfaces as their aggregates, the interaction is completely transparent. To take advantage of RSEF, you don't need to change your code.

In some cases, the wrapper classes also act as a Proxy. A Proxy pattern "provide[s] a surrogate ... for another object to control access to it," according to the Gang of Four. You'll see this demonstrated in future installments of this series. To see how a similar problem is tackled with the Adapter pattern, see the recent JavaWorld article entitled "Use Microsoft's Internet Information Server as a Java Servlet Engine" (direct link available in Resources).

Figure 3. How the wrappers are organized

As you can see from Figure 3, wrapping Session requires a bit more work. Because Session is an aggregate of Request, we can't simply wrap the Request object and be done with it. We have to wrap the Session object inside the Request wrapper. Sound simple enough? Don't be fooled: it's about to get hairy.

Put it all together

In order to pull off some of the tricks we will discuss in future articles, each wrapper has to talk to its brethren. The RequestWrapper doesn't just aggregate the engine's Request and the SessionWrapper; it also needs a handle to the ResponseWrapper. Likewise, the ResponseWrapper must communicate with the RequestWrapper. The SessionWrapper needs references to both the RequestWrapper and the ResponseWrapper.

This hodgepodge of relationships creates a rather nasty order-of-operations problem. The RequestWrapper contains the SessionWrapper, and therefore must instantiate before the SessionWrapper. In fact, the RequestWrapper shoulders the responsibility of wrapping the Session. How then do you mix and match the different flavors of wrappers?

For example, say you want to use the RequestWrapper that handles the cookie subdomain bug with the SessionWrapper that stores its state in a database. Assuming that this SessionWrapper is one of four possible implementations, you must have four different versions of that particular RequestWrapper, one for each possible session-wrapping scenario. As the number of RequestWrapper and SessionWrapper implementations increase, the number of mappings become ridiculous. It's a cut-and-paste nightmare and completely unacceptable.

If you know which RequestWrapper and SessionWrapper combination you want to use, and you are certain such a combination will accommodate the project throughout its life cycle, you can implement a hardcoded mapping. But doing so unnecessarily sacrifices flexibility.

If, on the other hand, you are unsure about which combination you wish to use and whether that mapping will be applied globally to the product, then you need to control the mappings at runtime. But how? One alternative is to use flags: simple static final values that trigger a RequestWrapper to use a specific SessionWrapper upon execution. Functional? Yes. Elegant? Not quite. There must be a better way ... and there is!

The wonderful Gang of Four comes to our aid again. The pattern we need this time is the Factory Method. As defined in Design Patterns, the Factory Method is "...an interface for creating an object ... [that] lets the class defer instantiation to the subclass." In English: rather than telling the RequestWrapper which SessionWrapper to use, you give the RequestWrapper a black box that automagically creates SessionWrappers of whichever type you desire.

The black box is a factory. Factories simply produce objects, usually of a single specific type but with a generic interface. Just as a toy factory produces toys, a SessionWrapperFactory manufactures SessionWrappers. The SessionWrapper's creation and function are invisible to the RequestWrapper. The RequestWrapper simply asks the factory for a SessionWrapper instance, and the factory obliges.

The code

By now you might be thoroughly confused, so let's jump straight into the code. The code below illustrates the wrappers' constructor signatures:

public ResponseWrapper( HttpServletRequest request, HttpServletResponse
response )
public RequestWrapper( HttpServletRequest request, ResponseWrapper
response, SessionWrapperFactory factory )
public SessionWrapper( RequestWrapper request, ResponseWrapper response,
HttpSession session )

The ResponseWrapper requires handles to the raw -- meaning the engine's version -- Request and Response objects. The RequestWrapper requires a handle to the raw request and the wrapped response. It also needs a SessionWrapperFactory, which I'll explain shortly. The SessionWrapper then requires handles to the wrapped versions of the Request and the Response, as well as a reference to the raw version of the Session (which it wraps).

Why such a convoluted train wreck of relationships? You'll discover the answer in future articles. For now, simply understand that for each wrapper to perform its tricks, it needs handles to its brethren wrappers, and, in some cases, the raw counterparts.

The factory explained

Now, why does our code need a SessionWrapperFactory? It provides our key to the Factory Method pattern explained earlier. The SessionWrapperFactory allows subclasses of the RSEF HttpServlet to define which SessionWrapper type the RequestWrapper should use.

Here's the SessionWrapperFactory interface:

package net.rudiment.servlet;
import javax.servlet.http.HttpSession;
public interface SessionWrapperFactory
{
    public SessionWrapper wrapSession( RequestWrapper request,
ResponseWrapper response, HttpSession session );
}

This interface features only one little method, a method responsible for instantiating the desired SessionWrapper. You'll learn later how to implement the method and propagate the factory into the RequestWrapper.

Bootstrap the framework

With all the pieces in place, we can now analyze the assembled puzzle. The RSEF HttpServlet that your servlet extends contains the big picture. The RSEF version manages the wrappers' instantiation and assembly. The service() method performs all the work -- the main point of contact between the servlet engine and the executing servlets. service() simply wraps the response, wraps the request, and then passes the wrappers up to the superclass, the engine's instance. Here's the code:

public void service( HttpServletRequest request, HttpServletResponse
response )
    throws ServletException, IOException
{
    ResponseWrapper wrappedResponse = wrapResponse( request, response );
    RequestWrapper wrappedRequest = wrapRequest( request, wrappedResponse
);
    super.service( wrappedRequest, wrappedResponse );
}
public abstract RequestWrapper wrapRequest( HttpServletRequest request,
ResponseWrapper response );
public abstract ResponseWrapper wrapResponse( HttpServletRequest request,
HttpServletResponse response );

Aha! What are those two abstract methods doing there? This framework isn't so invisible after all, now, is it? To answer the question, let's take the sandwich analogy a little further. If the servlet engine represents one slice of bread and your servlet the other slice, then RSEF signifies the meat in the middle. Wouldn't that sandwich taste a whole lot better with a slice of cheese? That slice of cheese is your bootstrap layer, where you define the wrapper combinations that you wish to use.

Figure 4. The bootstrap servlet

The two abstract methods in the RSEF version of HttpServlet give you the power and flexibility to write your own recipe of servlet engine goodness. Now that we've come full circle, you are ready to implement the scenario described above: the cookie-bug-handling request wrapper and the database-storing session wrapper. Combine the two in your BootStrap class with the following code:

1 2 Page 1