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 应用奠定坚实基础。