Tomcat 源码分析(二)-请求分析(4)

本文深入剖析了Tomcat7的阀机制,详细介绍了管道和阀的初始化、数据流转过程及其实现原理。从Connector到Wrapper,层层递进,揭示了请求如何在各组件间传递。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Tomcat 源码分析(二)-请求分析(4)

四、Tomcat 7 阀机制原理

这里的阀机制,就是数据(request)在Tomcat组件之间传递使用的东东。【类似阀门一样的通道的东西】

先看下这个图,Tomcat内的组件数据流转的图↓↓↓↓↓

img

这个图基本上已经画出了整个的过程,在 Connector 接收到一次连接并转化成请求( Request )后,会将请求传递到 Engine 的管道( Pipeline )的阀( ValveA )中。请求在 Engine 的管道中最终会传递到 Engine Valve 这个阀中。接着请求会从 Engine Valve 传递到一个 Host 的管道中,在该管道中最后传递到 Host Valve 这个阀里。接着从 Host Valve 传递到一个 Context 的管道中,在该管道中最后传递到 Context Valve 中。接下来请求会传递到 Wrapper C 内的管道所包含的阀 Wrapper Valve 中,在这里会经过一个过滤器链( Filter Chain ),最终送到一个 Servlet 中。

这里来分析一下,这里面的管道( Pipeline )和阀( Valve )的操作【流转看上面图的箭头就可以了】

管道和阀 初始化和初次调用

这里管道来流转数据,出现的地方是在:承接上个文章中分析到的CoyoteAdapter类的service 方法中:【44行】

connector.getService().getContainer().getPipeline().getFirst().invoke(request, response);

这里就开始分析一下这一行的代码:

**connector.getService():**获取当前的 connector 关联的 Service 组件

这里默认的对象是:org.apache.catalina.core.StandardService的对象。

**getContainer():**获得的就是里面Engine容器的东东了

这里返回的是:org.apache.catalina.core.StandardEngine的对象。

这是在Server组件初始化的时候创建的:

digester.addRuleSet(new EngineRuleSet("Server/Service/"));

在 EngineRuleSet 类的 addRuleInstances 方法中的这一段代码:

public void addRuleInstances(Digester digester) {

digester.addObjectCreate(prefix + "Engine",
                       "org.apache.catalina.core.StandardEngine",
                       "className");
digester.addSetProperties(prefix + "Engine");
digester.addRule(prefix + "Engine",
               new LifecycleListenerRule
               ("org.apache.catalina.startup.EngineConfig",
                "engineConfigClass"));
digester.addSetNext(prefix + "Engine",
                  "setContainer",   //已Container方法设置了StandardEngine对象
                  "org.apache.catalina.Container");

所以这里获取的是:org.apache.catalina.core.StandardEngine对象。

getPipeline(): StandardEngine 类的pipeline 变量

这里就是StandardEngine对象的getPipeline()方法,Pipeline 成员变量。

变量定义在其父类:ContainerBase 类中被定义:

protected Pipeline pipeline = new StandardPipeline(this);

所以 ,这里返回的是StandardPipeline类的对象。

这里返回的StandardPipeline就是管道了,之后会详细分析一下的。

**getFirst().invoke(request, response):**这个就是调用管道里面的阀,并执行内部方法流转数据了。

分析管道和阀的概念和实现

**管道Pipeline:**所谓的管道就是实现了org.apache.catalina.Pipeline这个接口,在上面的new StandardPipeline(this);这个代码的时候创建的。

这里的Pipeline接口中,最重要的就是

basic:基础阀(通过 getBasic、setBasic 方法调用)

first:第一个阀

vaules:普通阀(通过 addValve、removeValve 调用)

这里需要细说一下addValve方法

public void addValve(Valve valve) {
// Validate that we can add this Valve
if (valve instanceof Contained)
  ((Contained) valve).setContainer(this.container);
// Start the new component if necessary
。。。。。
//主要的就是下面这个:将此阀添加到与此管道关联的集合中
if (first == null) {
  first = valve;
  valve.setNext(basic);
} else {
  Valve current = first;
  while (current != null) {
      if (current.getNext() == basic) {
          current.setNext(valve);
          valve.setNext(basic);
          break;
      }
      current = current.getNext();
  }
}
container.fireContainerEvent(Container.ADD_VALVE_EVENT, valve);
}

这里的代码,会给管道添加一个普通阀的时候,已【first】和【basic】作为头尾,维护一个链表结构。

**阀Valve:**实现org.apache.catalina.Valve这个接口。

接口中主要的定义,就是setNext、getNext、invoke 这三个方法;

通过setNext设置该阀的下一阀,通过 getNext 返回该阀的下一个阀的引用,invoke 方法则执行该阀内部自定义的请求处理代码。

在invoke 方法中通常都会有这段代码:getNext().invoke(request, response);,这样就会执行所有的阀了。

具体的数据流转

  • 这里从上面分析的getPipeline()返回StandardPipeline类开始;

这个类的初始化时在getContainer()返回的StandardEngine类的初始化方法中定义的:

public class StandardEngine extends ContainerBase implements Engine {

    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;
    }
    。。。。。。

第5行,设置了基础阀。所以

connector.getService().getContainer().getPipeline().getFirst().invoke(request, response)

会执行的是 StandardEngineValve类的invoke方法。

  • StandardEngineValve.invoke()方法:
 final class StandardEngineValve    extends ValveBase {
     @Override
    public final void invoke(Request request, Response response)
        throws IOException, ServletException {

        // Select the Host to be used for this Request
        Host host = request.getHost();
        if (host == null) {
            response.sendError
                (HttpServletResponse.SC_BAD_REQUEST,
                 sm.getString("standardEngine.noHost", 
                              request.getServerName()));
            return;
        }
        if (request.isAsyncSupported()) {
            request.setAsyncSupported(host.getPipeline().isAsyncSupported());
        }

        // Ask this Host to process this request 对就是这里获取下一个的容器来流转数据
        host.getPipeline().getFirst().invoke(request, response);
    }
     。。。。。。

这个从request中获取到Host对象,【默认是:org.apache.catalina.core.StandardHost对象】。然后就是第20行,进行获取管道以及执行阀的**invoke()**方法;

  • StandardHost类的通道的阀的invoke()方法:

StandardHost 的构造方法的实现:

public StandardHost() {
    super();
    pipeline.setBasic(new StandardHostValve());
}

StandardHostValve类的invoke()方法:

/**选择适当的子上下文来处理此请求,
基于指定的请求URI。如果没有匹配的上下文可以
如果找到,则返回相应的HTTP错误。**/
public final void invoke(Request request, Response response)
        throws IOException, ServletException {
        // Select the Context to be used for this Request
        Context context = request.getContext();  //获取到Context 组件对象
       //。。。一些判断。。。。。
        if (asyncAtStart || context.fireRequestInitEvent(request.getRequest())) {
            try {
                if (!asyncAtStart || asyncDispatching) {
                    //下↓↓↓↓↓↓↓↓面这里看看就可以了
                    context.getPipeline().getFirst().invoke(request, response);
                    //↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
                } else {
                    if (!response.isErrorReportRequired()) {
                        throw new IllegalStateException(sm.getString("standardHost.asyncStateError"));
                    }
                }
            } catch (Throwable t) {
             // 。。。。。。。。
                }
            }
           // 。。。。。。。。
            //现在请求/响应对回到容器下
            //控制提升悬架,以便错误处理可以
            //完成和/或容器可以刷新任何剩余数据
    }

这里可以看到Host【StandardHostValve】中流转,接下来会流转到Context,也就是org.apache.catalina.core.StandardContext类中的管道去。

  • StandardContext类的通道的阀的invoke()方法:

管道在StandardContext构建的时候创建:

    public StandardContext() {
        super();
        pipeline.setBasic(new StandardContextValve()); // 就是这个 设置了当前的管道
        broadcaster = new NotificationBroadcasterSupport();
        if (!Globals.STRICT_SERVLET_COMPLIANCE) {
            resourceOnlyServlets.add("jsp");
        }
    }

这里的阀是:StandardContextValve

  public final void invoke(Request request, Response response)
        throws IOException, ServletException {

        MessageBytes requestPathMB = request.getRequestPathMB();
        if ((requestPathMB.startsWithIgnoreCase("/META-INF/", 0))
                || (requestPathMB.equalsIgnoreCase("/META-INF"))
                || (requestPathMB.startsWithIgnoreCase("/WEB-INF/", 0))
                || (requestPathMB.equalsIgnoreCase("/WEB-INF"))) {
            response.sendError(HttpServletResponse.SC_NOT_FOUND);
            return;
        }
        Wrapper wrapper = request.getWrapper();
        if (wrapper == null || wrapper.isUnavailable()) {
            response.sendError(HttpServletResponse.SC_NOT_FOUND);
            return;
        }
        try {
            response.sendAcknowledgement();
        } catch (IOException ioe) {
        }

        if (request.isAsyncSupported()) {
            request.setAsyncSupported(wrapper.getPipeline().isAsyncSupported());
        }
        wrapper.getPipeline().getFirst().invoke(request, response);  //要看的就只是这个
    }

这里,基础阀的invoke方法:是调用wrapper容器的管道进行数据流转。

wrapper 对象默认是org.apache.catalina.core.StandardWrapper类的实例

  • StandardWrapper类的通道的阀的invoke()方法:

设置基础阀:

    public StandardWrapper() {
        super();
        swValve=new StandardWrapperValve();
        pipeline.setBasic(swValve);
        broadcaster = new NotificationBroadcasterSupport();
    }

然后就是StandardWrapperValve这个阀的invoke 方法;

这个代码 戝长。。。。。不去copy了  //╮(╯_╰)╭#\\
    它的方法备注是酱紫的:
    
Invoke the servlet we are managing, respecting the rules regarding
servlet lifecycle and SingleThreadModel support.
调用我们正在管理的servlet,遵守有关
servlet生命周期和singlethreadmodel支持。

意思就是:
在这里最终会调用请求的 url 所匹配的 Servlet 相关过滤器( filter )的 doFilter 方法及该 Servlet 的 service 方法(这段实现都是在过滤器链 ApplicationFilterChain 类的 doFilter 方法中)

  • 数据流转结束

到这里,已经到了Tomcat 处理组件的最里层了。Wrapper 是对一个 Servlet 的包装,所以他的基础阀内部调用的是过滤链doFiter方法 和 Servlet 的 service 方法;

关于数据内部传递小结

请求在service组件,完成请求分析组装成内部对象request和response之后,就根据组件之间的管道和阀进行数据传递和处理链的传递。

request对象在组装的时候已经适配了对应的组件,在《请求与容器中具体组件的匹配》的时候已经说明。组件内部的管道在初始化之后,会设置基础阀–基础阀的invoke方法会调用下一个组件的管道,xxxxxxxx.getPipeline().getFirst().invoke(request, response);进行数据处理的传递。

处理链的顺序是被固定的。

管道的处理会从First阀一直执行到基础阀。

这样子,数据和处理链就会一直传递处理到Wrapper 组件,也就是具体的一个Servlet 。

到这里Socket连接请求在Tomcat容器内的处理、传递就完成了。

之后的的数据和处理就到了 web 应用里面了

参考资料


小杭 2019-04-18

终于看完这一部分了 (:з」∠)
这一部分的,学习了好几个月 ε=(´ο`*)))唉

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小_杭

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值