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.
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.
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(); }
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.
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.
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!
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);
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.
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.
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(); } } }
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 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.
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")); }
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.
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(); } } }
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
3.17.6.75