Tomcat中HTTP请求流程的源码分析
本文基于Tomcat9.0.55源码分析,源码环境搭建教程。
HTTP 请求处理总览
当客户端(比如浏览器)向运行在 Tomcat 上的 Web 应用程序发起 HTTP 请求时,Tomcat 会按照一套既定的流程来处理这个请求,整个过程涉及多个组件的协同工作,以确保请求被正确解析、分发和处理,并最终生成响应返回给客户端。以下是这个过程的大致步骤:
1.请求到达与接收:客户端通过网络发送 HTTP 请求,Tomcat 服务器上配置的Connector
组件会监听特定端口(如默认的 8080 端口),一旦监听到请求,Connector
就会接收该请求的 TCP 连接,并读取 HTTP 请求的内容 。
2.请求解析与封装:Connector
接收到原始请求数据后,会将其交给对应的协议处理器(如Http11Processor
处理 HTTP/1.1 协议请求 ),协议处理器负责解析 HTTP 请求头和消息体,将请求信息封装成org.apache.coyote.Request
对象,这个对象包含了请求的所有信息,如请求方法(GET、POST 等)、请求头、请求参数等 。
3.请求映射:Tomcat 使用Mapper
组件将请求的 URL 映射到相应的Servlet
。Mapper
维护了所有已部署 Web 应用的 URL 模式与Servlet
之间的映射关系,通过对比请求 URL 和映射表,找到能处理该请求的目标Servlet
。
4.请求处理:
Tomcat 创建
org.apache.catalina.connector.Request
和org.apache.catalina.connector.Response
对象,这两个对象将用于和Servlet
进行交互。这两个对象对org.apache.coyote.Request
和org.apache.coyote.Response
进行了进一步封装,提供了更多面向Servlet
编程的方法和属性 。请求会依次经过
Engine
(引擎)、Host
(虚拟主机)、Context
(上下文)等容器组件的处理。这些容器组件会根据Mapper
组件的映射结果找到对应的Servlet
,并调用其service()
方法 。在调用
Servlet
的service()
方法之前,请求可能会经过一系列过滤器(Filter
)的处理。过滤器可以对请求进行预处理(如身份验证、日志记录、参数修改等)或后处理 。Servlet
接收到请求后,执行实际的业务逻辑操作,例如进行数据库查询、数据处理等,处理完成后,Servlet
生成响应内容,并通过HttpServletResponse
对象返回给 Tomcat 容器 。
5.响应构建与返回:
Tomcat 容器根据
Servlet
生成的响应内容,设置响应头信息,包括状态码(如 200 表示成功、404 表示未找到等)、MIME 类型(用于指示响应内容的类型,如 text/html 表示 HTML 页面 )等。将响应内容写入
HttpServletResponse
对象的响应体中。Connector
组件将HttpServletResponse
对象封装成 HTTP 响应,并通过网络发送回客户端 。
6.日志记录与资源清理:
Tomcat 在处理请求的过程中,会记录访问日志和错误日志。访问日志记录了每次请求的基本信息,如请求时间、客户端 IP、请求 URL 等;错误日志则捕获异常并记录堆栈跟踪信息,方便开发人员排查问题 。
一旦响应发送完毕,Tomcat 将清理相关的资源,如关闭 I/O 流、释放线程等,以便为下一次请求做好准备 。
处理 HTTP 请求的流程
处理 HTTP 请求的流程
接下来我们深入到 Tomcat 的源码层面,详细分析上述处理过程的具体实现。在 Tomcat 中,处理 HTTP 请求的核心流程涉及多个关键组件和类,它们协同工作,确保请求能够被高效、准确地处理。
端口监听与请求接收
Tomcat 的 Connector 组件负责监听端口并接收 HTTP 请求。以高性能的 NioEndpoint 为例:
Acceptor 线程监听连接:通过
ServerSocketChannel
监听端口,当客户端连接到达时,返回SocketChannel
对象。连接配置:对
SocketChannel
进行非阻塞模式设置、超时配置等,确保连接可用性。连接移交:通过
processSocket()
方法将连接交给后续组件处理。
Socket 处理与线程分配
Socket 封装:
SocketChannel
被封装为SocketWrapper
,便于统一管理。线程池调度:通过
Executor
线程池分配工作线程,执行SocketProcessor
任务。Processor 加载:根据协议类型(如 HTTP/1.1)获取对应处理器(如
Http11Processor
),处理请求数据。
请求解析与封装
Http11Processor 负责解析 HTTP 请求并封装对象:
数据解析:从
SocketWrapper
读取数据,解析请求行(Method、URL、Protocol)、请求头和请求体。对象封装:将解析结果封装为
org.apache.coyote.Request
和org.apache.coyote.Response
对象。请求移交:通过
CoyoteAdapter
将封装后的请求传递给容器层(Container)处理。
请求传递与容器匹配
CoyoteAdapter
是连接Connector
和Container
的桥梁,它会将org.apache.coyote.Request
和org.apache.coyote.Response
转换为org.apache.catalina.connector.Request
和org.apache.catalina.connector.Response
,并将请求交给Container
处理 。在这个过程中,会使用Mapper
组件来匹配请求的 URL 到对应的Servlet
。
public void service(org.apache.coyote.Request req, org.apache.coyote.Response res)
throws Exception {
// 构造tomcat的request对象转servlet的request对象
...
// 内部会调用下面的方法,匹配到当前url对应的host、context、wrapper
// connector.getService().getMapper().map(serverName, decodedURI, version, request.getMappingData());
postParseSuccess = postParseRequest(req, request, res, response);
if (postParseSuccess) {
// 调用service内的容器,即StandardEngine对象,获取pipeline,执行Engine内的所有valve
connector.getService().getContainer().getPipeline().getFirst().invoke(
request, response);
}
}
在postParseRequest
方法中,会使用Mapper
来查找与请求 URL 匹配的Context
和Wrapper
,并将相关信息存储在request
对象的mappingData
属性中 。然后,通过container.getPipeline().getFirst().invoke(request, response)
方法将请求交给Engine
的Pipeline
进行处理 。
容器处理请求
Tomcat 的Container
采用分层结构,包括Engine
(引擎)、Host
(虚拟主机)、Context
(上下文)和Wrapper
(包装器),它们按照一定的顺序处理请求 。每个Container
都有一个Pipeline
,Pipeline
由多个Valve
组成,请求会依次经过这些Valve
的处理 。
getPipeline().getFirst()
获取的是第一个valve,类似StandardEngineValve这种是basic,最后一个valve。最后一个valve会调用下一个容器的pipeline。而前面的valve会调用下一个valve。
// StandardEngineValve类中的invoke方法
public final void invoke(Request request, Response response)
throws IOException, ServletException {
// 获取到对应host,在CoyoteAdapter中已经解析到mapper对象中
Host host = request.getHost();
// 获取host容器中的pipeline,执行所有的valve
host.getPipeline().getFirst().invoke(request, response);
}
在StandardEngineValve
的invoke
方法中,首先会获取与请求相关的Host
,然后将请求交给Host
的Pipeline
进行处理 。Host
的Pipeline
会依次调用其Valve
,最终将请求传递给Context
,Context
再将请求传递给Wrapper
。
Servlet 处理请求
当请求到达Wrapper
时,StandardWrapperValve
会负责创建Servlet
实例(如果尚未创建),构建过滤器链,并调用Servlet
的service
方法处理请求 。
// StandardWrapperValve类中的invoke方法
public final void invoke(Request request, Response response)
throws IOException, ServletException {
// 获取到wrapper
StandardWrapper wrapper = (StandardWrapper) getContainer();
// 获取servlet实例
if (!unavailable) {
servlet = wrapper.allocate();
}
// 创建过滤器链
ApplicationFilterChain filterChain =
ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);
// 执行过滤器链,内部会执行所有过滤器链和servlet的service方法
filterChain.doFilter(request.getRequest(), response.getResponse());
}
在invoke
方法中,wrapper.allocate()
会获取一个Servlet
实例 。然后,通过ApplicationFilterFactory.createFilterChain(request, wrapper, servlet)
创建一个过滤器链,将请求和响应对象传递给过滤器链的doFilter
方法,过滤器链会依次调用各个过滤器的doFilter
方法,最后调用Servlet
的service
方法 。处理完成后,会调用wrapper.deallocate(servlet)
释放Servlet
实例 。
总结
通过对 Tomcat 处理 HTTP 请求流程的源码分析,我们清晰地了解到从端口监听、请求接收,到请求解析、分发以及最终响应返回的全过程。这一复杂而有序的处理机制,充分展现了 Tomcat 作为一款成熟的 Java Web 服务器,在高效处理大量并发请求方面的卓越能力。希望读者在理解这一流程后,能进一步探索 Tomcat 的更多特性和优化策略,为开发高性能的 Web 应用奠定坚实基础。