Tomcat中的Host和Engine级别的servlet容器
这边文章主要介绍的是Host容器 和 Engine容器。如果你想在同一个Tomcat上部署运行多个Context容器的话,你就需要使用Host容器,从理论上来讲,如果你的Tomcat只想要部署一个Context容器的话,你可以不使用Host容器。
在org.apache.catalina.Context接口的描述有下一段话:
Context容器的父容器通常是Host容器,也有可能是其他实现,或者如果不是必要的话,就可以不使用父容器。
但是 在tomcat的实际部署中,总会使用一个Host容器,在下面在解释原因,
Engine容器表示Catalina的整个Servlet引擎,如果使用了Engine容器,那么它总是处于容器层级的最顶层,添加到Enginer容器中的子容器通常是org.apache.catalina.Host 或者 org.apahce.catalina.Context的实现,默认情况下Tomcat会使用一个Engine容器并且使用一个Host容器作为其子容器,
Host接口
host容器是 org.apahce.catalina.Host接口的实例,Host接口继承自Container接口
package org.apache.catalina; /** * * <p> * <b>Title:Host.java</b> * </p> * <p> * Copyright:ChenDong 2018 * </p> * <p> * Company:仅学习时使用 * </p> * <p> * 类功能描述:Host是表示Catalina servlet引擎中的虚拟主机的容器。它在以下类型的场景中很有用: * * * * 您希望使用拦截器来查看此特定虚拟主机处理的每个请求。 * * 您希望使用独立的HTTP连接器运行Catalina,但是仍然希望支持多个虚拟主机。 * * 通常,在部署连接到Web服务器(如Apache)的Catalina时,您不会使用主机,因为连接器将利用Web服务器的设施来确定应该使用哪个上下文( * 或者甚至哪个包装器)来处理这个请求。 * * 附加到主机的父容器通常是一个引擎,但是可以是一些其他的实现,或者如果不必要的话可以省略。 * * * * 附加到主机的子容器通常是上下文的实现(表示单个servlet上下文)。 * </p> * * @author * @date 2018年12月15日 下午9:28:58 * @version 1.0 */ public interface Host extends Container { // ----------------------------------------------------- Manifest Constants /** * * * 当使用<code>addAlias()</code>方法添加新的别名时发送的 {@code ContainerEvent}事件类型。 */ public static final String ADD_ALIAS_EVENT = "addAlias"; /** * 当使用<code>removeAlias()</code>移除一个旧的别名时 触发的 {@code ContainerEvent}事件类型 */ public static final String REMOVE_ALIAS_EVENT = "removeAlias"; // ------------------------------------------------------------- Properties /** * 返回此{@code Host}容器的 根路径,它可以是 绝对路径、相对路径、或者URL */ public String getAppBase(); /** * * 为这个{@code Host}容器 设置一个根路径,它可以是 绝对路径、相对路径、或者URL * * @param appBase * 新的容器根路径 */ 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); /** * * 为新的web应用程序设置 {@code DefaultContext}。 * * @param defaultContext * 新的 DefaultContext */ public void addDefaultContext(DefaultContext defaultContext); /** * 为新的web应用程序检索 并返回 DefaultContext. */ public DefaultContext getDefaultContext(); /** * 返回此容器表示的虚拟主机的规范、完全限定的名称 */ public String getName(); /** * 设置此容器表示的虚拟主机的规范、完全限定的名称 * * @param name * 虚拟主机的名称 * * @exception IllegalArgumentException * 如果这个名字是 {@code null} */ public void setName(String name); // --------------------------------------------------------- Public Methods /** * * 将DefaultContext 的 config 导入到web应用程序上下文中。 * * @param context * 导入默认Context的web应用程序Context */ public void importDefaultContext(Context context); /** * 添加应该映射到同一主机的别名 * * @param alias * 要被添加的别名 */ public void addAlias(String alias); /** * * 返回此主机的别名集。如果没有定义,则返回一个零长度数组 */ public String[] findAliases(); /** * * 返回一个用来处理引用Http请求的 Context 根据 请求的URI 若果不存在则返回 * * @param uri * Request URI to be mapped */ public Context map(String uri); /** * 从此主机的别名中删除指定的别名 * * @param alias * 要被删除的别名 */ public void removeAlias(String alias); }
下面说下它在Tomat中的标准实现
StandardHost类
在Catalina中的 org.apache.catalina.core.StandardHost类 是 org.apache.catalin.Host接口的标准实现,该类继承自 org.apache.catalina.core.ContainerBase类 ,实现了 Host 和 Deployer接口。
与StandardContext 和 StandardWrapper 类 相似,StandardHost类的构造器函数会将一个基础阀的实例 添加到其管道对相中。
/** * * 创建一个带有基础阀的 {@code StandardHost}实例 */ public StandardHost() { super(); pipeline.setBasic(new StandardHostValve()); }
那么 它的基础阀 就是 org.apahce.catalina.core.StandardHostValue类的实例,
当调用 StandardHost 类的 start()方法时,StandardHost实例 会新添加两个阀,分别是 ErrorReportValue类 和 ErrorDispatcherValue类的实例,这个两个阀均位于org.apahce.catalina.values包下,
1 /** 2 * 启动这个Host. 3 * 4 * @exception LifecycleException 5 * 如果此组件检测到阻止其启动的致命错误 6 * 7 */ 8 public synchronized void start() throws LifecycleException { 9 // 如果 errorReportValveClass 阀的 完全限定名 不为空 的话 10 if ((errorReportValveClass != null) && (!errorReportValveClass.equals(""))) { 11 try { 12 Valve valve = (Valve) Class.forName(errorReportValveClass).newInstance(); 13 //添加这个ErrorReportValve阀 14 addValve(valve); 15 } catch (Throwable t) { 16 log(sm.getString("standardHost.invalidErrorReportValveClass", errorReportValveClass)); 17 } 18 } 19 20 //添加一个ErrorDispatcherValve阀 21 addValve(new ErrorDispatcherValve()); 22 23 super.start(); 24 25 }
变量 errorReportValueClass的值 定义在StandardHost类中;
private String errorReportValveClass = "org.apache.catalina.valves.ErrorReportValve";
每当引入一个Http请求的时候,都会调用StandardHost实例的 invoke方法,由于StandardHost类并没有提供invoke方法的实现,因此它会调用父类 ContainerBase 类的 invoke方法,而ContainerBase类 的invoke方法将会调用StandardHost类的 基础阀StandardHostValue实例的invoke方法,StandardHostValue的invoke方法将会调用StandardHostr类的map方法来获取响应的Context实例来处理Http请求。
1 /** 2 * 3 * 返回一个Context实例 来处理这个 相对于Host容器的 相对URI所代表的请求,如果没有则返回 <code>null</code> 4 * 5 * @param uri 6 * 要被映射的请求URI 7 */ 8 public Context map(String uri) { 9 10 if (debug > 0) 11 log("Mapping request URI '" + uri + "'"); 12 if (uri == null) 13 return (null); 14 15 // Match on the longest possible context path prefix 16 // 匹配可能是最长的Context路径前缀 17 if (debug > 1) 18 log(" Trying the longest context path prefix"); 19 Context context = null; 20 String mapuri = uri; 21 while (true) { 22 // 不断尝试根据路径去子容器中找对应的Context 23 context = (Context) findChild(mapuri); 24 if (context != null) 25 break; 26 int slash = mapuri.lastIndexOf('/'); 27 if (slash < 0) 28 break; 29 // 不断截取路径最后一个/之前的路径 做匹配路径 30 mapuri = mapuri.substring(0, slash); 31 } 32 33 34 // 如果没有匹配到Context 则选择 默认的Context 35 if (context == null) { 36 if (debug > 1) 37 log(" Trying the default context"); 38 context = (Context) findChild(""); 39 } 40 41 //如果还是没有选中的 Context 直接返回null 并返回 错误信息 42 if (context == null) { 43 log(sm.getString("standardHost.mappingError", uri)); 44 return (null); 45 } 46 47 // 返回映射的上下文(如果有的话) 48 if (debug > 0) 49 log(" Mapped to context '" + context.getPath() + "'"); 50 return (context); 51 52 }