5、容器组件
这一小节中我们将讨论请求处理组件:引擎(engine)、虚拟主机、上下文(context)组件。
5.1、引擎(engine)
引擎表示可运行的Catalina的servlet引擎实例并且包含了servlet容器的核心功能。在一个服务中只能有一个引擎。同时,作为一个真正的容器,Engine元素之下可以包含一个或多个虚拟主机。
作为请求处理的主要组件,它接收代表传入请求的对象以及输出相应结果。它主要功能是将传入请求委托给适当的虚拟主机处理。如果根据名称没有找到可处理的虚拟主机,那么将根据默认的Host来判断该由哪个虚拟主机处理。
5.2、虚拟主机
虚拟主机在Tomcat中使用Host组件表示,是web应用容器或者是Tomcat中所说的上下文。
在虚拟主机中有两个概念非常重要--主机的域名和根目录。
·域名:每个虚拟主机是由它注册的域名来标识的(例:www.host1.com)。域名是您预期的在客户端浏览器地址栏输入的值,对虚拟主机来说就是请求头部。一台虚拟主机的名称在包含它的引擎内必须是唯一的。
·根目录:根目录所在的文件夹包含将被部署到此主机的上下文。根目录可以是一个绝对路径,也可以是对CATALINA_BASE 来说的一个相对路径。
CATALINA_HOME 是一个环境变量,它引用了tomcat 二进制文件的位置。通过CATALINA_BASE 环境变量仅仅使用一个tomcat安装信息的二进制文件,就可以根据不同的配置运行多个tomcat实例(这主要由conf文件夹的内容决定)。
此外,使用一个CATALINA_BASE引用的位置(和CATALINA_HOME不同)保持标准的二进制分配独立于您的安装。这是有好处的,使tomcat升级到一个新版本变得容易,而不必担心影响已经发布的web应用程序和相关的配置文件 。
基本概念
当涉及到主机名映射到互联网协议地址时,最简单的场景,一个给定的完全合格的主机名(FQHN),例如www.swengsol.com 与映射到特定主机的IP地址相关联。
这种方法的缺点是,主机连接到互联网是相当昂贵的。这是真实存在的,尤其是当您考虑到带宽成本、网络基础设施建设 (例如:数据库/邮件服务器、防火墙、不间断电源ups、容错等) 以及维护 (包括人员配置、管理和备份) ,更不用说首先要获得一个IP地址。
因此,许多小企业认为最好的方法是从托管服务提供商那里租赁空间和基础设施。托管服务可能是提供单个的物理服务器,它可以连接到互联网并由特定的IP地址标识。这台物理服务器可以托管多个域名,每个域名代表一个提供商的客户。
例如,我们假设Acme Widgets Inc. 和 Vertico LLC 拥有它们的域名,www.acme-widgets.com 和 www.vertico.com ,这些域名被托管在同一台物理服务器上。应用程序被部署到各自对应的域,并且互不干扰。
在这种情况下,这些域被称为虚拟主机,从这种意义上来讲,每一个域看起来都是一个独立的“物理主机”。然而,事实上,他们(域)仅仅是同一台物理主机上不同的逻辑分区。
5.3、虚拟主机技术
有两种常用的方法来设置虚拟主机:
·基于独立IP地址的虚拟主机服务
·基于名称的虚拟主机服务
5.3.1基于独立IP地址的虚拟主机服务
使用这种技术,每个FQHN(完全合格的主机名)被解析为一个单独的IP地址。然而,这些IP中的每一个被解析后都映射到同一台物理机器上。
您可以使用以下的机制来实现此技术:
·多宿主服务器,也就是说它安装了多个网卡(NICs),每一个网卡都分配了IP地址
·使用操作系统功能来设置虚拟网络接口,为单个物理NIC(网卡)动态分配多个IP地址
无论在哪一种情况下,缺点是我们要获得多个IP地址,而且这些地址(至少对于IPv4来说)是一种有限的资源。
Web服务器监听为这些IP地址分配的端口,当Web服务器在一个特定的IP地址检测到传入的请求时,它会生成该IP地址的响应信息。
例如,您有一个web服务器,它运行在一个特定的在80端口监听 11.156.33.345 和 11.156.33.346 IP地址请求的物理主机上。此web服务器用以下方式响应请求:当收到来自主机域名www.host1.com的请求时,则映射到11.156.33.345 IP地址;反之当收到来自主机域名www.host2.com的请求时则映射到后面的 IP地址 11.156.33.346 。
当接收到一个来自11.156.33.346 IP地址的请求时,web服务器知道它应当为ww.host2.com对应的域准备响应信息。对用户来说,这是一个完全独立的物理服务器在为他提供服务。
5.3.2基于名称的虚拟主机服务
这是一种比较新的技术,它允许您把不同的域名映射到同一个IP地址。这些都是经过注册的正常的域名,多个DNS条目将这些域名映射到同一IP地址。
HTTP 1.1协议要求每个请求必须包含一个主机头:带有完全合格的主机域名,以及用户希望连接的端口号(如果已指定)。主机上运行的web服务器接收到此请求,解析此请求中的主机头信息,以确定相应的虚拟主机来响应此请求。简单、而且不使用不必要的IP地址,基于名称的虚拟主机服务是我们的首选。
然而,当您同时使用SSL(安全套接层)和虚拟主机时,您也许不得不使用基于IP地址的虚拟主机服务。原因是,在特定的虚拟主机响应请求之前,协商协议要进行证书认证。这是因为:SSL协议层位于HTTP协议层的下方,而且在握手消息认证完成之前,与客户端请求进行安全认证的模块无法读取HTTP请求头信息。
您也许可以同时使用SSL和基于名称的虚拟主机服务,如果您的web服务器和客户机支持RFC 3546(传输层安全性扩展http://www.ietf.org/rfc/rfc3546.txt) 指定的服务器名称标识扩展。使用此扩展,在SSL协商期间,客户端会传输主机名称给它尝试连接的对象,从而使web服务器能够处理握手信息并为正确的主机名返回证书。
虚拟主机别名
当web服务器解析别名信息时,例如它在主机头里看到了域名的别名,那么web服务器会把此别名当作虚拟主机的域名来处理。 例如,您把swengsol.com设置为虚拟主机域名www.swengsol.com的别名,那么在客户端url里无论是输入域名还是别名,您都会收到来自同一个虚拟主机的响应信息。 这种方式效果不错,当一个物理主机有多个域名时,而且您不想弄乱配置文件在为每个别名创建一组条目时。
5.4、上下文(Context)
上下文或者web应用是应用自定义代码(servlet、jsp)所存活的地方。它为web应用组织资源提供了便利。
同时context容器为servlet提供了一个ServletContext实例。在许多方面,servlet规范主要是关心这个上下文组件。例如,它规定了部署上下文的格式以及部署内容的描述符。
以下是上下文的一些重要属性:
·根目录(document base):这个路径是指war包或者未压缩的项目文件所存放的目录,可以是相对的,也可以是绝对的。
·上下文路径(context path):它是指在一个host下url中唯一标识一个web应用的部分。它帮助host容器来判断该由哪一个已部署的上下文来处理传入的请求。
也许你可能配置了默认context,它可以在找不到匹配的context的情况下来处理传入请求。该默认context可以通过将其上下文路径配置为空来标记的,因此,可以通过只有主机名的路径来访问它(译注:如http://localhost:8080/来访问)。并且该context已被tomcat默认定义为根目录下的ROOT目录。
·自动重加载(automic reload):上下文中的资源会被tomcat监控,一旦资源发生改变Tomcat就会自动重新加载资源文件。虽然该功能在开发过程中非常有用,但是在生产环境这个操作代价非常高,通常需要重启产品应用。
Context配置
Context是唯一的,这主要是因为它的配置包含多个选项。而我们之前已经注意到的conf/server.xml是用来配置Tomcat实例中一些全局性的参数。虽然在这个文件中可以配置context相关的东西,但是不推荐这样做。
相反,Tomcat推荐大家将context相关的配置从server.xml中提取出来,配置到上下文段文件中,该文件会被Tomcat监控并且可以在运行过程中重新加载。
请再次注意,server.xml只有在启动时被加载一次。
同时需要确保在context中配置一个独立明确的host和engine,因为Tomcat会在CATALINA_HOME/conf///目录下查找context相关配置。而该目录下为特定主机配置的上下文段文件则是以名称.xml命名。
默认情况下,会有一个引擎Catalina和一个名称为localhost的主机,对应的工作目录为CATALINA_HOME/conf/Catalina/localhost。但是该目录也可以是有效域名,如www.swengsol.com,那么对应目录就是CATALINA_HOME/conf/Catalina/www.swengsol.com。
另外,context片段也可以在war或部署目录中被包含在META-INF目录下。这种情况下,context文件名称必须为context.xml。
此外,Context还可以被配置在web应用描述符文件web.xml中。虽然这个片段文件是Tomcat专用的,但是由于该描述符是通过Servlet规范来描述的,因此它也适用与JavaEE下的其他轻量级servlet容器。
包装器(Wrapper)
包装器wrapper对象是context容器的子容器,表示一个单独的servlet(或者由jsp文件转换而来的servlet)。它之所以称为包装器是因为它包装了java.servlet.Servlet实例。
这是容器层次结构的最底层,添加任何子类都会导致异常。
同时包装器还对它所包装的servlet负责,包括加载、实例化servlet以及调用控制servlet生命周期相关的函数,如init()、service()和destroy()方法。
此外包装器还通过它基本的Valve来调用和其包装的servlet相关的过滤器。
在下篇文章中我们将讨论嵌套组件相关东西。