How Tomcat Works

OverView

A container is a module that processes the requests for a servlet and populates the response objects for web clients. A container is represented by the org.apache.catalina.Container interface and there are four types of containers: Engine, Host, Context, and Wrapper. This chapter covers Context and Wrapper and leaves Engine and Host to Chapter 13. This chapter starts with the discussion of the Container interface, followed by the pipelining mechanism in a container. It then looks at the Wrapper and Context interfaces. Two applications conclude this chapter by presenting a simple wrapper and a simple context respectively.

The Container Interface

A container must implement org.apache.catalina.Container. As you have seen in Chapter 4, you pass an instance of Container to the setContainer method of the connector, so that the connector can call the container's invoke method. Recall the following code from the Bootstrap class in the application in Chapter 4.

HttpConnector connector = new HttpConnector();
SimpleContainer container = new SimpleContainer();
connector.setContainer(container);

The first thing to note about containers in Catalina is that there are four types of containers at different conceptual levels:

  • Engine. Represents the entire Catalina servlet engine.
  • Host. Represents a virtual host with a number of contexts.
  • Context. Represents a web application. A context contains one or more wrappers.
  • Wrapper. Represents an individual servlet.

Each conceptual level above is represented by an interface in the org.apache.catalina package. These interfaces are Engine, Host, Context, and Wrapper. All the four extends the Container interface. Standard implementations of the four containers are StandardEngine, StandardHost, StandardContext, and StandardWrapper, respectively, all of which are part of the org.apache.catalina.core package.

Figure 5.1 shows the class diagram of the Container interface and its sub-interfaces and implementations. Note that all interfaces are part of the org.apache.catalina package and all classes are part of the org.apache.catalina.core package.

Figure 5.1: The class diagram of Container and its related types Note All implementation classes derive from the abstract class ContainerBase.

A functional Catalina deployment does not need all the four types of containers. For example, the container module in this chapter's first application consists of only a wrapper. The second application is a container module with a context and a wrapper. Neither host nor engine is needed in the applications accompanying this chapter.

A container can have zero or more child containers of the lower level. For instance, a context normally has one or more wrappers and a host can have zero or more contexts. However, a wrapper, being the lowest in the 'hierarchy', cannot contain a child container. To add a child container to a container, you use the Container interface's addChild method whose signature is as follows.

public void addChild(Container child);

To remove a child container from a container, call the Container interface's removeChild method. The remove method's signature is as follows.

public void removeChild(Container child);

In addition, the Container interface supports the finding of a child container or a collection of all child containers through the findChild and findChildren methods. The signatures of both methods are the following.

public Container findChild(String name);
public Container[] findChildren();

A container can also contain a number of support components such as Loader, Logger, Manager, Realm, and Resources. We will discuss these components in later chapters. One thing worth noting here is that the Container interface provides the get and set methods for associating itself with those components. These methods include getLoader and setLoader, getLogger and setLogger, getManager and setManager, getRealm and setRealm, and getResources and setResources.

More interestingly, the Container interface has been designed in such a way that at the time of deployment a Tomcat administrator can determine what a container performs by editing the configuration file (server.xml). This is achieved by introducing a pipeline and a set of valves in a container, which we will discuss in the next section, "Pipelining Tasks".

Note: The Container interface in Tomcat 4 is slightly different from that in Tomcat 5. For example, in Tomcat 4 this interface has a map method, which no longer exists in the Container interface in Tomcat 5.

Pipelining Tasks

This section explains what happens when a container's invoke method is called by the connector. This section then discusses in the sub-sections the four related interfaces in the org.apache.catalina package: Pipeline, Valve, ValveContext, and Contained.

A pipeline contains tasks that the container will invoke. A valve represents a specific task. There is one basic valve in a container's pipeline, but you can add as many valves as you want. The number of valves is defined to be the number of additional valves, i.e. not including the basic valve. Interestingly, valves can be added dynamically by editing Tomcat's configuration file (server.xml). Figure 5.2 shows a pipeline and its valves.

Figure 5.2: Pipeline and valves

If you understand servlet filters, it is not hard to imagine how a pipeline and its valve work. A pipeline is like a filter chain and each valve is a filter. Like a filter, a valve can manipulate the request and response objects passed to it. After a valve finishes processing, it calls the next valve in the pipeline. The basic valve is always called the last.

A container can have one pipeline. When a container's invoke method is called, the container passes processing to its pipeline and the pipeline invokes the first valve in it, which will then invoke the next valve, and so on, until there is no more valve in the pipeline. You might imagine that you could have the following pseudo code inside the pipeline's invoke method:

// invoke each valve added to the pipeline
for (int n=0; n < valves.length; n++) {
  valve[n].invoke( ... );
}
// then, invoke the basic valve
basicValve.invoke( ... );

However, the Tomcat designer chose a different approach by introducing the org.apache.catalina.ValveContext interface. Here is how it works.

A container does not hard code what it is supposed to do when its invoke method is called by the connector. Instead, the container calls its pipeline's invoke method. The Pipeline interface's invoke method has the following signature, which is exactly the same as the invoke method of the Container interface.

public void invoke(Request request, Response response) throws IOException, ServletException;

Here is the implementation of the Container interface's invoke method in the org.apache.catalina.core.ContainerBase class.

public void invoke(Request request, Response response) throws IOException, ServletException {
    pipeline.invoke(request, response);
}

where pipeline is an instance of the Pipeline interface inside the container.

Now, the pipeline has to make sure that all the valves added to it as well as its basic valve must be invoked once. The pipeline does this by creating an instance of the ValveContext interface. The ValveContext is implemented as an inner class of the pipeline so that the ValveContext has access to all members of the pipeline. The most important method of the ValveContext interface is invokeNext:

public void invokeNext(Request request, Response response) throws IOException, ServletException

After creating an instance of ValveContext, the pipeline calls the invokeNext method of the ValveContext. The ValveContext will first invoke the first valve in the pipeline and the first valve will invoke the next valve before the first valve does its task. The ValveContext passes itself to each valve so that the valve can call the invokeNext method of the ValveContext. Here is the signature of the invoke method of the Valve interface.

public void invoke(Request request, Response response, ValveContext ValveContext) throws IOException, ServletException

An implementation of a valve's invoke method will be something like the following.

public void invoke(Request request, Response response,
ValveContext valveContext) throws IOException, ServletException {
  // Pass the request and response on to the next valve in our pipeline   valveContext.invokeNext(request, response);
  // now perform what this valve is supposed to do
  ...
}

The org.apache.catalina.core.StandardPipeline class is the implementation of Pipeline in all containers. In Tomcat 4, this class has an inner class called StandardPipelineValveContext that implements the ValveContext interface. Listing 5.1 presents the StandardPipelineValveContext class.

Listing 5.1: The StandardPipelineValveContext class in Tomcat 4

protected class StandardPipelineValveContext implements ValveContext {
  protected int stage = 0;
  public String getInfo() {
    return info;
  }
  public void invokeNext(Request request, Response response) throws IOException, ServletException {
    int subscript = stage;
    stage = stage + 1;
    // Invoke the requested Valve for the current request thread
    if (subscript < valves.length) {
      valves[subscript].invoke(request, response, this);
    }else if ((subscript == valves.length) && (basic != null)) {
      basic.invoke(request, response, this);
    }else {
      throw new ServletException (sm.getString("standardPipeline.noValve"));
  } }
}

The invokeNext method uses subscript and stage to remember which valve is being invoked. When first invoked from the pipeline's invoke method, the value of subscript is 0 and the value of stage is 1. Therefore, the first valve (array index 0) is invoked. The first valve in the pipeline receives the ValveContext instance and invokes its invokeNext method. This time, the value of subscript is 1 so that the second valve is invoked, and so on.

When the invokeNext method is called from the last valve, the value of subscript is equal to the number of valves. As a result, the basic valve is invoked.

Tomcat 5 removes the StandardPipelineValveContext class from StandardPipeline and instead relies on the org.apache.catalina.core.StandardValveContext class, which is presented in Listing 5.2.

Listing 5.2: The StandardValveContext class in Tomcat 5

package org.apache.catalina.core;

import java.io.IOException;
import javax.servlet.ServletException;
import org.apache.catalina.Request;
import org.apache.catalina.Response;
import org.apache.catalina.Valve;
import org.apache.catalina.ValveContext;
import org.apache.catalina.util.StringManager;

public final class StandardValveContext implements ValveContext {
  protected static StringManager sm =
StringManager.getManager(Constants.Package);
  protected String info =
"org.apache.catalina.core.StandardValveContext/1.0";
  protected int stage = 0;
  protected Valve basic = null;
  protected Valve valves[] = null;
  public String getInfo() {
    return info;
  }
  public final void invokeNext(Request request, Response response) throws IOException, ServletException {
    int subscript = stage;
    stage = stage + 1;
    // Invoke the requested Valve for the current request thread
    if (subscript < valves.length) {
      valves[subscript].invoke(request, response, this); }
    else if ((subscript == valves.length) && (basic != null)) {
      basic.invoke(request, response, this);
    }else {
      throw new ServletException (sm.getString("standardPipeline.noValve"));
    } }
    void set(Valve basic, Valve valves[]) {
      stage = 0;
      this.basic = basic;
      this.valves = valves;
  }
}

Can you see the similarities between the StandardPipelineValveContext class in Tomcat 4 and the StandardValveContext class in Tomcat 5?

We will now explain the Pipeline, Valve, and ValveContext interfaces in more detail. Also discussed is the org.apache.catalina.Contained interface that a valve class normally implements.

The Pipeline Interface

The first method of the Pipeline interface that we mentioned was the invoke method, which a container calls to start invoking the valves in the pipeline and the basic valve. The Pipeline interface allows you to add a new valve through its addValve method and remove a valve by calling its removeValve method. Finally, you use its setBasic method to assign a basic valve to a pipeline and its getBasic method to obtain the basic valve. The basic valve, which is invoked last, is responsible for processing the request and the corresponding response. The Pipeline interface is given in Listing 5.3.

Listing 5.3: The Pipeline interface

package org.apache.catalina; import java.io.IOException;
import javax.servlet.ServletException;

public interface Pipeline {
  public Valve getBasic();
  public void setBasic(Valve valve);
  public void addValve(Valve valve);
  public Valve[] getValves();
  public void invoke(Request request, Response response)
throws IOException, ServletException;
  public void removeValve(Valve valve);

The Valve Interface

The Valve interface represents a valve, the component responsible for processing a request. This interface has two methods: invoke and getInfo. The invoke method has been discussed above. The getInfo method returns information about the valve implementation. The Valve interface is given in Listing 5.4.

Listing 5.4: The Valve interface

package org.apache.catalina;

import java.io.IOException;
import javax.servlet.ServletException;

public interface Valve {
  public String getInfo();
  public void invoke(Request request, Response response,
ValveContext context) throws IOException, ServletException;
}

The ValveContext Interface

This interface has two methods: the invokeNext method, which has been discussed above, and the getInfo method, which returns information about the ValveContext implementation. The ValveContext interface is given in Listing 5.5.

Listing 5.5: The ValveContext interface

package org.apache.catalina;

import java.io.IOException;
import javax.servlet.ServletException;

public interface ValveContext {
  public String getInfo();
  public void invokeNext(Request request, Response response)
throws IOException, ServletException;
}

The Contained Interface

A valve class can optionally implement the org.apache.catalina.Contained interface. This interface specifies that the implementing class is associated with at most one container instance. The Contained interface is given in Listing 5.6.

Listing 5.6: The Contained interface

package org.apache.catalina;
public interface Contained {
public Container getContainer();
public void setContainer(Container container);

he Wrapper Interface

The org.apache.catalina.Wrapper interface represents a wrapper. A wrapper is a container representing an individual servlet definition. The Wrapper interface extends Container and adds a number of methods. Implementations of Wrapper are responsible for managing the servlet life cycle for their underlying servlet class, i.e. calling the init, service, and destroy methods of the servlet. Since a wrapper is the lowest level of container, you must not add a child to it. A wrapper throws an IllegalArgumantException if its addChild method is called.

Important methods in the Wrapper interface include allocate and load. The allocate method allocates an initialed instance of the servlet the wrapper represents. The allocate method must also take into account whether or not the servlet implements the javax.servlet.SingleThreadModel interface, but we will discuss this later in Chapter 11. The load method loads and initializes an instance of the servlet the wrapper represents. The signatures of the allocate and load methods are as follows.

public javax.servlet.Servlet allocate() throws
javax.servlet.ServletException;
public void load() throws javax.servlet.ServletException;

The other methods will be covered in Chapter 11 when we discuss the org.apache.catalina.core.StandardWrapper class.

The Context Interface

A context is a container that represents a web application. A context usually has one or more wrappers as its child containers.

Important methods include addWrapper, createWrapper, etc. This interface will be covered in more detail in Chapter 12.

The Wrapper Application

This application demonstrates how to write a minimal container module. The core class of this application is ex05.pyrmont.core.SimpleWrapper, an implementation of the Wrapper interface. The SimpleWrapper class contains a Pipeline (implemented by the ex05.pyrmont.core.SimplePipeline class) and uses a Loader (implemented by the ex05.pyrmont.core.SimpeLoader) to load the servlet. The Pipeline contains a basic valve (ex05.pyrmont.core.SimpleWrapperValve) and two additional valves (ex05.pyrmont.core.ClientIPLoggerValve and ex05.pyrmont.core.HeaderLoggerValve). The class diagram of the application is given in Figure 5.3.

Figure 5.3: The Class Diagram of the Wrapper Application Note The container uses Tomcat 4's default connector.

The wrapper wraps the ModernServlet that you have used in the previous chapters. This application proves that you can have a servlet container consisting only of one wrapper. All classes are not fully developed, implementing only methods that must be present in the class. Let's now look at the classes in detail.

ex05.pyrmont.core.SimpleLoader

The task of loading servlet classes in a container is assigned to a Loader implementation. In this application, the SimpleLoader class is that implementation. It knows the location of the servlet class and its getClassLoader method returns a java.lang.ClassLoader instance that searches the servlet class location. The SimpleLoader class declares three variables. The first is WEB_ROOT, which points to the directory where the servlet class is to be found.

public static final String WEB_ROOT = System.getProperty("user.dir") + File.separator + "webroot";

The other two variables are object references of type ClassLoader and Container:

ClassLoader classLoader = null;
Container container = null;

The SimpleLoader class's constructor initializes the class loader so that it is ready to be returned to the SimpleWrapper instance.

public SimpleLoader() {
  try {
      URL[] urls = new URL[l];
      URLStreamHandler streamHandler = null;
      File classPath = new File(WEB_ROOT);
      String repository = (new URL("file", null,
classPath.getCanonicalPath() + File.separator)).toString() ;
      urls[0] = new URL(null, repository, streamHandler);
      classLoader = new URLClassLoader(urls);
    }catch (IOException e) {
      System.out.println(e.toString() ); }
}

The code in the constructor has been used to initialize class loaders in the applications in previous chapters and won't be explained again.

The container variable represents the container associated with this loader.

Note Loaders will be discussed in detail in Chapter 8.

ex05.pyrmont.core.SimplePipeline

The SimplePipeline class implements the org.apache.catalina.Pipeline interface. The most important method in this class is the invoke method, which contains an inner class called SimplePipelineValveContext. The SimplePipelineValveContext implements the org.apache.catalina.ValveContext interface and has been explained in the section "Pipelining Tasks" above.

ex05.pyrmont.core.SimpleWrapper

This class implements the org.apache.catalina.Wrapper interface and provides implementation for the allocate and load methods. Among others, this class declares the following variables:

private Loader loader;
protected Container parent = null;

The loader variable is a Loader that is used to load the servlet class. The parent variable represents a parent container for this wrapper. This means that this wrapper can be a child container of another container, such as a Context.

Pay special attention to its getLoader method, which is given in Listing 5.7.

Listing 5.7: The SimpleWrapper class's getLoader method

public Loader getLoader() {
  if (loader != null)
    return (loader);
  if (parent != null)
    return (parent.getLoader());

  return (null);
}

The getLoader method returns a Loader that is used to load a servlet class. If the wrapper is associated with a Loader, this Loader will be returned. If not, it will return the Loader of the parent container. If no parent is available, the getLoader method returns null.

The SimpleWrapper class has a pipeline and sets a basic valve for the pipeline. You do this in the SimpleWrapper class's constructor, given in Listing 5.8.  Listing 5.8: The SimpleWrapper class's constructor

public SimpleWrapper() {
  pipeline.setBasic(new SimpleWrapperValve());
}

Here, pipeline is an instance of SimplePipeline as declared in the class:

private SimplePipeline pipeline = new SimplePipeline(this);

ex05.pyrmont.core.SimpleWrapperValve

The SimpleWrapperValve class is the basic valve that is dedicated to processing the request for the SimpleWrapper class. It implements the org.apache.catalina.Valve interface and the org.apache.catalina.Contained interface. The most important method in the SimpleWrapperValve is the invoke method, given in Listing 5.9.

Listing 5.9: The SimpleWrapperValve class's invoke method

public void invoke(Request request, Response response, ValveContext valveContext)
throws IOException, ServletException {
  SimpleWrapper wrapper = (SimpleWrapper) getContainer(); ServletRequest sreq = request.getRequest();
  ServletResponse sres = response.getResponse();
  Servlet servlet = null;
  HttpServletRequest hreq = null;
  if (sreq instanceof HttpServletRequest)
    hreq = (HttpServletRequest) sreq;
  HttpServletResponse hres = null;
  if (sres instanceof HttpServletResponse)
    hres = (HttpServletResponse) sres;
  // Allocate a servlet instance to process this request try {
  servlet = wrapper.allocate();
  if (hres!=null && hreq!=null) {
    servlet.service(hreq, hres);
  }else {
    servlet.service(sreq, sres);
  } }catch (ServletException e) {
} }

Because SimpleWrapperValve is used as a basic valve, its invoke method does not need to call the invokeNext method of the ValveContext passed to it. The invoke method calls the allocate method of the SimpleWrapper class to obtain an instance of the servlet the wrapper represents. It then calls the servlet's service method. Notice that the basic valve of the wrapper's pipeline invokes the servlet's service method, not the wrapper itself.

ex05.pyrmont.valves.ClientIPLoggerValve

The ClientIPLoggerValve class is a valve that prints the client's IP address to the console. This class is given in Listing 5.10.

Listing 5.10: The ClientIPLoggerValve class

package ex05.pyrmont.valves;

import java.io.IOException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletException;
import org.apache.catalina.Request;
import org.apache.catalina.Response;
import org.apache.catalina.Valve;
import org.apache.catalina.ValveContext;
import org.apache.catalina.Contained;
import org.apache.catalina.Container;

public class ClientIPLoggerValve implements Valve, Contained {
  protected Container container;
  public void invoke(Request request, Response response,
ValveContext valveContext) throws IOException, ServletException {
    // Pass this request on to the next valve in our pipeline
    valveContext.invokeNext(request, response);
    System.out.println("Client IP Logger Valve");
    ServletRequest sreq = request.getRequest();
    System.out.println(sreq.getRemoteAddr());

    System, out.println("-----------------------------------");
    }
  public String getInfo() { return null; }
  public Container getContainer() { return container; }
  public void setContainer(Container container) {
    this.container = container;
  }
}

Pay attention to the invoke method. The first thing the invoke method does is call the invokeNext method of the valve context to invoke the next valve in the pipeline, if any. It then prints a few lines of string including the output of the getRemoteAddr method of the request object.

ex05.pyrmont.valves.HeaderLoggerValve

This class is very similar to the ClientIPLoggerValve class. The HeaderLoggerValve class is a valve that prints the request header to the console. This class is given in Listing 5.11.

Listing 5.11: The HeaderLoggerValve class

package ex05.pyrmont.valves;

import java.io.IOException;
import java.util.Enumeration;
import javax.servlet.ServletRequest;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import org.apache.catalina.Request;
import org.apache.catalina.Response;
import org.apache.catalina.Valve;
import org.apache.catalina.ValveContext;
import org.apache.catalina.Contained;
import org.apache.catalina.Container;

public class HeaderLoggerValve implements Valve, Contained {
  protected Container container;
  public void invoke(Request request, Response response,
ValveContext valveContext) throws IOException, ServletException {
    // Pass this request on to the next valve in our pipeline
    valveContext.invokeNext(request, response);
    System.out.println("Header Logger Valve");
    ServletRequest sreq = request.getRequest();
    if (sreq instanceof HttpServletRequest) {
      HttpServletRequest hreq = (HttpServletRequest) sreq;
      Enumeration headerNames = hreq.getHeaderNames();
      while (headerNames.hasMoreElements()) {
        String headerName = headerNames.nextElement().toString();
        String headerValue = hreq.getHeader(headerName);
        System.out.println(headerName + ":" + headerValue);
    }
    } else
        System.out.println("Not an HTTP Request");
    System.out.println ("-----------------------------------"); }
  public String getInfo() { return null; }
  public Container getContainer() { return container; }
  public void setContainer(Container container) {
    this.container = container;
  }
}

Again, pay special attention to the invoke method. The first thing the invoke method does is call the invokeNext method of the valve context to invoke the next valve in the pipeline, if any. It then prints the values of some headers.

ex05.pyrmont.startup.Bootstrap1

The Bootstrap1 class is used to start the application. It is given in Listing 5.12.

Listing 5.12: The Bootstrap1 class

package ex05.pyrmont.startup;

import ex05.pyrmont.core.SimpleLoader;
import ex05.pyrmont.core.SimpleWrapper;
import ex05.pyrmont.valves.ClientlPLoggerValve;
import ex05.pyrmont.valves.HeaderLoggerValve;
import org.apache.catalina.Loader;
import org.apache.catalina.Pipeline;
import org.apache.catalina.Valve;
import org.apache.catalina.Wrapper;
import org.apache.catalina.connector.http.HttpConnector;

public final class Bootstrap1 {
  public static void main(String[] args) {
    HttpConnector connector = new HttpConnector();
    Wrapper wrapper = new SimpleWrapper();
    wrapper.setServletClass("ModernServlet");
    Loader loader = new SimpleLoader();
    Valve valve1 = new HeaderLoggerValve(); Valve valve2 = new ClientIPLoggerValve();
wrapper.setLoader(loader);
((Pipeline) wrapper).addValve(valve1); ((Pipeline) wrapper).addValve(valve2);
connector.setContainer(wrapper);
try { connector.initialize(); connector.start();
// make the application wait until we press a key.
System.in.read(); }
catch (Exception e) { e.printStackTrace();
} }

}