Apache Tomcat 请求处理

通过之前的 Apache Tomcat 可以把得到如下的 Tomcat 的架构图:
在这里插入图片描述

1、请求处理核心组件

以下是 Tomcat 处理请求时涉及到的核心组件:

组件名称 说明
EndPoint Endpoint 可以理解成通信端点,用于 Socket 通信处理 TCP/IP 协议。监听 Socket 暴露的端口,当有请求进来的时候 EndPoint 进行处理。支持 BIO、NIO、NIO2 及 APR 网络 IO。
Processor 处理器组件,解析处理 HTTP 报文。当 EndPoint 监听到网络请求时,把网络请求交给 Processor 解析 HTTP 协议。把 HTTP 请求解析成 Request 与 Response 对象
ProtocolHandler 这个组件持有 EndPoint 组件与 Processor 组件
Adapter 适配器组件,把 Processor 解析后的 Request 对象适配成 Servlet 容器中需要使用的 ServletRequest 以及把 Servlet 容器中的 ServletResponse 适配成响应的 Response 对象
Engine Engine 表示整个 Servlet 引擎。在 Tomcat 中,Engine 为最高层级的容器对象。尽管 Engine 不是直接处理请求的容器,却是获取目标容器的入口
Host Host 作为一类容器,表示 Servlet 引擎(即 Engine) 中的虚拟机,与一个服务器的网络名有关,如域名等。客户端可以使用这个网络名连接服务器,这个名称必须要在 DNS 服务器上注册
Context Context 作为一类容器,用于表示 ServletContext,在 Servlet 规范中,一个 ServletContext 即表示一个独立的 Web 应用
Wrapper Wrapper 作为一类容器,用于表示 Web 应用中定义的 Servlet
Mapper 维护 Host、Context 和 Wrapper 容器的映射,同时通过 MapperListener 监听器监听所有的 Host、Context、 Wrapper 组件,在相关组件启动、停止或者移除相关映射

2、定义一个 Servlet

为了进行代码 debug 方便,我们需要创建一个简单的 Web 项目,并且定义一个 Servlet。并且满足 Tomcat Web 规范。下面就是项目的结构图:

在这里插入图片描述
在 webapps 下面定义一个我们的 Web 项目,项目名称叫 web-demo,并且按照 Tomcat 的 web 规范定义一个 WEB-INF,最主要包含 2 个东西:

  • classes : 定义业务处理的 Servlet
  • web.xml :把 Servlet 规范信息,包含 Listener、Filter、Servlet 等声明

定义一个简单的 Servlet 声明类:

HelloWorldServlet

public class HelloWorldServlet extends HttpServlet {

	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		System.out.println("invoke HelloWorldServlet.doGet success");
	}

	@Override
	protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		System.out.println("invoke HelloWorldServlet.doPost success");
	}
}

把这个 Servlet 声明到 web.xml 文件里面:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
                      http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
         version="3.1"
         metadata-complete="true">

    <description>
        Servlet and JSP Examples.
    </description>
    <display-name>Servlet and JSP Examples</display-name>

    <servlet>
        <servlet-name>HelloWorldServlet</servlet-name>
        <servlet-class>cn.carlzone.tomcat.servlet.HelloWorldServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>HelloWorldServlet</servlet-name>
        <url-pattern>/servlets/hello</url-pattern>
    </servlet-mapping>
</web-app>

下面就开启 Apache Tomcat 请求处理,源码分析。

3、Servlet 请求处理

要明白 Tomcat 请求处理是如何处理的,我们首先需要想明白一个问题。

当一个 Servlet 请求到来的时候,Tomcat 是通过怎样的机制锁定到 Servlet 并且执行的。

当我们需要访问上面定义的 HelloWorldServlet 的时候,是不是需要在浏览器里面输入 http://localhost:8080/web-demo/servlets/hello。根据之前对 Tomcat 里面的容器组件分析我们可以得到下面架构图:

在这里插入图片描述
当访问地址:http://localhost:8080/web-demo/servlets/hello 的时候, Connector 组件会接收并按照协议解析 http 请求,然后把解析好的请求发送给 Tomcat 里面的容器组件 (Container)。通过责任链的形式交给 Engine、Host、Context 、Wrapper 来处理。在之前的文章中我们说到 Tomcat 是通过 Mapper 和 MapperListener 来维护请求映射的。在之前的启动图里面没有 Mapper 和 MapperListener 的时序图。在这里我们就来分析一下 Tomcat 是如何构建请求路径映射的。

4、Mapper 和 MapperListener

在之前的 Tomcat 的架构演进之中分析了 Tomcat 是通过 Service 这个组件来管理请求与 Http 请求的。Service 持有 Mapper 和 MappingListener 这两个组件并且利用它们来做请求映射。下面我们就来分析一下 Mapper 和 MapperListener 这两个组件。首先来看一下 Mapper,下面就是它的类图:

在这里插入图片描述
它们的关系是:

  • 一个 Mapper 对象里面持有多个 MappedHost
  • 一个 MappedHost 里面持有 多个MappedContext
  • 一个 MapperContext 里面持有多个 MappedWrapper
  • MappedXxx 对象都继承自 MapElement 对象
  • MapMapElement 对象包含了名称和和对象两个属性。名称的作用就是 http 请求路径的 key,Object 就是对应的处理容器。比如: http://localhost:8080/web-demo/hello,MappedHost 对应的key 就是 localhost,对应的处理器就是StandardHost;MappedContext 对应的 key 就是 web-demo ,对应的处理容器就是 StandardContext;MappedWrapper 对应的 key 就是 hello,对应的处理容器就是 StandardWrapper。

那么这个映射关系是什么时候构建的呢?在 Service 刚刚创建的时候只是通过 new 关键字来创建这个对象。其实是通过 MapperListener 来构建 Mapper 对象里面的映射关系的。MapperListener 其实也是实现了生命周期管理组件 LifecycleListener 。在 Tomcat 启动时,它随着 Service 的生命周期而被调用。

StandardService#startInternal

    protected void startInternal() throws LifecycleException {

        if(log.isInfoEnabled())
            log.info(sm.getString("standardService.start.name", this.name));
        setState(LifecycleState.STARTING);

        // Start our defined Container first
        if (engine != null) {
            synchronized (engine) {
                engine.start();
            }
        }

        synchronized (executors) {
            for (Executor executor: executors) {
                executor.start();
            }
        }

        mapperListener.start();

        // Start our defined Connectors second
        synchronized (connectorsLock) {
            for (Connector connector: connectors) {
                try {
                    // If it has already failed, don't try and start it
                    if (connector.getState() != LifecycleState.FAILED) {
                        connector.start();
                    }
                } catch (Exception e) {
                    log.error(sm.getString(
                            "standardService.connector.startFailed",
                            connector), e);
                }
            }
        }
    }

它的时机是在 service 服务调用 start 方法,也就是当前服务的容器都初始化完成之后,Connector 服务调用 start 方法之前。在这个时候,当前的容器父子级关系已经解析完成了。

  • 当前服务包含哪些虚拟主机(一对多)
  • 当前虚拟主机下包含哪些 Context(一对多)一个 tomcat 下可以发布多个 War 包。
  • 当前 Context 下包含哪些 Wrapper(一对多)也就是 web.xml 里面定义的 Servlet。

随着 Tomcat 的生命周期构建起 http 请求与 Servlet 请求的映射关系:
在这里插入图片描述

5、CoyoteAdapter

org.apache.catalina.connector.CoyoteAdapter 的作用是把 Connector 与 Mapper、Containner 联系起来。当 Connector 接收到请求后,首先读取数据,然后调用 CoyoteAdapter#service 方法完成请求处理。它的主要处理过程包含以下几个步骤:

  • 根据 Connector 的请求(org.apache.coyote.Request) 和响应(org.apache.coyote.Response)对象创建 Servlet 请求(org.apache.catalina.connector.Request) 和响应(org.apache.catalina.connector.Response)
  • 转换请求参数并通过 MappingData 与请求映射 URI 定位到具体处理的 Wrapper
  • 得到当前 Engine 的第一个 Valve 并执行(invoke),完成客户端请求处理

请求与映射关系的查询时序图如下:

在这里插入图片描述

6、Pipeline 与 Valve

Tomcat 为了方便扩展采用了 Pipeline 与 Valve 构建成责任链来调用。

在这里插入图片描述
它的创建时机是在每个容器的初始化的时候:

StandardEngine

    public StandardEngine() {

        super();
        pipeline.setBasic(new StandardEngineValve());
        /* Set the jmvRoute using the system property jvmRoute */
        try {
            setJvmRoute(System.getProperty("jvmRoute"));
        } catch(Exception ex) {
            log.warn(sm.getString("standardEngine.jvmRouteFail"));
        }
        // By default, the engine will hold the reloading thread
        backgroundProcessorDelay = 10;

    }

StandardHost

    public StandardHost() {

        super();
        pipeline.setBasic(new StandardHostValve());

    }

StandardContext

    public StandardContext() {

        super();
        pipeline.setBasic(new StandardContextValve());
        broadcaster = new NotificationBroadcasterSupport();
        // Set defaults
        if (!Globals.STRICT_SERVLET_COMPLIANCE) {
            // Strict servlet compliance requires all extension mapped servlets
            // to be checked against welcome files
            resourceOnlyServlets.add("jsp");
        }
    }

StandardWrapper

    public StandardWrapper() {

        super();
        swValve=new StandardWrapperValve();
        pipeline.setBasic(swValve);
        broadcaster = new NotificationBroadcasterSupport();

    }

容器在初始化的时候设置当前容器的 Pipeline 的 Basic 属性为当前容器对应的 Valve,然后在容器中以下面方法进行调用

container.getPipeline().getFirst().invoke(request, response)
其实就是调用初始化方法中设置的在当前容器 Pipeline 中对应的 Valve。

    public Valve getFirst() {
        if (first != null) {
            return first;
        }

        return basic;
    }

好了,到此整个 Tomcat 的请求处理都分析完了。

发布了195 篇原创文章 · 获赞 248 · 访问量 74万+

猜你喜欢

转载自blog.csdn.net/u012410733/article/details/105622322