开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 05 天,点击查看活动详情
Tomcat 是一个开源的 Web 服务器和 Servlet 容器。因此,它的核心功能包括两部分:
- 作为 Web 服务器,需要负责网络连接管理。 具体来说就是,处理 Socket 连接,负责接收、发送网络字节流,根据应用层协议解析字节流,将解析的字节流封装称 ServletRequest 交给 Servlet 容器并处理 ServletResponse 通过字节流发送回请求端。
- 作为 Servlet 容器,需要加载和管理 Servlet,处理 ServletRequest 并生成 ServletResponse。
根据以上功能上的划分,Tomcat 定义了两个模块:连接器和容器,分别用来处理网络连接和管理 Servlet。 接下来,我们将一块学习下这两个模块。
01-Web 连接器
通过上面的描述,我们知道连接器主要功能包括:
- 网络通信,TCP / ARP 等;
- 应用层协议解析,HTTP、AJP 等;
- 请求转化,主要是将应用层协议请求包装成 Servlet 可以处理的请求类型,并将 Servlet 产生的响应转化为底层应用协议响应。
针对这三部分功能,Tomcat 中设计了对应的实现:
- 网络通信,由 Endpoint 负责。 Tomcat 中实现的 I/O 模型包括 NIO、NIO.2、ARP。
- 应用层协议解析,由 Processor 负责。 Tomcat 支持的应用层协议包括:HTTP/1.1、AJP、HTTP/2。
- 请求转化,由 Adapter 负责
它们之间的关系为:
- 上行方向,Endpoint 负责从底层网络连接上接收字节流,并交给 Processor 处理,产生 request 对象给 Adapter,再包装成 Servlet 可以处理的 ServletRequest,交给容器处理。
- 下行方向,容器中 Servlet 返回的 ServletResponse 交由 Adapter 处理变成对应的应用层协议 response 对象,然后交给 Processor 处理,变成字节流交给 Endpoint 发送给请求端。
注:Processor 产生的 request 对象,实际上是 org.apache.coyote.Request
。
Adapter 指 org.apache.catalina.connector.CoyoteAdapter
,它的核心方法是 CoyoteAdapter#service(org.apache.coyote.Request req, org.apache.coyote.Response res)
。
在这个方法里,req/res 被转换为 org.apache.catalina.connector.Request
和 org.apache.catalina.connector.Response
,这两个类实现了 Servlet 规范中的 API,是 Servlet 可以处理的类型。
org.apache.coyote.Request
-> org.apache.catalina.connector.Request
的方式是作为后者的一个成员变量(适配器模式)。
在 service 方法里,转换后的 request/response 被交给容器(Engine)的 pipeline 处理,getPipeline().getFirst().invoke(request, response)
。
更进一步地,Tomcat 的设计者将底层网络协议和应用层协议放在一起考虑,设计了 ProtocolHandler,并提供了一个基本的抽象实现 AbstractProtocol。 并针对 HTTP/1.1 和 AJP 协议,NIO / NIO.2 I/O 模型,提供了协议解析实现:
02-Servlet 容器
Tomcat 中的容器(Servlet 容器,以下如果没特殊说明,容器特指 Servlet 容器)是分层次设计的,一共有4类:
- Engine,是最顶层容器,管理多个站点。
- Host,表示一个主机或站点。
- Context,表示一个 Web 应用。
- Wrapper,表示一个 Servlet。
不同容器之间的关系如下所示(图源自《深入拆解 Tomcat & Jetty》* 极客时间):
- 首先,根据协议和端口号确定的是 Engine。 如何确定的?前面我们介绍过连接器,根据协议和端口号,可以确定使用的连接器。每个连接器都关联着一个 Engine 容器。
- 然后,根据域名来确定 Host。
- 之后,根据 URL 中的路径信息,确定请求的是哪个 Web 应用,即确定 Context。
- 然后,根据 URL 中的路径信息,确定要处理该 URL 的 Servlet (Wrapper)是哪个。
以上四类容器都继承了容器基类 org.apache.catalina.core.ContainerBase
,该类中有两个比较关键的字段属性:
HashMap<String, Container> children;
,保存的是下一层次的容器,例如 Engine 中存储的是 Host,Host 中存储的是 Context。Pipeline pipeline = new StandardPipeline(this);
,Valve 组成的 pipeline,有些地方也称之为 “Pipeline-Valve”。 Pipeline 中有basic
和first
两个 Valve 对象,它们与 Pipeline 以及各类 Valve 对象串联起来形成处理 request/response 的流水线,如下图所示(图源自《深入拆解 Tomcat & Jetty》* 极客时间):
Engine 的实现类是 org.apache.catalina.core.StandardEngine
,与它关联的 pipeline 中的 basic 是 org.apache.catalina.core.StandardEngineValve
。
在其 invoke 方法中会调用 host 关联的 pipeline 中的 first 的 invoke 方法:
host.getPipeline().getFirst().invoke(request, response);
Host 的实现类是 org.apache.catalina.core.StandardHost
,与它关联的 pipeline 中的 basic 是 org.apache.catalina.core.StandardHostValve
。
在其 invoke 方法中会调用 context 关联的 pipeline 中的 first 的 invoke 方法:
Context context = request.getContext();
context.getPipeline().getFirst().invoke(request, response);
Context 的实现类是 org.apache.catalina.core.StandardContext
,与它关联的 pipeline 中的 basic 是 org.apache.catalina.core.StandardContextValve
。
在其 invoke 方法中会调用 wrapper 关联的 pipeline 中的 first 的 invoke 方法:
Wrapper wrapper = request.getWrapper();
wrapper.getPipeline().getFirst().invoke(request, response);
Wrapper 的实现类是 org.apache.catalina.core.StandardWrapper
,与它关联的 pipeline 中的 basic 是 org.apache.catalina.core.StandardWrapperValve
。
在其 invoke 方法中会创建 ApplicationFilterChain,并调用其 doFilter 方法(这里最终是将请求交给了 Servlet 处理):
// Create the filter chain for this request
ApplicationFilterChain filterChain =
ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);
// Call the filter chain for this request
filterChain.doFilter(request.getRequest(), response.getResponse());
Pipeline-Valve 与 FilterChain 的区别与相同点?
- 相同点,都实现了责任链模式,前者由 Valve 组成,后者由 Filter 组成。
- 不同点,前者作用于容器级别,后者作用于应用级别;前者是 Tomcat 特有的实现,而 FilterChain 是所有实现了 Servlet 规范的容器都支持的。
03-总结
今天,我介绍了 Apache Tomcat 的总体架构,主要包括两大部分,Web 连接器和 Servlet 容器。 Web 连接器负责网络连接管理及协议解析,并将请求包装成 Servlet 能够处理的格式。 了解了 Servlet 容器的分层设计以及请求、响应的基本处理流程,以及容器处理请求响应时的 Pipeline-Valve 责任链模式。