Host

Host是Context的父容器。如果想在一个tomcat上部署多个context就需要使用Host了。上下文容器的父容器是主机,但是可能有一些其它实现,没有必要的时候也可以忽略。不过在实践中,即使是一个Context,我们也使用了Host,为什么?后面我们再说。


Host是个接口,里面有个map方法比较重要


/**
* 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);

根据url来返回一个Context。


Host的标准实现是StandardHost


构造函数,不解释


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

StandardHost并没有invoke方法,它需要调用ContainerBase的invoke方法。


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

另外StandardHostValve这个基础阀会调用StandardHost的map方法获得一个context容器。


public void invoke(Request request, Response response,
ValveContext valveContext)
throws IOException, ServletException {
....
StandardHost host = (StandardHost) getContainer();
//这里调用的是ContainerBase的map方法 最终会调用StandardHost的map方法(两个map方法没有关系 没有//复写 参数都不一样)
Context context = (Context) host.map(request, true);
....
}


StandHostMapper

在ContainerBase的start中有如下的代码

addDefaultMapper(this.mapperClass);

看ConntainerBase


ContainerBase.java
protected void addDefaultMapper(String mapperClass) {
......
// Instantiate and add a default Mapper
Class<?> clazz = Class.forName(mapperClass);
Mapper mapper = (Mapper) clazz.newInstance();
mapper.setProtocol("http");
addMapper(mapper);
......
}


这里我又一个小问题,为什么ContainerBase的start中的


addDefaultMapper(this.mapperClass);


调用的是子类中的addDefaultMapper呢?


StandardHost.java
protected void addDefaultMapper(String mapperClass) {
// 参数默认为 "org.apache.catalina.core.StandardHostMapper";
super.addDefaultMapper(this.mapperClass);
}


StandardHost的start方法在结尾的时候会调用父类的start方法。



注意:Tomcat4 中的 standardContext 使用了不同的方法来创建一个默认映射器。它的 start 方法中并没有调用 super.start()。相反 Standardcontext 的start 方法调用 addDefaultMapper 来传递 mapperClass 变量。



StandardHostMapper的map方法就是调用StandardHost的map方法。


public Container map(Request request, boolean update) {
...
String uri = ((HttpRequest) request).getDecodedRequestURI();
Context context = host.map(uri);
...
return (context);
}

在得到上下对象的时候需要一个往返过程,map 方法介绍两个参数,该方法是在 ContainerBase 中的。然后 ContainerBase 类又在它的子对象中查找合适的映射器并调用它的 map 方法。



StandardHostValve

这是Host的基础阀,什么时候注入的?大家自己想。



StandardHostValve这个基础阀会调用StandardHost的map方法获得一个context容器。


public void invoke(Request request, Response response,
ValveContext valveContext)
throws IOException, ServletException {
....
StandardHost host = (StandardHost) getContainer();
//这里调用的是ContainerBase的map方法 最终会调用StandardHost的map方法(两个map方法没有关系 没有//复写 参数都不一样)
Context context = (Context) host.map(request, true);
....
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(); //修改session的最后访问时间
}
}
context.invoke(request, response); //最后调用context的invoke
}

为什么必须要有一个Host容器

想想我们之前的SimpleConextConfig,它的作用是将context的configured设置为true,为什么不在context里直接设置?参见观察者模式。


在ConextConfig的标准实现的applicationConfig中


private void applicationConfig() {
...
synchronized (webDigester) {
try {
URL url =
servletContext.getResource(Constants.ApplicationWebXml);
.....
}


servletContext为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();
....
}

看到最后一行的getParent大家都明白了吧。


当然如果使用的是之前的SimpleContextConfig,就不需要Host了。



Engine

在tomcat中,Engine的标准实现是:org.apache.catalina.core.StandardEngine。


同样的Engine也有一个基础阀:StandardEngineValve


同是Engine不能再有父容器了,子容器也只能是Host。



StandardEngineValve

StandardEngineValve的invoke如下:


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

// Select the Host to be used for this Request
StandardEngine engine = (StandardEngine) getContainer();
Host host = (Host) engine.map(request, true); //同样调用的是Containerbase的map
//addDefaultMapper 怎么来的就不用说//了吧
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);
}


验证了request与response后,就调用Containerbase的map获得一个Host,然后调用host的invoke。