本文基于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 映射到相应的ServletMapper维护了所有已部署 Web 应用的 URL 模式与Servlet之间的映射关系,通过对比请求 URL 和映射表,找到能处理该请求的目标Servlet

4.请求处理

  • Tomcat 创建org.apache.catalina.connector.Requestorg.apache.catalina.connector.Response对象,这两个对象将用于和Servlet进行交互。这两个对象对org.apache.coyote.Requestorg.apache.coyote.Response进行了进一步封装,提供了更多面向Servlet编程的方法和属性 。

  • 请求会依次经过Engine(引擎)、Host(虚拟主机)、Context(上下文)等容器组件的处理。这些容器组件会根据Mapper组件的映射结果找到对应的Servlet,并调用其service()方法 。

  • 在调用Servletservice()方法之前,请求可能会经过一系列过滤器(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 为例:

  1. Acceptor 线程监听连接:通过 ServerSocketChannel 监听端口,当客户端连接到达时,返回 SocketChannel 对象。

  2. 连接配置:对 SocketChannel 进行非阻塞模式设置、超时配置等,确保连接可用性。

  3. 连接移交:通过 processSocket() 方法将连接交给后续组件处理。

Socket 处理与线程分配

  1. Socket 封装SocketChannel 被封装为 SocketWrapper,便于统一管理。

  2. 线程池调度:通过 Executor 线程池分配工作线程,执行 SocketProcessor 任务。

  3. Processor 加载:根据协议类型(如 HTTP/1.1)获取对应处理器(如 Http11Processor),处理请求数据。

请求解析与封装

Http11Processor 负责解析 HTTP 请求并封装对象:

  1. 数据解析:从 SocketWrapper 读取数据,解析请求行(Method、URL、Protocol)、请求头和请求体。

  2. 对象封装:将解析结果封装为 org.apache.coyote.Requestorg.apache.coyote.Response 对象。

  3. 请求移交:通过 CoyoteAdapter 将封装后的请求传递给容器层(Container)处理。

请求传递与容器匹配

CoyoteAdapter是连接ConnectorContainer的桥梁,它会将org.apache.coyote.Requestorg.apache.coyote.Response转换为org.apache.catalina.connector.Requestorg.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 匹配的ContextWrapper,并将相关信息存储在request对象的mappingData属性中 。然后,通过container.getPipeline().getFirst().invoke(request, response)方法将请求交给EnginePipeline进行处理 。

容器处理请求

Tomcat 的Container采用分层结构,包括Engine(引擎)、Host(虚拟主机)、Context(上下文)和Wrapper(包装器),它们按照一定的顺序处理请求 。每个Container都有一个PipelinePipeline由多个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);
}

StandardEngineValveinvoke方法中,首先会获取与请求相关的Host,然后将请求交给HostPipeline进行处理 。HostPipeline会依次调用其Valve,最终将请求传递给ContextContext再将请求传递给Wrapper

Servlet 处理请求

当请求到达Wrapper时,StandardWrapperValve会负责创建Servlet实例(如果尚未创建),构建过滤器链,并调用Servletservice方法处理请求 。

// 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方法,最后调用Servletservice方法 。处理完成后,会调用wrapper.deallocate(servlet)释放Servlet实例 。

总结

通过对 Tomcat 处理 HTTP 请求流程的源码分析,我们清晰地了解到从端口监听、请求接收,到请求解析、分发以及最终响应返回的全过程。这一复杂而有序的处理机制,充分展现了 Tomcat 作为一款成熟的 Java Web 服务器,在高效处理大量并发请求方面的卓越能力。希望读者在理解这一流程后,能进一步探索 Tomcat 的更多特性和优化策略,为开发高性能的 Web 应用奠定坚实基础。

文章作者: Z
本文链接:
版权声明: 本站所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 微博客
性能调优
喜欢就支持一下吧