Tomcat Host组件在Tomcat中代表一个“Virtual Host”,使Tomcat可以在单个Tomcat实例中支持多个“Virtual Host”,这样,我们也就可以知道一个Engine可以包含多个Host组件。Host组件包含两个主要的Valve,一个Valve决定请求由哪一个Context处理,另一个Valve负责处理在Context中未被捕获的异常。除了两个Valve以外,Host组件还包含了一个Configurator,它的作用是负责应用的部署,加载等事情。
一:Virtual Host
在了解Virtual Host之前,我们先来了解下什么是Host。我们知道,在我们访问一个网站时,我们需要在浏览器的地址栏输入一个网页地址,浏览器会试图将域名解析成IP,这个IP代表了连接到互联网的一台主机(Host)。在浏览器向主机发送的HTTP请求中,也包含了请求的Host信息:
在最简单的情况下,一台主机只需要对应一个IP,提供一个web服务即可,这种情况下一个IP就对应一台物理主机(Physical Host)。然而,在多数情况下,一台主机不会只提供一个web服务,因而一台物理主机就需要虚拟出多台主机来,这就是Virtual Host。Virtual Host根据实现技术的不同可以分为基于名称的Virtual Host和基于IP的Virtual Host。
基于名称的Virtual Host
基于名称的Virtual Host,对于基于名称的Virtual Host来说,每一个Virtual Host对应一个域名,这些域名都解析到同一个IP下去,这样,这些Virtual Host就共享了这个IP对应的物理主机的资源,在Tomcat中,主要配置以下信息就配置了一个基于名称的Virtual Host:
1
2
3
4
|
<
Host
name
=
"localhosts"
appBase
=
"webapps"
unpackWARs
=
"true"
autoDeploy
=
"true"
xmlValidation
=
"false"
xmlNamespaceAware
=
"false"
>
</
Host
>
|
当然,在Tomcat中,如果多个Virtual Host仅仅名称是不一样的,其他都是一样的,那么就可以使用别名来配置,如下图:
1
2
3
4
5
|
<
Host
name
=
"localhosts"
appBase
=
"webapps"
unpackWARs
=
"true"
autoDeploy
=
"true"
xmlValidation
=
"false"
xmlNamespaceAware
=
"false"
>
<
Alias
>khotyn.org</
Alias
>
</
Host
>
|
基于IP的Virtual Host
不同于基于名称的Virtual Host,在基于IP的Virtual Host中,可以将多个IP地址绑定到同一台物理主机上去,这个是怎么做到的呢?一个方式在物理主机上配置多块网卡,另一个就是通过Virtual Network Interfaces来实现,在Tomcat中配置基于IP的Virtual Host,可以参考下图中的配置:
1
2
|
<
Connector
port
=
"8080"
protocol
=
"org.apache.coyote.http11.Http11NioProtocol"
connectionTimeout
=
"20000"
redirectPort
=
"8443"
executor
=
"tomcatThreadPool"
address
=
"127.0.0.2"
useIPVHosts
=
"true"
/>
|
注意,需要将Connector的useIPVHosts设置成true,默认情况为false,才能够使用基于IP的Virtual Host。
二:StandardHost和HostConfig
StandardHost
Tomcat对Host的默认实现是StandardHost,StandardHost是一个非常简单地类,其属性和方法基本上和server.xml中对应的host元素的属性一一对应,但是值得注意的是,StandardHost在初始化的时候,会初始化一个HostConfig,并把它注册成一个Lifecycle Listener,用于负责应用的部署。StandardHost另外一个特别的地方就是在初始化的时候不仅仅加入它的Basic Valve:StandardHostValve到Pipeline中,而且加入了一个ErrorReportValve到Pipeline中去。
HostConfig
在Host调用其start方法的时候,它也生成了一个HostConfig实例,并让它去监听Host的启动,关闭以及定时事件。
在Host中每一个应用由一个Context和一个DeployedApplication来表示,你可以在server.xml或者CATALINA_BASE/conf/[engineName]/[hostName]下或者在应用的META-INF目录下配置context。
前一种在解析server.xml的时候就会被解析成Context,后两种会在HostConfig部署应用的时候被解析并生成对应的Context。
DeployedApplication是HostConfig的一个内部类,是用来监视应用的文件的修改的,它有两个重要的属性:
- redeployResources:这里面的文件被删除或者被修改会造成应用的重新部署。
- reloadResources:这里面的文件被删除或者被修改会造成应用的重新加载。具体哪些文件需要被监视可以在Context的WatchedResource里面配置。
在这里需要区分应用的重新部署和重新加载是不一样的行为:重新部署一个Context只需要调用Context的stop方法,然后在调用start方法就可以了,但是重新加载一个Context则需要将Context完全从Host中移除,然后重新生成一个Context,并加入到Host中去。
HostConfig的一个重要的作用就是负责应用的部署,在HostConfig中,“应用”被分成3种不用的类型:context.xml描述文件,war包,文件夹,其部署方式都是类似的,下面仅以war包的部署为例来讲一讲在HostConfig中大概的一个应用部署过程:
- 判断是否已经部署了该war,如果已经部署就直接返回。
- 看下war包里面有没有META-INF/context.xml文件,有就将其复制一份到CATALINA_BASE/conf/[engineName]/[hostName]目录下并解析成一个Context
- 生成一个DeloyedApplication实例并将需要监视的文件放入其中。
- 设置2中解析到的Context(如果2中没有context.xml文件,就实例化一个Context)的一些属性,比如DocBase等。
- 将Context加入到host中去。
- 如果war包是需要被解开来被部署的,那么就需要更新Context的Docbase属性(具体的解包过程在步骤5中由Context的start方法去完成)。
三:StandardHostValve和ErrorReportValve
对于每一个组件,我们可以通过观察其包含的Valve的实现来了解其功能,在Host组件中,有两个重要的Valve:StandardHostValve和ErrorReportValve;
StandardHostValve
StandardHostValve的工作一个是决定请求由Host内的哪一个Context去处理,另一个处理应用没有捕获的异常,把处理未捕获异常的工作放在Host中是因为Host包含的子组件就是Context了,对应一个应用,理所当然,这个工作就放在了应用的上一层组件Host中处理。前一个决定哪一个Context来处理请求的代码相当简单,无非就是从Request中取出Context,并交给它处理:
1
|
context.getPipeline().getFirst().invoke(request, response);
|
我们重点来看一下StandardHostValve对异常的处理,但是在这个之前,我们还是了解下Servlet规范(JSR154)对error page的描述,因为StandardHostValve对异常的处理就是按照JSR154来的:
为了让开发者能够在servlet产生错误的时候返回给客户端一些自定义的信息,我们可以在部署描述符(web.xml)中定义error page。这个配置可以让容器在servlet、filter调用response的sendError方法或者servlet产生错误、异常传播到容器的时候,向客户端返回一个自定义的错误页面。
也就是说Servlet容器需要能够根据web.xml中定义的ErrorPage的配置来处理由应用进入容器的错误。
下面通过一幅流程图来描述StandardHostValve对错误的处理过程:
ErrorReportValve
这个Valve的作用更像是给StandardHostValve擦屁股的,如果请求没有被提交,并且包含了javax.servlet.error.exception,ErrorReportValve就调用response.sendError,然后产生一个默认的错误页面给客户端。