Host和Engine
在Catalina中Engine代表Catalina实体,Host代表一个虚拟的主机,Engine包括多个Host,一个Host包括多个Context。
Host接口
在Tomcat中Host用org.apache.catalina.Host接口表示:
public interface Host extends Container {
public static final String ADD_ALIAS_EVENT = "addAlias";
public static final String REMOVE_ALIAS_EVENT = "removeAlias";
public String getAppBase();
public void setAppBase(String appBase);
public boolean getAutoDeploy();
public void setAutoDeploy(boolean autoDeploy);
public void addDefaultContext(DefaultContext defaultContext);
public DefaultContext getDefaultContext();
public String getName();
public void setName(String name);
public void importDefaultContext(Context context);
public void addAlias(String alias);
public String[] findAliases();
public Context map(String uri);
public void removeAlias(String alias);
}
其中最重要的是map方法,它会根据请求调用正确的Context来处理。
StandardHost
StandardHost是Host的标准实现,在其构造方法中也会为管道中添加StandardHostValue:
public StandardHost() {
super();
pipeline.setBasic(new StandardHostValve());
}
同时在启动的时候还会添加两个Value:
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();
}
当HTTP请求来的时候,Host的invoke方法会被调用,其中会调用到StandardHostValue的invoke方法,通过调用Host的map方法找到正确的Context来处理请求:
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);
}
StandardHostMapper
在StandardHost中有一个映射器类StandardHostMapper专门用来根据请求uri调用StandardHost的map方法找到对应的Context进行处理,其map方法实现如下:
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);
}
StandardHostValue
StandardHostValue是StandardHost的基础Value,其中invoke方法实现如下:
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);
}
在这里,它首先获取匹配的Context,然后获得session并调用session的access方法更新session的操作时间,最后调用context的invoke方法。
为什么必须要Host
每个Context都需要ContextConfig来进行配置,需要通过ApplicationContext的getResource方法来获取应用的部署文件“WEB-INF/web.xml”,在getResource中需要用到Host:
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();
try {
resources.lookup(path);
if( System.getSecurityManager() != null ) {
try {
PrivilegedGetResource dp =
new PrivilegedGetResource
(hostName, fullPath, resources);
return (URL)AccessController.doPrivileged(dp);
} catch( PrivilegedActionException pe) {
throw pe.getException();
}
} else {
return new URL
("jndi", null, 0, getJNDIUri(hostName, fullPath),
new DirContextURLStreamHandler(resources));
}
} catch (Exception e) {
//e.printStackTrace();
}
}
return (null);
}
Engine接口
Engine表示catalina的servlet引擎实体,如果你要用到多个Host就必须用到Engine,在Tomcat中引擎用org.apache.catalina.Engine接口来实现。通常情况下Tomcat部署的时候都会用到engine。
public interface Engine extends Container {
public String getDefaultHost();
public void setDefaultHost(String defaultHost);
public String getJvmRoute();
public void setJvmRoute(String jvmRouteId);
public Service getService();
public void setService(Service service);
public void addDefaultContext(DefaultContext defaultContext);
public DefaultContext getDefaultContext();
public void importDefaultContext(Context context);
}
从接口定义中我们可以看到你可以为Engine添加一个默认的Host和默认的Engine,同时可以为Engine分配一个service。
StandardEngine
StandardEngine是Engine接口的标准实现,在构造的时候为管道中添加基础value:
public StandardEngine() {
super();
pipeline.setBasic(new StandardEngineValve());
}
同时你可以为Engine添加Host:
public void addChild(Container child) {
if (!(child instanceof Host))
throw new IllegalArgumentException
(sm.getString("standardEngine.notHost"));
super.addChild(child);
}
同时Engine没有父容器:
public void setParent(Container container) {
throw new IllegalArgumentException
(sm.getString("standardEngine.notParent"));
}
StandardEngineValue
StandardEngineValue是StandardEngine的基础value,它的invoke方法如下:
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);
}
当验证了请求(request)和响应(response)的类型以后,调用Engine的map方法获取Host用来处理来自客户端的HTTP请求。