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.
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!
- Part 1: Invisibly extend the functionality of the servlet API
- Part 2: Alternatives to servlet session management
- Part 3: Beware of the cookie monster
-
:END_TEXTBOX
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 , then hit the same page using the address https://www.drum.rudiment.net/, you will have two cookies with the exact same name. When you return to 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.
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).
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.
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:
public class BootStrap extends net.rudiment.servlet.HttpServlet
{
public net.rudiment.servlet.ResponseWrapper wrapResponse(
HttpServletRequest request, HttpServletResponse response )
{
return( new net.rudiment.servlet.cookie.ResponseWrapper( request,
response ) );
}
public net.rudiment.servlet.RequestWrapper wrapRequest(
HttpServletRequest request, net.rudiment.servlet.ResponseWrapper response
)
{
return(
new net.rudiment.servlet.cookie.RequestWrapper(
request,
response,
factory
)
);
}
private static final net.rudiment.servlet.SessionWrapperFactory
factory =
new SessionWrapperFactory()
{
public net.rudiment.servlet.SessionWrapper wrapSession(
net.rudiment.servlet.RequestWrapper request,
net.rudiment.servlet.ResponseWrapper response, HttpSession session )
{
return(
new
net.rudiment.servlet.session.database.SessionWrapper( request, response,
session )
);
}
};
}
The wrapResponse()
method instantiates the RequestWrapper
from the cookie subpackage. The wrapRequest()
method completes the same. The SessionWrapperFactory
instance is an anonymous inner class; since it’s a stateless object, you don’t need to create a new one each time the servlet executes (wasting memory and clock ticks), so we designated it a static final member.
The framework is now in place. You’ve configured your bootstrap. Your last step is to extend the bootstrap servlet instead of the RSEF version, like so:
public class MyServlet extends BootStrap
And you’re ready to go! It couldn’t possibly be simpler … or could it? Stay tuned to find out!
Conclusion
The framework we introduced here will open the doors to a multitude of performance and scalability enhancements — completely transparent to your existing servlet-based project. In the next installment, we will discuss session management and touch on database storage. Part 3 will focus on the cookie-domain bug. Then you will begin to see the real power of RSEF.