通过这份配置文件,我们就可以了解 Tomcat 的体系结构,示例文件如下所示
<Server port="8005" shutdown="SHUTDOWN">
<Listener className="org.apache.catalina.startup.VersionLoggerListener" />
<Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" />
<Listener className="org.apache.catalina.core.JasperListener" />
<Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />
<Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
<Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" />
<GlobalNamingResources>
<Resource name="UserDatabase" auth="Container"
type="org.apache.catalina.UserDatabase"
description="User database that can be updated and saved"
factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
pathname="conf/tomcat-users.xml" />
</GlobalNamingResources>
<Service name="Catalina">
<Connector port="8080" protocol="HTTP/1.1" maxThreads=”150″
connectionTimeout="20000"
redirectPort="8443" />
<Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />
<Connector port=”8443″ maxThreads=”150″ minSpareThreads=”25″
maxSpareThreads=”75″ enableLookups=”false” acceptCount=”100″
debug=”0″ scheme=”https” secure=”true”
clientAuth=”false” sslProtocol=”TLS” />
<Engine name="Catalina" defaultHost="localhost">
<Realm className="org.apache.catalina.realm.LockOutRealm">
<Realm className="org.apache.catalina.realm.UserDatabaseRealm"
resourceName="UserDatabase"/>
</Realm>
<Host name="localhost" appBase="webapps" unpackWARs="true" autoDeploy="true">
<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
prefix="localhost_access_log." suffix=".txt"
pattern="%h %l %u %t "%r" %s %b" />
</Host>
</Engine>
</Service>
</Server>
1.tomcat核心组件介绍
<Server>
<Service>
<Connector />
<Connector />
<Engine>
<Host>
<Context />
</Host>
</Engine>
</Service>
</Server>
上述结构中包含了 Tomcat 的核心组件:Server 组件在最顶层,代表整个 Tomcat 容器。一个 Server 组件中可以包含一个或多个 Service 组件。Service 在 Connector 和 Engine 外面包了一层,把它们组装在一起对外提供服务。一个 Service 可以包含多个 Connector,但是只能包含一个 Engine。不同 Connector 负责接收不同端口上相应协议的请求,而 Engine 负责处理请求。Engine 包含一个或多个 Host,Host 包含一个或多个 Context,Engine、Host、Context 都属于容器组件,一个 Host 组件代表一个虚拟主机,一个 Context 组件代表在隶属 Host 上运行的一个 Web 应用。
p1
1.1.顶层类组件 Server
它是整个配置文件的唯一根元素,代表整个 Tomcat 容器,内部可以包含多个 Service。Server 主要职责就是管理多个 Service,对外提供给客户端访问,同时维护所有 Service 的生命周期,包括初始化服务、结束服务、定位客户端要访问的 Service 等等。所有 Tomcat 组件的生命周期都是通过 Lifecycle 接口来控制的,组件只要继承这个接口并实现其中的方法就可以统一被父组件控制了,这样层层递进 Server 组件就可以控制所有组件的生命周期了,而控制 Server 就是通过启动和关停 Tomcat。在前面配置示例中,Server 的配置如下所示:
<Server port="8005" shutdown="SHUTDOWN">
其中,属性 shutdown 指定关闭 Server 的指令。属性 port 指定 Server 接收 shutdown 指令的端口号,设置为“-1”可以禁掉该端口。
1.2.顶层类组件 Service
Service 主要职责就是将 Engine 与 Connector 装配在一起对外提供服务,一个 Service 可以包含多个 Connector,但只能包含一个 Engine,其中 Connector 负责从客户端接收请求,Engine 负责处理 Connector 接收进来的请求。如前面配置示例中,Service 的配置如下所示:
<Service name="Catalina">
我们可以通过属性 name 为 Service 指定名称,不同的 Service 负责监管其下属 Connector 所绑定的端口。
1.3. 连接器组件 Connector
Tomcat 的工作模式可以分为下面两类:
- 作为 Web 服务器:请求是直接来自于客户端 HTTP 请求(或浏览器)。
- 作为 Java Web 应用服务器:请求来自于前置 Web 服务器,通常包括:Apache、IIS、Nginx 等。Tomcat 主要优势是作为 JSP/Servlet 容器,在处理静态资源方面效率偏低。因此,它通常要跟 Apache、IIS、Nginx 等 Web 服务器集成使用。AJP 协议主要负责 Tomcat 和集成 Web 服务器的交互连接。
p2
每个 Service 可以有一个或多个 Connector,不同工作模式下,Tomcat 需要为各种类型的请求分别定义相应的 Connector,这样才能正确接收客户端对应协议的请求。定义 Connector 可以使用多种属性,某些属性只适用于某种特定的 Connector 类型。一般说来,常见的 Connector 有 4 种类型:HTTP、SSL、AJP、Proxy。
p3
作为通信接口,Connector 为其所属特定的 Service 接收外部客户端请求,以及回送应答至外部客户端。具体职责包括创建 Request、Response 对象用于跟外部客户端交换数据,并将 Request 交给配套的 Engine 来处理。通过修改 Connector 的属性取值,我们可以控制 Service 所监听的网络协议及端口号,具体示例如下:
<Connector port="8080" protocol="HTTP/1.1" maxThreads=”150″
connectionTimeout="20000"
redirectPort="8443" />
<Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />
<Connector port=”8443″ maxThreads=”150″ minSpareThreads=”25″
maxSpareThreads=”75″ enableLookups=”false” acceptCount=”100″
debug=”0″ scheme=”https” secure=”true”
clientAuth=”false” sslProtocol=”TLS” />
- 配置一,客户端可以通过 8080 端口号使用 HTTP 协议访问 Tomcat。
- 配置二,客户端可以通过 8009 端口使用 AJP 协议访问 Tomcat。AJP 协议主要用于跟其他的 HTTP 服务器连接协作。当 Tomcat 与其他 HTTP 服务器集成时,我们就要用到这个连接器。
- 配置三,客户端可以通过 8443 端口号使用 HTTPS 协议访问 Tomcat。
连接器的定义可以配置的属性非常多,下面是常用属性的说明。
- address:指定连接器监听的地址,默认为所有地址,即 0.0.0.0。
- maxThreads:支持最大的并发连接数,默认为 200。
- port:监听端口,默认为 0。
- protocol:连接器使用的协议,默认为:HTTP/1.1,定义 AJP 协议时通常为 AJP/1.3。
- redirectPort:在强制要求 HTTPS 的情况下,如果请求时 HTTP,则将会被重定向至 8443 端口。
- connectionTimeout:连接的超时时间,单位为毫秒,默认为 60000,即 1 分钟。
- enableLookups:是否通过 request.getRemoteHost() 进行 DNS 查询以获取客户端的主机名。
- acceptCount:设置等待队列的最大长度。通常,在 Tomcat 所有处理线程均处于繁忙状态时,新请求将被放置于等待队列中。
2.1.4 容器类组件 Engine
Engine 内部可以包含多个 Host,它是 Service 组件中负责请求处理的组件。它从一个或多个 Connector 中接收请求并处理,并将处理结果封装成应答交给 Connector,最终回传给外部客户端。在前文配置文件示例中,Engine 的配置如下所示:
<Engine name="Catalina" defaultHost="localhost">
其中,属性 name 用于日志和错误信息,其取值在整个 Server 中保证唯一。属性 defaultHost 指定了默认的Host 名称,当 HTTP 请求所指定的 Host 名称不存在时,一律使用 defaultHost 指定的 Host 来处理。因此,defaultHost 的值,必须与 Engine 中的某个 Host 组件的属性 name 取值匹配。
2.1.5 容器类组件 Host
Host 代表一个虚拟主机,它对应计算机网络上的一个实体,即某个在 DNS 服务器上注册过的域名或者 IP 地址,例如:www.abc.com 或 201.187.10.21。Host 内部可以包含多个 Context,每个 Context 表示一个 Web 应用。Host 负责安装、展开、启动和结束每个 Web 应用。
客户端在填写收件人地址时会通过主机名来标识它希望访问的服务器,Tomcat 将从 HTTP 请求头的 Host 字段提取主机名,然后再匹配对应的虚拟主机。如果没有找到匹配的,HTTP 请求将被发送至默认主机 defaultHost。因此,默认主机不需要是在 DNS 服务器上注册的网络名,例如:localhost。在前面配置示例中,Host 的配置如下所示:
<Host name="localhost" appBase="webapps" unpackWARs="true" autoDeploy="true">
其中,属性 name 指定虚拟主机的名称。属性 appBase 指定 Web 应用所在的目录,默认值是 webapps,这是一个相对路径,标识 Tomcat 安装根目录下的 webapps 文件夹。属性 unpackWARs 指定是否将 Web 应用的 WAR 文件解压。如果取值为 true,Tomcat 将以解压后的文件结构运行该 Web 应用;如果为 false,Tomcat 将直接使用 WAR 文件运行 Web 应用。属性 autoDeploy 指定是否自动部署 Web 应用。
2.1.6 容器类组件 Context
Context 代表在特定虚拟主机上运行的一个 Web 应用,负责处理某个特定 Web 应用的所有请求。每个 Web 应用要么基于 WAR 文件,要么基于 WAR 文件解压后对应的文件目录。在前文配置文件示例中,我们没有看到 Context 的配置,这是因为 Host 开启了自动部署,Web 应用没有在配置文件中配置静态部署,而是由 Tomcat 通过特定的规则自动部署,Context 组件也将被自动创建。Context 通过属性 path 来唯一标识自身。考虑到 Web 应用自动部署与本文主题关系不大,老兵哥我就不再展开,如果你对此内容感兴趣,可以找资料做扩展阅读。
2.1.7 内嵌类元素
除了前面介绍的核心组件外,Tomcat 还提供了 Listener、GlobalNamingResources、Realm、Valve 等组件,这些组件都是嵌入到核心组件当中来使用,我们将它们归为内嵌组件,考虑到不涉及主题就不再赘述。
2.2 Tomcat 处理 HTTP 请求的流程
在前面介绍的各种核心组件基础上,我们一起来看看,当 HTTP 请求被投递到 Tomcat 所在主机之后,Tomcat 是如何将 HTTP 请求派发给特定的 Web 应用来处理的:
- 根据协议类型和端口号选定 Service 和 Engine:Service 下属的 Connector 组件负责监听接收特定协议和特定端口的请求。因此,当 Tomcat 启动时,Service 组件就开始监听特定的端口,如前文配置文件示例,Catalina 这个 Service 监听了 HTTP 协议 8080 端口和 AJP 协议的 8009 端口。当 HTTP 请求抵达主机网卡的特定端口之后,Tomcat 就会根据协议类型和端口号选定处理请求的 Service,随即 Engine 也就确定了。通过在 Server 中配置多个 Service,可以实现通过不同端口访问同一主机上的不同应用。
- 根据域名或 IP 地址选定 Host:待 Service 被选定之后,Tomcat 将在 Service 中寻找与 HTTP 请求头中指定的域名或 IP 地址匹配的 Host 来处理该请求。如果没有匹配成功,则采用 Engine 中配置的默认虚拟主机 defaultHost 来处理该请求。
- 根据 URI 选定 Context:URI 中的 context-path 指定了 HTTP 请求将要访问的 Web 应用。当请求抵达时,Tomcat 将根据 Context 的属性 path 取值与 URI 中的 context-path 的匹配程度来选择 Web 应用处理相应请求,例如:Web 应用 spring-demo 的 path 属性是”/spring-demo”,那么请求“/spring-demo/user/register”将交由 spring-demo 来处理。
p4
我们还是用向下文这个地址发送 HTTP 请求为例,将整个处理流程串一遍:
http://201.187.10.21:8080/spring-demo/user/register
- 客户端(或浏览器)发送请求至主机(201.187.10.21)的端口 8080,被在该端口上监听的 Coyote HTTP/1.1 Connector 所接收。
- Connector 将该请求交给它所在 Service 的 Engine 来负责处理,并等待 Engine 的回应。
- Engine 获得请求之后从报文头中提取主机名称(201.187.10.21),在所有虚拟主机 Host 当中寻找匹配。
- 在未匹配到同名虚拟主机的情况下,Engine 将该请求交给名为 localhost 的默认虚拟主机 Host 处理。
- Host 获得请求之后将根据 URI(/spring-demo/user/register)中的 context-path 的取值“/spring-demo” 去匹配它所拥有的所有 Context,将请求交给代表应用 spring-demo 的 Context 来处理。
- Context 构建 HttpServletRequest、HttpServletResponse 对象,将其作为参数调用应用 spring-demo,由应用完成业务逻辑执行、结果数据存储等过程,等待应答数据。
- Context 接收到应用返回的 HttpServletResponse 对象之后将其返回给 Host。
- Host 将 HttpServletResponse 对象返回给 Engine。
- Engine 将 HttpServletResponse 对象返回 Connector。
- Connector 将 HttpServletResponse 对象返回给客户端(或浏览器)。