正文
1 前言
Tomcat 隶属于 Apache 基金会,是开源的轻量级 Web 应用服务器,使用非常广泛。server.xml是 Tomcat 中最重要的配置文件,server.xml的每一个元素都对应了 Tomcat 中的一个组件;通过对 XML 文件中元素的配置,可以实现对 Tomcat 中各个组件的控制。因此,学习server.xml文件的配置,对于了解和使用 Tomcat 至关重要。
本文将通过实例,介绍server.xml中各个组件的配置,并详细说明 Tomcat 各个核心组件的作用以及各个组件之间的相互关系。
说明:由于server.xml文件中元素与 Tomcat 中组件的对应关系,后文中为了描述方便,“元素”和“组件”的使用不严格区分。
2 server.xml配置实例
server.xml位于$TOMCAT_HOME/conf目录下,下面是一个server.xml实例。后文中将结合该实例讲解server.xml中,各个元素的含义和作用;在阅读后续章节过程中,可以对照该 XML 文档进行理解。
3 server.xml文档的整体结构和元素分类
3.1 整体结构
server.xml的整体结构如下:
该结构中只给出了 Tomcat 的核心组件,除了核心组件外,Tomcat 还有一些其他组件,下面介绍一下组件的分类。
3.2 元素分类
server.xml文件中的元素可以分为以下 4 类:
第 1 类:顶层元素,
第 2 类:连接器,
第 3 类:容器,
容器的功能是处理 Connector 接收进来的请求,并产生相应的响应。Engine、Host 和 Context 都是容器,但它们不是平行的关系,而是父子关系:Engine 包含 Host,Host 包含 Context。一个 Engine 组件可以处理 Service 中的所有请求,一个 Host 组件可以处理发向一个特定虚拟主机的所有请求,一个 Context 组件可以处理一个特定 Web 应用的所有请求。
第 4 类:内嵌组件,可以内嵌到容器中的组件
实际上,Server、Service、Connector、Engine、Host 和 Context 是最重要的最核心的 Tomcat 组件,其他组件都可以归为内嵌组件。下面将详细介绍 Tomcat 中各个核心组件的作用,以及相互之间的关系。
4 核心组件
本部分将分别介绍各个核心组件的作用、特点以及配置方式等。
4.1 Server
Server 元素在最顶层,代表整个 Tomcat 容器,因此它必须是server.xml中唯一一个最外层的元素。一个 Server 元素中可以有一个或多个 Service 元素。
在第一部分的例子中,在最外层有一个
Server 的主要任务,就是提供一个接口让客户端能够访问到这个 Service 集合,同时维护它所包含的所有的 Service 的生命周期,包括如何初始化、如何结束服务、如何找到客户端要访问的 Service 等。
4.2 Service
Service 的作用,是在 Connector 和 Engine 外面包了一层,把它们组装在一起,对外提供服务。一个 Service 可以包含多个 Connector,但是只能包含一个 Engine;其中 Connector 的作用是从客户端接收请求,Engine 的作用是处理接收进来的请求。
在第一部分的例子中,Server 中包含一个名称为Catalina的 Service。实际上,Tomcat 可以提供多个 Service,不同的 Service 监听不同的端口,后文会有介绍。
4.3 Connector
Connector 的主要功能,是接收连接请求,创建 Request 和 Response 对象用于和请求端交换数据;然后分配线程让 Engine 来处理这个请求,并把产生的 Request 和 Response 对象传给 Engine。
通过配置 Connector,可以控制请求 Service 的协议及端口号。在第一部分的例子中,Service 包含两个 Connector:
通过配置第 1 个 Connector,客户端可以通过 8080 端口号使用http协议访问 Tomcat。其中,protocol属性规定了请求的协议,port规定了请求的端口号,redirectPort表示当强制要求https而请求是http时,重定向至端口号为 8443 的 Connector,connectionTimeout表示连接的超时时间。在这个例子中,Tomcat 监听 HTTP 请求,使用的是 8080 端口,而不是正式的 80 端口;实际上,在正式的生产环境中,Tomcat 也常常监听 8080 端口,而不是 80 端口。这是因为在生产环境中,很少将 Tomcat 直接对外开放接收请求,而是在 Tomcat 和客户端之间加一层代理服务器(如 nginx),用于请求的转发、负载均衡、处理静态文件等;通过代理服务器访问 Tomcat 时,是在局域网中,因此一般仍使用 8080 端口。
通过配置第 2 个 Connector,客户端可以通过 8009 端口号使用AJP协议访问 Tomcat。AJP协议负责和其他的 HTTP 服务器(如 Apache)建立连接;在把 Tomcat 与其他 HTTP 服务器集成时,就需要用到这个连接器。之所以使用 Tomcat 和其他服务器集成,是因为 Tomcat 可以用作 Servlet/JSP 容器,但是对静态资源的处理速度较慢,不如 Apache 和 IIS 等 HTTP 服务器;因此常常将 Tomcat 与 Apache 等集成,前者作 Servlet 容器,后者处理静态资源,而 AJP 协议便负责 Tomcat 和 Apache 的连接。Tomcat 与 Apache 等集成的原理如下图所示:
4.4 Engine
Engine 组件在 Service 组件中有且只有一个;Engine 是 Service 组件中的请求处理组件。Engine 组件从一个或多个 Connector 中接收请求并处理,并将完成的响应返回给 Connector,最终传递给客户端。
前面已经提到过,Engine、Host 和 Context 都是容器,但它们不是平行的关系,而是父子关系:Engine 包含 Host,Host 包含 Context。
在第一部分的例子中,Engine 的配置语句如下:
其中,name属性用于日志和错误信息,在整个 Server 中应该唯一。defaultHost属性指定了默认的 host 名称,当发往本机的请求指定的 host 名称不存在时,一律使用 defaultHost 指定的 host 进行处理;因此,defaultHost的值,必须与 Engine 中的一个 Host 组件的name属性值匹配。
4.5 Host
4.5.1 Engine 与 Host
Host 是 Engine 的子容器。Engine 组件中可以内嵌 1 个或多个 Host 组件,每个 Host 组件代表 Engine 中的一个虚拟主机。Host 组件至少有一个,且其中一个的name必须与 Engine 组件的defaultHost属性相匹配。
4.5.2 Host 的作用
Host 虚拟主机的作用,是运行多个 Web 应用(一个 Context 代表一个 Web 应用),并负责安装、展开、启动和结束每个 Web 应用。
Host 组件代表的虚拟主机,对应了服务器中一个网络名实体(如www.test.com,或 IP 地址116.25.25.25),为了使用户可以通过网络名连接 Tomcat 服务器,这个名字应该在 DNS 服务器上注册。
客户端通常使用主机名来标识它们希望连接的服务器;该主机名也会包含在 HTTP 请求头中。Tomcat 从 HTTP 头中提取出主机名,寻找名称匹配的主机。如果没有匹配,请求将发送至默认主机。因此默认主机不需要是在 DNS 服务器中注册的网络名,因为任何与所有 Host 名称不匹配的请求,都会路由至默认主机。
4.5.3 Host 的配置
在第一部分的例子中,Host 的配置如下:
下面对其中配置的属性进行说明:
name属性指定虚拟主机的主机名,一个 Engine 中有且仅有一个 Host 组件的name属性与 Engine 组件的defaultHost属性相匹配;一般情况下,主机名需要是在 DNS 服务器中注册的网络名,但是 Engine 指定的defaultHost不需要,原因在前面已经说明。
unpackWARs指定了是否将代表 Web 应用的 WAR 文件解压;如果为true,通过解压后的文件结构运行该 Web 应用,如果为false,直接使用 WAR 文件运行 Web 应用。
Host 的autoDeploy和appBase属性,与 Host 内 Web 应用的自动部署有关;此外,本例中没有出现的xmlBase和deployOnStartup属性,也与 Web 应用的自动部署有关;将在下一节(Context)中介绍。
4.6 Context
4.6.1 Context 的作用
Context 元素代表在特定虚拟主机上运行的一个 Web 应用。在后文中,提到 Context、应用或 Web 应用,它们指代的都是 Web 应用。每个 Web 应用基于 WAR 文件,或 WAR 文件解压后对应的目录(这里称为应用目录)。Context 是 Host 的子容器,每个 Host 中可以定义任意多的 Context 元素。
在第一部分的例子中,可以看到server.xml配置文件中并没有出现 Context 元素的配置。这是因为,Tomcat 开启了自动部署,Web 应用没有在server.xml中配置静态部署,而是由 Tomcat 通过特定的规则自动部署。下面介绍一下 Tomcat 自动部署 Web 应用的机制。
4.6.2 Web 应用自动部署
第 1 点:Host 的配置
要开启 Web 应用的自动部署,需要配置所在的虚拟主机;配置的方式就是前面提到的 Host 元素的deployOnStartup和autoDeploy属性。如果deployOnStartup和autoDeploy设置为true,则 tomcat 启动自动部署:当检测到新的 Web 应用或 Web 应用的更新时,会触发应用的部署(或重新部署)。二者的主要区别在于,deployOnStartup为true时,Tomcat 在启动时检查 Web 应用,且检测到的所有 Web 应用视作新应用;autoDeploy为true时,Tomcat 在运行时定期检查新的 Web 应用或 Web 应用的更新。除此之外,二者的处理相似。
通过配置deployOnStartup和autoDeploy可以开启虚拟主机自动部署 Web 应用;实际上,自动部署依赖于检查是否有新的或更改过的 Web 应用,而 Host 元素的appBase和xmlBase设置了检查 Web 应用更新的目录。
其中,appBase属性指定 Web 应用所在的目录,默认值是webapps,这是一个相对路径,代表 Tomcat 根目录下webapps文件夹。xmlBase属性指定 Web 应用的 XML 配置文件所在的目录,默认值为conf/
第 2 点:检查 Web 应用更新
一个 Web 应用可能包括以下文件:XML 配置文件,WAR 包,以及一个应用目录(该目录包含 Web 应用的文件结构);其中 XML 配置文件位于xmlBase指定的目录,WAR 包和应用目录位于appBase指定的目录。Tomcat 按照如下的顺序进行扫描,来检查应用更新:
扫描虚拟主机指定的xmlBase下的 XML 配置文件;
扫描虚拟主机指定的appBase下的 WAR 文件;
扫描虚拟主机指定的appBase下的应用目录。
第 3 点:
Context 元素最重要的属性是docBase和path,此外reloadable属性也比较常用。
docBase指定了该 Web 应用使用的 WAR 包路径,或应用目录。需要注意的是,在自动部署场景下(配置文件位于xmlBase中),docBase不在appBase目录中,才需要指定;如果docBase指定的 WAR 包或应用目录就在docBase中,则不需要指定,因为 Tomcat 会自动扫描appBase中的 WAR 包和应用目录,指定了反而会造成问题。
path指定了访问该 Web 应用的上下文路径,当请求到来时,Tomcat 根据 Web 应用的path属性与 URI 的匹配程度来选择 Web 应用处理相应请求。例如,Web 应用app1的path属性是/app1,Web 应用app2的path属性是/app2,那么请求/app1/index.html会交由app1来处理;而请求/app2/index.html会交由app2来处理。如果一个 Context 元素的path属性为"",那么这个 Context 是虚拟主机的默认 Web 应用;当请求的 URI 与所有的path都不匹配时,使用该默认Web应用来处理。
但是,需要注意的是,在自动部署场景下(配置文件位于xmlBase中),不能指定path属性,path属性由配置文件的文件名、WAR 文件的文件名或应用目录的名称自动推导出来。如扫描 Web 应用时,发现了xmlBase目录下的app1.xml,或appBase目录下的app1.WAR或app1应用目录,则该 Web 应用的path属性是app1。如果名称不是app1而是ROOT,则该 Web 应用是虚拟主机默认的 Web 应用,此时path属性推导为""。
reloadable属性指示 Tomcat 是否在运行时监控在WEB-INF/classes和WEB-INF/lib目录下class文件的改动。如果值为true,那么当class文件改动时,会触发 Web 应用的重新加载。在开发环境下,reloadable设置为true便于调试;但是在生产环境中设置为true会给服务器带来性能压力,因此reloadable参数的默认值为false。
下面来看自动部署时,xmlBase下的 XML 配置文件app1.xml的例子:
在该例子中,docBase位于 Host 的appBase目录之外;path属性没有指定,而是根据app1.xml自动推导为app1;由于是在开发环境下,因此reloadable设置为true,便于开发调试。
第 4 点:自动部署举例
最典型的自动部署,就是当我们安装完 Tomcat 后,$TOMCAT_HOME/webapps目录下有如下文件夹:
当我们启动 Tomcat 后,可以使用http://localhost:8080/来访问 Tomcat,其实访问的就是 ROOT 对应的 Web 应用;我们也可以通过http://localhost:8080/docs来访问docs应用,同理我们可以访问examples/host-manager/manager这几个 Web 应用。
4.6.3 server.xml中静态部署 Web 应用
除了自动部署,我们也可以在server.xml中通过
docBase:静态部署时,docBase可以在appBase目录下,也可以不在;本例中,docBase不在appBase目录下;
path:静态部署时,可以显式指定path属性,但是仍然受到了严格的限制:只有当自动部署完全关闭(deployOnStartup和autoDeploy都为false)或docBase不在appBase中时,才可以设置path属性。在本例中,docBase不在appBase中,因此path属性可以设置;
reloadable属性的用法与自动部署时相同。
5 核心组件的关联
5.1 整体关系
核心组件之间的整体关系,在上一部分有所介绍,这里总结一下:
Server 元素在最顶层,代表整个 Tomcat 容器;一个 Server 元素中可以有一个或多个 Service 元素。
Service 在 Connector 和 Engine 外面包了一层,把它们组装在一起,对外提供服务。一个 Service 可以包含多个 Connector,但是只能包含一个 Engine;Connector 接收请求,Engine 处理请求。
Engine、Host 和 Context 都是容器,且 Engine 包含 Host,Host 包含 Context。每个 Host 组件代表 Engine 中的一个虚拟主机;每个 Context 组件代表在特定 Host 上运行的一个 Web 应用。
5.2 如何确定请求由谁处理?
当请求被发送到 Tomcat 所在的主机时,如何确定最终哪个 Web 应用来处理该请求呢?
5.2.1 根据协议和端口号选定 Service 和 Engine
Service 中的 Connector 组件可以接收特定端口的请求,因此,当 Tomcat 启动时,Service 组件就会监听特定的端口。在第一部分的例子中,catalina这个 Service 监听了 8080 端口(基于 HTTP 协议)和 8009 端口(基于 AJP 协议)。当请求进来时,Tomcat 便可以根据协议和端口号选定处理请求的 Service;Service 一旦选定,Engine 也就确定。
通过在 Server 中配置多个 Service,可以实现通过不同的端口号来访问同一台机器上部署的不同应用。
5.2.2 根据域名或 IP 地址选定 Host
Service 确定后,Tomcat 在 Service 中寻找名称与域名/IP 地址匹配的 Host 处理该请求。如果没有找到,则使用 Engine 中指定的defaultHost来处理该请求。在第一部分的例子中,由于只有一个 Host(name属性为localhost),因此该 Service/Engine 的所有请求都交给该 Host 处理。
5.2.3 根据 URI 选定 Context/Web 应用
这一点在 Context 一节有详细的说明:Tomcat 根据应用的path属性与 URI 的匹配程度来选择 Web 应用处理相应请求,这里不再赘述。
5.2.4 举例
以请求http://localhost:8080/app1/index.html为例,首先通过协议和端口号(http和8080)选定 Service;然后通过主机名(localhost)选定 Host;然后通过 URI(/app1/index.html)选定 Web 应用。
5.3 如何配置多个服务
通过在 Server 中配置多个 Service 服务,可以实现通过不同的端口号来访问同一台机器上部署的不同 Web 应用。在server.xml中配置多服务的方法非常简单,分为以下几步:
复制
修改端口号:根据需要监听的端口号修改
以 Win7 为例,可以用如下方法找出某个端口是否被其他进程占用:netstat -aon|findstr "8081"发现 8081 端口被 PID 为 2064 的进程占用,tasklist |findstr "2064"发现该进程为FrameworkService.exe(这是 McAfee 杀毒软件的进程)。
修改 Service 和 Engine 的name属性;
修改 Host 的appBase属性(如webapps2)
Web 应用仍然使用自动部署;
将要部署的 Web 应用(WAR 包或应用目录)拷贝到新的appBase下。
以第一部分的server.xml为例,多个 Service 的配置如下:
<?xml version='1.0' encoding='utf-8'?>
再将原webapps下的docs目录拷贝到webapps2中,则通过如下两个接口都可以访问docs应用:
http://localhost:8080/docs/
http://localhost:8084/docs/
6 其他组件
除核心组件外,server.xml中还可以配置很多其他组件。下面只介绍第一部分例子中出现的组件,如果要了解更多内容,可以查看 Tomcat 官方文档。
6.1 Listener
Listener(即监听器)定义的组件,可以在特定事件发生时执行特定的操作;被监听的事件通常是 Tomcat 的启动和停止。监听器可以在 Server、Engine、Host 或 Context 中,本例中的监听器都是在 Server 中。实际上,本例中定义的 6 个监听器,都只能存在于 Server 组件中。监听器不允许内嵌其他组件。
监听器需要配置的最重要的属性是className,该属性规定了监听器的具体实现类,该类必须实现了org.apache.catalina.LifecycleListener接口。下面依次介绍例子中配置的监听器:
VersionLoggerListener:当 Tomcat 启动时,该监听器记录 Tomcat、Java 和操作系统的信息。该监听器必须是配置的第一个监听器。
AprLifecycleListener:Tomcat 启动时,检查 APR 库,如果存在则加载。APR,即 Apache Portable Runtime,是 Apache 可移植运行库,可以实现高可扩展性、高性能,以及与本地服务器技术更好的集成。
JasperListener:在 Web 应用启动之前初始化 Jasper,Jasper 是 JSP 引擎,把 JVM 不认识的 JSP 文件解析成 Java 文件,然后编译成class文件供 JVM 使用。
JreMemoryLeakPreventionListener:与类加载器导致的内存泄露有关。
GlobalResourcesLifecycleListener:通过该监听器,初始化
ThreadLocalLeakPreventionListener:当 Web 应用因thread-local导致的内存泄露而要停止时,该监听器会触发线程池中线程的更新。当线程执行完任务被收回线程池时,活跃线程会一个一个的更新。只有当 Web 应用(即 Context 元素)的renewThreadsWhenStoppingContext属性设置为true时,该监听器才有效。
6.2 GlobalNamingResources 与 Realm
第一部分的例子中,Engine 组件下定义了 Realm 组件:
Realm,可以把它理解成“域”;Realm 提供了一种用户密码与 Web 应用的映射关系,从而达到角色安全管理的作用。在本例中,Realm 的配置使用name为 UserDatabase 的资源实现。而该资源在 Server 元素中使用 GlobalNamingResources 配置:
GlobalNamingResources 元素定义了全局资源,通过配置可以看出,该配置是通过读取$TOMCAT_HOME/conf/tomcat-users.xml实现的。
6.3 Valve
在第一部分的例子中,Host 元素内定义了 Valve 组件:
单词 Valve 的意思是“阀门”,在 Tomcat 中代表了请求处理流水线上的一个组件;Valve 可以与 Tomcat 的容器(Engine、Host 或 Context)关联。不同的 Valve 有不同的特性,下面介绍一下本例中出现的 AccessLogValve。
AccessLogValve 的作用是通过日志记录其所在的容器中处理的所有请求,在本例中,Valve 放在 Host 下,便可以记录该 Host 处理的所有请求。AccessLogValve 记录的日志就是访问日志,每天的请求会写到一个日志文件里。AccessLogValve 可以与 Engine、Host 或 Context 关联;在本例中,只有一个 Engine,Engine 下只有一个 Host,Host 下只有一个 Context,因此 AccessLogValve 放在三个容器下的作用其实是类似的。本例的 AccessLogValve 属性的配置,使用的是默认的配置;下面介绍 AccessLogValve 中各个属性的作用:
className:规定了 Valve 的类型,是最重要的属性;本例中,通过该属性规定了这是一个 AccessLogValve。
directory:指定日志存储的位置,本例中,日志存储在$TOMCAT_HOME/logs目录下。
prefix:指定了日志文件的前缀。
suffix:指定了日志文件的后缀。通过directory、prefix和suffix的配置,在$TOMCAT_HOME/logs目录下,可以看到如下所示的日志文件。
pattern:指定记录日志的格式,本例中各项的含义如下:
%h:远程主机名或 IP 地址;如果有 Nginx 等反向代理服务器进行请求分发,该主机名/IP 地址代表的是 Nginx,否则代表的是客户端。后面远程的含义与之类似,不再解释。
%l:远程逻辑用户名,一律是-,可以忽略。
%u:授权的远程用户名,如果没有,则是-。
%t:访问的时间。
%r:请求的第一行,即请求方法(Get/Post 等)、URI 及协议。
%s:响应状态,200、404等等。
%b:响应的数据量,不包括请求头,如果为0,则是-。
例如,下面是访问日志中的一条记录
pattern的配置中,除了上述各项,还有一个非常常用的选项是%D,含义是请求处理的时间(单位是毫秒),对于统计分析请求的处理速度帮助很大。
开发人员可以充分利用访问日志,来分析问题、优化应用。例如,分析访问日志中各个接口被访问的比例,不仅可以为需求和运营人员提供数据支持,还可以使自己的优化有的放矢;分析访问日志中各个请求的响应状态码,可以知道服务器请求的成功率,并找出有问题的请求;分析访问日志中各个请求的响应时间,可以找出慢请求,并根据需要进行响应时间的优化。