Tomcat源码:Pipeline与Valve

news2024/12/25 2:09:10

参考资料:

《Tomcat组成与工作原理》

《Tomcat - Container的管道机制:责任链模式》

《Tomcat源码解析系列 Pipeline 与 Valve》

前文:

《Tomcat源码:启动类Bootstrap与Catalina的加载》

《Tomcat源码:容器的生命周期管理与事件监听》

《Tomcat源码:StandardServer与StandardService》

《Tomcat源码:Container接口》

《Tomcat源码:StandardEngine、StandardHost、StandardContext、StandardWrapper》

        写在开头:本文为个人学习笔记,内容比较随意,夹杂个人理解,如有错误,欢迎指正。

前言

        在前文中,我们介绍了tomcat容器部分中的Engine、Host、Context、Wrapper,截止Wrapper中loadOnStartup=1的servelt启动后整个tomcat的启动就算完成了,不过除了容器tomcat还有连接器的部分,即如何将请求发给对应的servlet来进行处理。连接器的内容我们会在后续的文章中进行介绍。

        本文我们来介绍下容器中最后的部分内容,即Pipeline 与 Valve,这两个组件也属于容器,不过他们的作用不是提供servlet服务,而是实现请求在各级容器中的传递,属于容器中的“连接器”。

目录

前言

一、Pipeline 与 Valve的启动

        1、StandardPipeline

       1.1、生命周期方法 

       1.2、Valve管理方法

        2、Valve

二、Pipeline与Valve传递请求

        1、StandardEngineValve

        2、StandardHostValve

        3、StandardContextValve

        4、StandardWrapperValve


一、Pipeline 与 Valve的启动

        1、StandardPipeline

        在抽象类ContainerBase中定义了成员变量Pipeline,其实现类为StandardPipeline

// ContainerBase.java
protected final Pipeline pipeline = new StandardPipeline(this);

         ,由于ContainerBase是我们上文所讲的Engine、Host、Context、Wrapper容器的公共父类,所以这些容器都会有一个成员变量Pipeline。

        1.1、生命周期方法 

        Pipeline同样继承了抽象类LifecycleBase,因此也实现了Lifecycle接口的生命周期的方法

public class StandardPipeline extends LifecycleBase implements Pipeline{
    // ...
}

         其中initInternal为空方法,而startInternal则用于启动另一个组件Valve,通过下面代码中的current = current.getNext();我们可以猜出Valve是类似于链表状的结构,这里的startInternal其实就是依次调用这个链表结构中的每个Valve的start方法。

    protected void initInternal() {
        // NOOP
    }

    protected synchronized void startInternal() throws LifecycleException {

        Valve current = first;
        if (current == null) {
            current = basic;
        }
        while (current != null) {
            if (current instanceof Lifecycle) {
                ((Lifecycle) current).start();
            }
            current = current.getNext();
        }

        setState(LifecycleState.STARTING);
    }

        1.2、Valve管理方法

        在Pipeline中有两个Valve的成员变量first与basic分别表示上面所说的Valve组成的链表结构的头尾节点,其结构如下图。

         链表中每个节点的下一节点由每个Valve节点自己保存,可以通过getNext来获取。Valve的更多相关内容我们会在下文介绍,这里先继续看下Pipeline的另外两个方法。

        首先是setBasic,从Engine到Wrapper的每个容器在构造方法中都会调用改方法,可以看出来这个方法是为了给StandardPipeline中的basic变量赋值,并且每个容器传入的Valve的实现类都不相同,可以从类名看出其具体类别与容器的实现类相关。

        从setBasic的简化内容来看当basic变量为空时直接赋值,如果不为空则操作过程和链表一样先遍历到其前面一个节点,断开连接并将新的basic变量接在最后面。

    // StandardPipeline.java
    protected Valve basic = null;

    public void setBasic(Valve valve) {
        Valve oldBasic = this.basic;
        // ...
        Valve current = first;
        while (current != null) {
            if (current.getNext() == oldBasic) {
                current.setNext(valve);
                break;
            }
            current = current.getNext();
        }
        this.basic = valve;
    }

    // StandardEngine.java
    public StandardEngine() {
        pipeline.setBasic(new StandardEngineValve());
    }

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

    // StandardContext.java
    public StandardContext() {
        pipeline.setBasic(new StandardContextValve());
    }

    // StandardWrapper.java
    public StandardWrapper() {
        swValve = new StandardWrapperValve();
        pipeline.setBasic(swValve);
    }

        然后是addValve方法,从简化的内容来看,除了basic外第一个加入的节点会成为first,第二个加入的会成为second,但basic不会变化,始终都会在最后。

    protected Valve first = null;
    public void addValve(Valve valve) {
        // ...
        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();
            }
        }
    }

 

        2、Valve

        Valve在每个容器中的实现都不相同,对应我们前文中介绍容器的每个类中的实现分别为StandardEngineValve、StandardHostValve、StandardContextValve、StandardWrapperValve。

         这四个实现类都继承于抽象类ValveBase,且均未实现生命周期方法,因此都是直接使用的父类ValveBase中的实现。可以从面的代码中看出initInternal、startInternal并未实现什么具体的操作,backgroundProcess则直接是空方法。这是因为Valve和Pipline一样虽然也属于容器但主要职责却是为连接请求提供转发服务,属于容器中的“连接器”。

    protected void initInternal() throws LifecycleException {
        // 调用父类LifecycleMBeanBase的initInternal方法
        // 内容为注册JMX
        super.initInternal();
        containerLog = getContainer().getLogger();
    }

    protected synchronized void startInternal() throws LifecycleException {
        setState(LifecycleState.STARTING);
    }

    public void backgroundProcess() {
        // NOOP by default
    }

二、Pipeline与Valve传递请求

        后面我们会介绍连接器中的CoyoteAdapter的内容,该类的asyncDispatch方法(即请求分发方法)中有如下内容。

    // CoyoteAdapter#asyncDispatch
    connector.getService().getContainer().
            getPipeline().getFirst().invoke(
                    request, response);


    // ContainerBase.java
    protected final Pipeline pipeline = new StandardPipeline(this);

    public Pipeline getPipeline() {
        return this.pipeline;
    }

    // StandardPipeline.java
    protected Valve first = null;
    public void addValve(Valve valve) {
        // ...
        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();
            }
        }
    }

        getService即获取StandardService,Service的container即Engine容器。getPipeline则是直接复用的抽象父类ContainerBase 中的实现,内容很明确就是获取成员变量pipline。然后是getFirst,结合上文中的描述,这里是获取的Valve链表结构的first节点,如果first节点为空则转而获取basic节点,然后调用其invoke方法。 (getFirst获取的必然是Valve链表的第一个节点,之所以这么说是因为如果链表中没有first那么basic就是第一个)

        下文我们以每个容器中默认的Valve作为切入点介绍下invoke方法,由于该方法中的具体内容需要结合后续的连接器的源码理解,所以暂时只做一些简单的介绍,详细的内容会在后续介绍完连接器后做分析。

        1、StandardEngineValve

        StandardEngine中的实现为StandardEngineValve,该类中的invoke方法首先获取当前请求中的Host对象,如果没有则直接返回。否则将继续如同上文一样调用host中setbasic时创建的Valve的invoke方法。

    // StandardEngine.java
    public StandardEngine() {
        pipeline.setBasic(new StandardEngineValve());
    }

    // StandardEngineValve.java
    public void invoke(Request request, Response response) throws IOException, ServletException {
        // 获取一个 Host 对象,获取不到就直接返回
        Host host = request.getHost();
        if (host == null) {
            if (!response.isError()) {
                response.sendError(404);
            }
            return;
        }
        if (request.isAsyncSupported()) {
            request.setAsyncSupported(host.getPipeline().isAsyncSupported());
        }

        host.getPipeline().getFirst().invoke(request, response);
    }

        2、StandardHostValve

        StandardHostValve中的invoke也和上文类似,获取当前请求中的context对象,如果没有则直接返回。否则将继续如同上文一样调用context中setbasic时创建的Valve的invoke方法。

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

        Context context = request.getContext();
        if (context == null) {
            if (!response.isError()) {
                response.sendError(404);
            }
            return;
        }
        // 其余代码
        context.getPipeline().getFirst().invoke(request, response);
    }

        3、StandardContextValve

        StandardContextValve继续调用下一个子容器wrapper中的invoke方法。

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

        Wrapper wrapper = request.getWrapper();
        if (wrapper == null || wrapper.isUnavailable()) {
            response.sendError(HttpServletResponse.SC_NOT_FOUND);
            return;
        }
        // 其余代码
        wrapper.getPipeline().getFirst().invoke(request, response);
    }

        4、StandardWrapperValve

        StandardWrapperValve是整个调用链的最后一环,在这里会调用wrapper.allocate();来获取一个Servlet实例,并调用ApplicationFilterChain#doFilter 方法来处理请求,由于涉及到连接器的内容,这里我们暂时略过,后续等介绍完了连接器我们再回来做具体分析。

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

        boolean unavailable = false;
        StandardWrapper wrapper = (StandardWrapper) getContainer();
        Servlet servlet = null;
        Context context = (Context) wrapper.getParent();

        if (!context.getState().isAvailable()) {
            response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE,
                    sm.getString("standardContext.isUnavailable"));
            unavailable = true;
        }

        if (!unavailable && wrapper.isUnavailable()) {
            container.getLogger().info(sm.getString("standardWrapper.isUnavailable", wrapper.getName()));
            checkWrapperAvailable(response, wrapper);
            unavailable = true;
        }

        try {
            if (!unavailable) {
                servlet = wrapper.allocate();
            }
        } 
        ApplicationFilterChain filterChain = ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);
        Container container = this.container;
        try {
            if ((servlet != null) && (filterChain != null)) {
                filterChain.doFilter(request.getRequest(), response.getResponse());
            }
        } 
        // 其余代码
    }

        整个流程如下图所示,StandardEngineValve、StandardHostValve、StandardContextValve这三个 Valve 的 invoke 方法的核心逻辑就是调用子容器的 Pipeline 的 Valve 的invoke 方法,也就是 StandardEngineValve#invoke -> StandardHostValve#invoke -> StandardContextValve#invoke -> StandardWrapper#invoke 方法。

        而 StandardWrapper#invoke 最终调用 ApplicationFilterChain#doFilter 方法来处理请求。        

        注意:有些文章里介绍说请求会通过getNext遍历子容器中的VAalve链表,但实际上并没有,这里只会调用getFirst来获取子容器中Valve链表的第一个节点(之所以必然是第一个是因为链表中如果没有first那么basic就是第一个)来触发invoke方法。

                

 

         本文分析了 Pipeline 和 Valve 的相关内容,这两个组件真正起作用的时候是在 Connector 使用容器 Container 处理请求的时候,Connector 会找到自己关联的 Service 的里的 Container 对象(也就是 Engine 对象),然后获取这个对象的 Pipeline,通过这个 Pipeline 对象获取 Pipeline 对象的 Valve 对象,最后通过调用 Valve 对象的 invoke 方法来处理请求。

 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/465232.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Mybatis源码01-Executor

前言 为了方便公司业务排查问题,要求打印执行的sql,以及执行时间。编写了一个Mybatis的拦截器,此前从未看过mybatis的源码,在调试的过程中不断阅读源码,后边想更深刻了解一下,看了鲁班大叔的视频&#xff…

OSCP-Nickel(爆破pdf、本地http提权)

目录 扫描 HTTP 提权 扫描 FileZilla不接受匿名FTP登录。 端口21上的SSH和3389上的RDP很少是初始入口点,但是如果遇到一些凭据,可以记住这一点。 HTTP 打开Web浏览器并导航到端口8089和3333,用于的HTTP服务器。端口8089似乎是某种类型的开发环境。 单击一个按钮重定向到…

boot-admin整合Quartz实现动态管理定时任务

淄博烧烤爆红出了圈,当你坐在八大局的烧烤摊,面前是火炉、烤串、小饼和蘸料,音乐响起,啤酒倒满,烧烤灵魂的party即将开场的时候,你系统中的Scheduler(调试器),也自动根据…

在函数中使用变量

shell脚本编程系列 向函数传递参数 函数可以使用标准的位置变量来表示在命令行中传给函数的任何参数。其中函数名保存在$0变量中,函数参数则依次保存在$1、$2等变量当中,也可以使用特殊变量$#来确定参数的个数 在脚本中调用函数时,必须将参…

day3 TCP/IP协议与五层体系结构

TCP / IP 四层体系结构 TCP / IP工作流程: 现在互联网使用的 TCP/IP 体系结构已经发生了演变,即某些应用程序可以直接使用 IP 层,或甚至直接使用最下面的网络接口层。 沙漏型展示: 五层体系结构 各层的主要功能 应用层&#xff1…

C++ Primer阅读笔记--语句的使用

① 空语句 最简单的语句是空语句,其只含有一个单独的分号; ② switch语句 case 关键字和它对应的值一起被称为 case 标签,case 标签必须是整型常量表达式; char ch getVal(); int iVal 42; switch(ch){case 3.14: // 错误&#…

ZmosHarmony buildroot移植与使用

前言 移植过程 1、添加编译选项编译buildroot。 2、开机启动时设置 LD库的环境变量与PATH路径。 是什么原因需要这样操作? 主要使用busybox,使用buildroot的瑞士军dao。 使用busybox 为buildroot下的使用 第一次启动时设置 由于是在vendor分区因此 …

01 openEuler虚拟化-KVM虚拟化简介

文章目录 01 openEuler虚拟化-KVM虚拟化简介1.1 简介1.2 虚拟化架构1.3 虚拟化组件1.4 虚拟化特点1.5 虚拟化优势1.6 openEuler虚拟化 01 openEuler虚拟化-KVM虚拟化简介 1.1 简介 在计算机技术中,虚拟化是一种资源管理技术,它将计算机的各种实体资源&…

ActiveMQ 反序列化漏洞 (CVE-2015-5254)漏洞复现

当前漏洞环境部署在vulhub,当前验证环境为vulhub靶场(所有实验均为虚拟环境) 实验环境:攻击机----kali 靶机:centos7 需要的jar包:jmet-0.1.0-all.jar 1、启动docker,进入vulhub(靶机&#xff0…

centos主机测试io极限

这里使用fio工具来测试磁盘的io 1.安装fio命令 yum -y install fio 2.在需要测试的磁盘所挂载的目录下创建一个测试目录 由于我就只有一个磁盘,/目录也挂载在这个磁盘上,所以就直接在tmp目录里创建 mkdir /tmp/cs 3.创建一个名为 test.fio 的文件&a…

中级软件设计师备考---信息系统安全

目录 安全属性对称加密技术非对称加密技术信息摘要和数字签名数字信封和PGP各个网络层次的安全保障网络威胁与攻击防火墙技术 安全属性 保密性:最小授权原则、防暴露、信息加密、物理保密 完整性:安全协议、校验码、密码校验、数字签名、公证 可用性&a…

【 Spring 事务传播机制 】

文章目录 一、概念二、为什么需要事务传播机制?三、事务传播机制有哪些?四、Spring 事务传播机制使⽤和各种场景演示4.1 ⽀持当前事务(REQUIRED)4.2 不⽀持当前事务(REQUIRES_NEW)4.3 NESTED 嵌套事务4.4 嵌…

软考软件设计师 软件工程笔记

软件工程 CMM(能力成熟度模型)CMMI(能力成熟度模型集成)瀑布模型V模型(质量保证)增量模型演化模型(迭代更新)原型模型螺旋模型(风险分析)喷泉模型统一过程&am…

大数据编程实验二:熟悉常用的HDFS操作

实验目的 1、理解HDFS在Hadoop体系结构中的角色 2、熟悉使用HDFS操作常用的Shell命令 3、熟悉HDFS操作常用的Java API 实验平台 1、操作系统:Windows 2、Hadoop版本:3.1.3 3、JDK版本:1.84、Java IDE:IDEA 实验步骤 前期&#x…

Springboot整合WebSocket(纯后端)

文章目录 一、 HTTP协议与WebSocket区别二、客户端(浏览器)实现1、websocket对象2、websocket事件3、WebSocket方法 三、服务端实现1、连接过程2、服务端接收客户端消息3、服务端推送消息给客户端 四、后端功能实现 一、 HTTP协议与WebSocket区别 HTTP协…

如何在家自学编程成为一名程序员?

转自:如何在家自学编程,成为一名优秀的程序员? - 知乎 跟着黑马程序员学,自学也可以很优秀。先找到方向—>前/后端?测试?还是什么?—>找到相关的学习路线 —> 坚持不懈的学习 —> …

论文学习——Video LDM (Align your Latents)

Align your Latents: High-Resolution Video Synthesis with Latent Diffusion Models 0. 来源 本文是阅读论文后的个人笔记,适应于个人水平,叙述顺序和细节详略与原论文不尽相同,并不是翻译原论文。 如果想了解所有细节,建议移…

华为OD机试真题(Java),旋转数组的最小数字(100%通过+复盘思路)

一、题目描述 有一个长度为 n 的非降序数组,比如[1,2,3,4,5],将它进行旋转,即把一个数组最开始的若干个元素搬到数组的末尾,变成一个旋转数组,比如变成了[3,4,5,1,2],或者[4,5,1,2,3]这样的。请问&#xf…

Filter 的使用

把对资源的请求拦截下来,从而实现一些特殊功能 ,比如需要先登录再使用其他功能 拦截对资源的请求 放行后,执行完资源,再执行放行后的逻辑 按字符比较升序排序,值小的优先级高 FilterDemo优先级高于FilterDemo2 Listene…

华为OD机试真题(Java),最长的连续子序列(100%通过+复盘思路)

一、题目描述 有N个正整数组成的一个序列,给定一个整数sum,求长度最长的的连续子序列使他们的和等于sum,返回该子序列的长度,如果没有满足要求的序列返回-1。 二、输入描述 第1行有N个正整数组成的一个序列。 第2行给定一个整…