Chapter 13. Host and Engine

Two topics of discussion in this chapter are hosts and engines. You use a host if you want to run more than one context in the same Tomcat deployment. In theory, you do not need a host if you only have one context, as stated in the description of the org.apache.catalina.Context interface:

The parent Container attached to a Context is generally a Host, but may be some other implementation, or may be omitted if it is not necessary.

In practice, however, a Tomcat deployment always needs a host. Why this is so is explained in the section, “Why You Cannot Live without a Host” later in this chapter.

An engine represents the entire Catalina servlet engine. If used, an engine is always the top level container. Child containers added to an engine are normally implementations of org.apache.catalina.Host or org.apache.catalina.Context. An engine is used in a Tomcat deployment by default. In this deployment, the engine has one host, the default host.

This chapter discusses classes related to the Host and Engine interfaces. It starts with the Host interface followed by the StandardHost, StandardHostMapper (in Tomcat 4) and StandardHostValve classes. Next, to conclude the discussion of Host, an application is presented that demonstrates the use of a host as a top level container. The Engine interface begins the second topic of discussion, followed by the StandardEngine and the StandardEngineValve classes. Then, comes the second application in this chapter, which illustrates the use of an engine as the top level container.

The Host Interface

A host is represented by the org.apache.catalina.Host interface. This interface extends the Container interface and is given in Listing 13.1.

Example 13.1. The Host interface

package org.apache.catalina;

public interface Host extends Container {
  public static final String ADD_ALIAS_EVENT = "addAlias";
  public static final String REMOVE_ALIAS_EVENT = "removeAlias";
  /**
   * Return the application root for this Host.
   * This can be an absolute
   * pathname, a relative pathname, or a URL.
   */
  public String getAppBase();

  /**
   * Set the application root for this Host.  This can be an absolute
   * pathname, a relative pathname, or a URL.
   *
   * @param appBase The new application root
   */
  public void setAppBase(String appBase);

  /**
   * Return the value of the auto deploy flag.
   * If true, it indicates that
   * this host's child webapps should be discovred and automatically
   * deployed.
   */
  public boolean getAutoDeploy();

  /**
   * Set the auto deploy flag value for this host.
   *
   * @param autoDeploy The new auto deploy flag
   */
  public void setAutoDeploy(boolean autoDeploy);


  /**
   * Set the DefaultContext
   * for new web applications.
   *
   * @param defaultContext The new DefaultContext
   */
  public void addDefaultContext (DefaultContext defaultContext);


  /**
   * Retrieve the DefaultContext for new web applications.
   */
  public DefaultContext getDefaultContext();

  /**
   * Return the canonical, fully qualified, name of the virtual host
   * this Container represents.
   */
  public String getName();

  /**
   * Set the canonical, fully qualified, name of the virtual host
   * this Container represents.
   *
   * @param name Virtual host name
   *
   * @exception IllegalArgumentException if name is null
   */
  public void setName(String name);

  /**
   * Import the DefaultContext config into a web application context.
   *
   * @param context web application context to import default context
   */
  public void importDefaultContext(Context context);

  /**
   * Add an alias name that should be mapped to this same Host.
   *
   * @param alias The alias to be added
   */
  public void addAlias(String alias);

  /**
   * Return the set of alias names for this Host.  If none are defined,
   * a zero length array is returned.
   */
  public String[] findAliases();

  /**
   * Return the Context that would be used to process the specified
   * host-relative request URI, if any; otherwise return
   * <code>null</code>.
   *
   * @param uri Request URI to be mapped
   */
  public Context map(String uri);

  /**
   * Remove the specified alias name from the aliases for this Host.
   * @param alias Alias name to be removed
   */
  public void removeAlias (String alias);
}

Of particular importance is the map method that returns the right context to handle the incoming request. The implementation of this method can be found in the StandardHost class, discussed in the next section.

StandardHost

The org.apache.catalina.core.StandardHost class is the standard implementation of Host. This class extends the org.apache.catalina.core.ContainerBase class and implements the Host and Deployer interfaces. Deployer is discussed in Chapter 17.

Just like the StandardContext and the StandardWrapper classes, the StandardHost class’s constructor adds the basic valve to its pipeline:

public StandardHost() {
  super();
  pipeline.setBasic(new StandardHostValve());
}

As you can see, the basic valve is an instance of org.apache.catalina.core.StandardHostValve.

When started, i.e. when its start method is called, the StandardHost adds two valves: ErrorReportValve and ErrorDispatcherValve. Both valves are part of the org.apache.catalina.valves package. The start method of StandardHost in Tomcat 4 is given in Listing 13.2:

Example 13.2. The start method of StandardHost

public synchronized void start() throws LifecycleException {
  // Set error report valve
  if ((errorReportValveClass != null)
    && (!errorReportValveClass.equals(""))) {
    try {
      Valve valve =
        (Valve) Class.forName(errorReportValveClass).newInstance();
      addValve(valve);
    }
    catch (Throwable t) {
      log(sm.getString
        ("standardHost.invalidErrorReportValveClass",
          errorReportValveClass));
    }
  }
  // Set dispatcher valve
  addValve(new ErrorDispatcherValve());
  super.start();
}

Note

In Tomcat 5, the start method is similar, except for the fact that it includes code for constructing JXM object name, which will only be discussed in Chapter 20.

The value of errorReportValveClass is determined in the StandardHost class as follows:

private String errorReportValveClass =
  "org.apache.catalina.valves.ErrorReportValve";

For every incoming request, the invoke method of the host will be called. Since StandardHost does not have its own implementation of the invoke method, the invoke method of ContainerBase, its parent class, will be called. The invoke method in turn calls the invoke method of the StandardHostValve, the basic valve of StandardHost. The invoke method of the StandardHostValve is discussed in the section “StandardHostValve” below. Among others, the StandardHostValve class’s invoke method calls the StandardHost class’s map method to obtain the right context to handle the request. The implementation of the map method in StandardHost is given in Listing 13.3.

Example 13.3. The map method in the StandardHost class

public Context map(String uri) {
  if (debug > 0)
    log("Mapping request URI '" + uri + "'");
  if (uri == null)
    return (null);

  // Match on the longest possible context path prefix
  if (debug > 1)
    log("  Trying the longest context path prefix");
  Context context = null;
  String mapuri = uri;
  while (true) {
    context = (Context) findChild(mapuri);
    if (context != null)
      break;
    int slash = mapuri.lastIndexOf('/'),
    if (slash < 0)
      break;
    mapuri = mapuri.substring(0, slash);
  }

  // If no Context matches, select the default Context
  if (context == null) {
    if (debug > 1)
      log("  Trying the default context");
    context = (Context) findChild("");
  }

  // Complain if no Context has been selected
  if (context == null) {
    log(sm.getString("standardHost.mappingError", uri));
    return (null);
  }

  // Return the mapped Context (if any)
  if (debug > 0)
    log(" Mapped to context '" + context.getPath() + "'");
  return (context);
}

Note that the ContainerBase class in Tomcat 4 also defines a map method with the following signature:

public Container map(Request request, boolean update);

In Tomcat 4 the invoke method in StandardHostValve calls the map method in ContainerBase, which in turn calls the map method in StandardHost. Tomcat 5 does not use a mapper component, and the right context is obtained from the request object.

StandardHostMapper

In Tomcat 4 the ContainerBase class, the parent class of StandardHost, creates a default mapper by calling its addDefaultMapper method from the start method. The type of the default mapper is given by the mapperClass property. Here is the addDefaultMapper method of ContainerBase:

protected void addDefaultMapper(String mapperClass) {
  // Do we need a default Mapper?
  if (mapperClass == null)
    return;
  if (mappers.size() >= 1)
    return;

  // Instantiate and add a default Mapper
  try {
    Class clazz = Class.forName(mapperClass);
    Mapper mapper = (Mapper) clazz.newInstance();
    mapper.setProtocol("http");
    addMapper(mapper);
  }
  catch (Exception e) {
    log(sm.getString("containerBase.addDefaultMapper", mapperClass),
      e);
  }
}

The StandardHost class defines the mapperClass variable as follows:

private String mapperClass =
  "org.apache.catalina.core.StandardHostMapper";

Also, the StandardHost class’s start method calls super.start () at the end of its body, thus ensuring the creation of a default mapper.

Note

The standardContext class in Tomcat 4 uses a slightly different approach to creating a default mapper. Its start method does not call supoer.start(). Instead, the StandardContext class’s start method calls the addDefaultMapper method passing the mapperClass variable.

The most important method in StandardHostMapper is, of course, map. Here it is.

public Container map(Request request, boolean update) {
  // Has this request already been mapped?
  if (update && (request.getContext() != null))
    return (request.getContext());

  // Perform mapping on our request URI
  String uri = ((HttpRequest) request).getDecodedRequestURI();
  Context context = host.map(uri);

  // Update the request (if requested) and return the selected Context
  if (update) {
    request.setContext(context);
    if (context != null)
      ((HttpRequest) request).setContextPath(context.getPath());
    else
      ((HttpRequest) request).setContextPath(null);
  }
  return (context);
}

Notice that the map method simply calls the Host’s map method!

StandardHostValve

The org.apache.catalina.core.StandardHostValve class is the basic valve of StandardHost. Its invoke method (in Listing 13.4) is called when there is an incoming HTTP request.

Example 13.4. The invoke method of StandardHostValve

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

  // Validate the request and response object types
  if (!(request.getRequest() instanceof HttpServletRequest) ||
    !(response.getResponse() instanceof HttpServletResponse)) {
    return;     // NOTE - Not much else we can do generically
  }

  // Select the Context to be used for this Request
  StandardHost host = (StandardHost) getContainer();
  Context context = (Context) host.map(request, true);
  if (context == null) {
    ((HttpServletResponse) response.getResponse()).sendError
    (HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
    sm.getString("standardHost.noContext"));
    return;
  }

  // Bind the context CL to the current thread
  Thread.currentThread().setContextClassLoader
    (context.getLoader().getClassLoader());

  // Update the session last access time for our session (if any)
  HttpServletRequest hreq = (HttpServletRequest) request.getRequest();
  String sessionId = hreq.getRequestedSessionId();
  if (sessionId != null) {
    Manager manager = context.getManager();
    if (manager != null) {
      Session session = manager.findSession(sessionId);
      if ((session != null) && session.isValid())
        session.access();
      }
    }

  // Ask this Context to process this request
  context.invoke(request, response);
}

The invoke method in Tomcat 4 obtains the appropriate context by calling the map method of StandardHost.

// Select the Context to be used for this Request
StandardHost host = (StandardHost) getContainer();
Context context = (Context) host.map(request, true);

Note

There is a round trip when obtaining the Context object above. The map method above accepts two arguments. This is the map method defined in the ContainerBase class. The map method in the ContainerBase class then finds the appropriate mapper in the child object (i.e. the StandardHost instance in this case) and calls its map method.

The invoke method then obtains the session object associated with the request object and calls its access method. The access method updates the last access time. Here is the access method in the org.apache.catalina.session.StandardSession class:

public void access() {
  this.isNew = false;
  this.lastAccessedTime = this.thisAccessedTime;
  this.thisAccessedTime = System.currentTimeMillis();
}

Finally, the invoke method calls the invoke method of the context, letting the context handle the request.

Why You Cannot Live without a Host

A Tomcat deployment (Tomcat 4 and 5) must have a host if each context is to be configured using ContextConfig. The reason is this.

ContextConfig needs the location of the application web.xml file. It attempts to open the web.xml file in its applicationConfig method. Here is the fragment of the applicationConfig method:

synchronized (webDigester) {
  try {
    URL url =
      servletContext.getResource(Constants.ApplicationWebXml);
    InputSource is = new InputSource(url.toExternalForm());
    is.setByteStream(stream);
    ...
    webDigester.parse(is);
    ...

where Constants.ApplicationWebXml is /WEB-INF/web.xml, the relative path to the web.xml file, and servletContext is an object of type org.apache.catalina.core.ApplicationContext (implements javax.servlet.ServletContext).

Here is the getResource method of ApplicationContext:

public URL getResource(String path)
  throws MalformedURLException {

  DirContext resources = context.getResources();
  if (resources != null) {
    String fullPath = context.getName() + path;
    // this is the problem. Host must not be null
    String hostName = context.getParent().getName();

The last line shows clearly that a context must have a parent (a host) if it is to be configured by a ContextConfig. You will learn how the web.xml file is parsed in Chapter 15, “Digester”. In short, you must have a host unless you write your own ContextConfig class.

Application 1

The first application in this chapter demonstrates the use of a host as the top level container. This application uses two classes, ex13.pyrmont.core.SimpleContextConfig and ex13.pyrmont.startup.Bootstrap1 class. The SimpleContextConfig class is copied from Chapter 11 and the Bootstrap2 class is given in Listing 13.5:

Example 13.5. The Bootstrap1 Class

package ex13.pyrmont.startup;

import ex13.pyrmont.core.SimpleContextConfig;
import org.apache.catalina.Connector;
import org.apache.catalina.Context;
import org.apache.catalina.Host;
import org.apache.catalina.Lifecycle;
import org.apache.catalina.LifecycleListener;
import org.apache.catalina.Loader;
import org.apache.catalina.Wrapper;
import org.apache.catalina.connector.http.HttpConnector;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.core.StandardHost;
import org.apache.catalina.core.StandardWrapper;
import org.apache.catalina.loader.WebappLoader;

public final class Bootstrap1 {
  public static void main(String[] args) {
    System.setProperty("catalina.base",
      System.getProperty("user.dir"));
    Connector connector = new HttpConnector();
    Wrapper wrapper1 = new StandardWrapper();
    wrapper1.setName("Primitive");
    wrapper1.setServletClass("PrimitiveServlet");
    Wrapper wrapper2 = new StandardWrapper();
    wrapper2.setName("Modern");
    wrapper2.setServletClass("ModernServlet");
    Context context = new StandardContext();
    // StandardContext's start method adds a default mapper
    context.setPath("/app1");
    context.setDocBase("app1");
    context.addChild(wrapper1);
    context.addChild(wrapper2);
    LifecycleListener listener = new SimpleContextConfig();
    ((Lifecycle) context).addLifecycleListener(listener);
    Host host = new StandardHost();
    host.addChild(context);
    host.setName("localhost");
    host.setAppBase("webapps");
    Loader loader = new WebappLoader();
    context.setLoader(loader);
    // context.addServletMapping(pattern, name);
    context.addServletMapping("/Primitive", "Primitive");
    context.addServletMapping("/Modern", "Modern");
    connector.setContainer(host);
    try {
      connector.initialize();
      ((Lifecycle) connector).start();
      ((Lifecycle) host).start();
      // make the application wait until we press a key.
      System.in.read();
      ((Lifecycle) host).stop();
    }
    catch (Exception e) {
      e.printStackTrace();
    }
  }
}

Running the Applications

To run the application in Windows, from the working directory, type the following:

java -classpath ./lib/servlet.jar;./lib/commons-
collections.jar;./lib/commons-digester.jar;./
ex13.pyrmont.startup.Bootstrap1

In Linux, you use a colon to separate two libraries.

java -classpath ./lib/servlet.jar:./lib/commons-
collections.jar:./lib/commons-digester.jar:./
ex13.pyrmont.startup.Bootstrap1

To invoke PrimitiveServlet, use the following URL in your browser.

http://localhost:8080/app1/Primitive

To invoke ModernServlet, use the following URL.

http://localhost:8080/app1/Modern

The Engine Interface

The org.apache.catalina.Engine interface represents an engine. An engine represents the entire Catalina servlet engine. You would want to use an engine if you want to support multiple virtual hosts. In fact, a Tomcat deployment normally uses an engine.

The Engine interface is shown in Listing 13.6:

Example 13.6. The Engine Interface

package org.apache.catalina;

public interface Engine extends Container {
  /**
   * Return the default hostname for this Engine.
   */
  public String getDefaultHost();
  /**
   * Set the default hostname for this Engine.
   *
   * @param defaultHost The new default host
   */
  public void setDefaultHost(String defaultHost);
  /**
   * Retrieve the JvmRouteId for this engine.
   */
  public String getJvmRoute();
  /**
   * Set the JvmRouteId for this engine.
   *
   * @param jvmRouteId the (new) JVM Route ID. Each Engine within a
   * cluster must have a unique JVM Route ID.
   */
  public void setJvmRoute(String jvmRouteId);
  /**
   * Return the <code>Service</code> with which we are associated (if
   * any).
   */
  public Service getService();
  /**
   * Set the <code>Service</code> with which we are associated (if
   * any).
   *
   * @param service The service that owns this Engine
   */
  public void setService(Service service);
  /**
   * Set the DefaultContext
   * for new web applications.
   *
   * @param defaultContext The new DefaultContext
   */
  public void addDefaultContext(DefaultContext defaultContext);
  /**
   * Retrieve the DefaultContext for new web applications.
   */
  public DefaultContext getDefaultContext();
  /**
   * Import the DefaultContext config into a web application context.
   *
   * @param context web application context to import default context
   */
  public void importDefaultContext(Context context);
}

You can set a default host or add a default context to an Engine. Note also that an engine can be associated with a service. Services are discussed in Chapter 14.

StandardEngine

The org.apache.catalina.core.StandardEngine is the standard implementation of the Engine interface. Compared to StandardContext and StandardHost, the StandardEngine class is relatively small. When instantiated, the StandardEngine class adds a basic valve, as indicated in its constructor:

public StandardEngine() {
  super();
  pipeline.setBasic(new StandardEngineValve());
}

As the top level container, a StandardEngine will have child containers. A child container of a StandardEngine must be a host. An exception will be thrown if you try to add a non-host container. Here is the addChild method of StandardEngine.

public void addChild(Container child) {
  if (!(child instanceof Host))
    throw new IllegalArgumentException
      (sm.getString("standardEngine.notHost"));
  super.addChild(child);
}

As a top level container, it is impossible for an engine to have a parent. An exception will be thrown if you try to set a parent, as displayed by the StandardEngine class’s setParent method:

public void setParent(Container container) {
  throw new IllegalArgumentException
   (sm.getString("standardEngine.notParent"));
}

StandardEngineValve

The org.apache.catalina.core.StandardEngineValve is the basic valve of StandardEngine. The invoke method of StandardEngineValve is presented in Listing 13.7.

Example 13.7. The invoke method of StandardEngineValve

public void invoke(Request request, Response response,
  ValveContext valveContext)
  throws IOException, ServletException {
  // Validate the request and response object types
  if (!(request.getRequest() instanceof HttpServletRequest) ||
    !(response.getResponse() instanceof HttpServletResponse)) {
    return;     // NOTE - Not much else we can do generically
  }
  // Validate that any HTTP/1.1 request included a host header
  HttpServletRequest hrequest = (HttpServletRequest) request;
  if ("HTTP/1.1".equals(hrequest.getProtocol()) &&
    (hrequest.getServerName() == null)) {
    ((HttpServletResponse) response.getResponse()).sendError
    (HttpServletResponse.SC_BAD_REQUEST,
      sm.getString("standardEngine.noHostHeader",
    request.getRequest().getServerName()));
    return;
  }
  // Select the Host to be used for this Request
  StandardEngine engine = (StandardEngine) getContainer();
  Host host = (Host) engine.map(request, true);
  if (host == null) {
    ((HttpServletResponse) response.getResponse()).sendError
      (HttpServletResponse.SC_BAD_REQUEST,
      sm.getString("standardEngine.noHost",
      request.getRequest().getServerName()));
    return;
  }
  // Ask this Host to process this request
  host.invoke(request, response);
}

After validating the type of the request and response objects, the invoke method obtains the Host instance used for processing the request. It obtains the Host by calling the Engine's map method. Once the Host is obtained, its invoke method is called.

Application 2

The second application in this chapter demonstrates the use of an engine as the top level container. This application uses two classes, ex13.pyrmont.core.SimpleContextConfig and ex13.pyrmont.startup.Bootstrap2 class. The Bootstrap2 class is given in Listing 13.8:

Example 13.8. The Bootstrap2 class

package ex13.pyrmont.startup;
//Use engine
import ex13.pyrmont.core.SimpleContextConfig;
import org.apache.catalina.Connector;
import org.apache.catalina.Context;
import org.apache.catalina.Engine;
import org.apache.catalina.Host;
import org.apache.catalina.Lifecycle;
import org.apache.catalina.LifecycleListener;
import org.apache.catalina.Loader;
import org.apache.catalina.Wrapper;
import org.apache.catalina.connector.http.HttpConnector;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.core.StandardEngine;
import org.apache.catalina.core.StandardHost;
import org.apache.catalina.core.StandardWrapper;
import org.apache.catalina.loader.WebappLoader;

public final class Bootstrap2 {
  public static void main(String[] args) {
    System.setProperty("catalina.base",
      System.getProperty("user.dir"));
    Connector connector = new HttpConnector();
    Wrapper wrapper1 = new StandardWrapper();
    wrapper1.setName("Primitive");
    wrapper1.setServletClass("PrimitiveServlet");
    Wrapper wrapper2 = new StandardWrapper();
    wrapper2.setName("Modern");
    wrapper2.setServletClass("ModernServlet");
    Context context = new StandardContext();
    // StandardContext's start method adds a default mapper
    context.setPath("/app1");
    context.setDocBase("app1");
    context.addChild(wrapper1);
    context.addChild(wrapper2);
    LifecycleListener listener = new SimpleContextConfig();
    ((Lifecycle) context).addLifecycleListener(listener);
    Host host = new StandardHost();
    host.addChild(context);
    host.setName("localhost");
    host.setAppBase("webapps");
    Loader loader = new WebappLoader();
    context.setLoader(loader);
    // context.addServletMapping(pattern, name);
    context.addServletMapping("/Primitive", "Primitive");
    context.addServletMapping("/Modern", "Modern");
    Engine engine = new StandardEngine();
    engine.addChild(host);
    engine.setDefaultHost("localhost");
    connector.setContainer(engine);
    try {
      connector.initialize();
      ((Lifecycle) connector).start();
      ((Lifecycle) engine).start();
      // make the application wait until we press a key.
      System.in.read();
      ((Lifecycle) engine).stop();
     }
    catch (Exception e) {
      e.printStackTrace();
    }
  }
}

Running the Applications

To run the application in Windows, from the working directory, type the following:

java -classpath ./lib/servlet.jar;./lib/commons-
collections.jar;./lib/commons-digester.jar;./
ex13.pyrmont.startup.Bootstrap2

In Linux, you use a colon to separate two libraries.

java -classpath ./lib/servlet.jar:./lib/commons-
collections.jar:./lib/commons-digester.jar:./
ex13.pyrmont.startup.Bootstrap2

To invoke PrimitiveServlet, use the following URL in your browser.

http://localhost:8080/app1/Primitive

To invoke ModernServlet, use the following URL.

http://localhost:8080/app1/Modern

Summary

In this chapter you have learned about two types of containers: host and engine. This chapter also explained the classes related to both containers. Two applications are also presented that show the use of a host and an engine as top level containers.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.17.6.75