tomcat请求处理过程源码分析

前言

在之前的篇章中,基本上了解了tomcat的整体架构,并通过源码调试的方式分析了tomcat初始化过程中各个组件主要做的事情,我们知道,tomcat作为一款企业应用级的容器,其最重要的功能就是发布应用,接收客户端的请求并响应,但是从tomcat底层其究竟是怎么从接收一个请求到最后响应给客户端,这个过程是怎么执行的呢?本篇通过源码的方式简单分析一下

环境准备

编写一个简单的web工程,使用本地的tomcat部署起来并运行,由于这个过程比较简单,相信做过web应用开发并使用tomcat部署过项目的同学都知道,将主要的几个文件直接贴出来,方便学习的小伙伴们参考
在这里插入图片描述

DemoServlet

public class DemoServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("doGet  demo servlet .... working");
        resp.getWriter().write("hello servlet");
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("doPost  demo servlet .... working");
    }
}

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_4_0.xsd"
         version="4.0">

    <servlet>
        <servlet-name>demoServlet</servlet-name>
        <servlet-class>com.congge.servlet.DemoServlet</servlet-class>
    </servlet>

    <servlet-mapping>
        <servlet-name>demoServlet</servlet-name>
        <url-pattern>/demo/findServlet</url-pattern>
    </servlet-mapping>

</web-app>

最后浏览器访问下,http://localhost:8080/demo/findServlet,即可得到响应结果

在这里插入图片描述

以上是一个非常简单的使用tomcat进行请求处理与响应的例子,通过这个案例接下来我们就要思考,从请求到响应,tomcat为我们做了哪些事情呢?

Tomcat 请求处理流程

在这里插入图片描述
其实总结起来就是一句话,tomcat负责将请求交给合适的servlet处理这个请求

还记得tomcat的容器架构图吗?通过一层层的包裹,设计了多层次容器,Tomcat是怎么确定每一个请求应该由哪个Wrapper容器里的 Servlet来处理呢?答案是,Tomcat是用Mapper组件来完成这个任务的

Mapper组件的功能就是将用户请求的URL定位到一个Servlet,它的工作原理是: Mapper组件里保存了Web应用的配置信息,其实就是容器组件与访问路径的映射关系, 比如Host容器里配置的域名、Context容器里的Web应用路径,以及Wrapper容器里 Servlet映射的路径,你可以想象这些配置信息就是一个多层次的Map

当一个请求到来时,Mapper组件通过解析请求URL里的域名和路径,再到自己保存的 Map里去查找,就能定位到一个Servlet。请你注意,一个请求URL最后只会定位到一个 Wrapper容器,也就是一个Servlet

下面的示意图中 , 就描述了 当用户请求链接 order/buy接口 之 后, 是如何找到最终处理业务逻辑的servlet

上面这幅图描述了根据请求的URL如何查找到需要执行的Servlet , 下面我们再来解析一下 , 从Tomcat的设计架构层面来分析Tomcat的请求处理

在这里插入图片描述

整个执行步骤分解如下:

  • Connector组件Endpoint中的Acceptor监听客户端套接字连接并接收Socket
  • 将连接交给线程池Executor处理,开始执行请求响应任务
  • Processor组件读取消息报文,解析请求行、请求体、请求头,封装成Request对象
  • Mapper组件根据请求行的URL值和请求头的Host值匹配由哪个Host容器、Context容
    器、Wrapper容器处理请求
  • CoyoteAdaptor组件负责将Connector组件和Engine容器关联起来,把生成的 Request对象和响应对象Response传递到Engine容器中,调用 Pipeline
  • Engine容器的管道开始处理,管道中包含若干个Valve、每个Valve负责部分处理逻 辑。执行完Valve后会执行基础的 Valve–StandardEngineValve,负责调用Host容器的 Pipeline
  • Host容器的管道开始处理,流程类似,最后执行 Context容器的Pipeline
  • Context容器的管道开始处理,流程类似,最后执行 Wrapper容器的Pipeline
  • Wrapper容器的管道开始处理,流程类似,最后执行 Wrapper容器对应的Servlet对象 的 处理方法

源码分析前准备

找到上面创建的servlet的demo位置,windows下在项目的out目录中
在这里插入图片描述

将整个servlet_demo1_war_exploded拷贝出来,拷贝至本地的某个tomcat的webapps目录下,文件重新命名为servlet_demo1,
在这里插入图片描述
启动本地的tomcat,浏览器访问:http://localhost:8080/servlet_demo1/demo/findServlet
在这里插入图片描述

至此,我们完成了一个简单的web应用部署到tomcat容器的过程,但这样并不利于我们阅读源码,分析请求的执行流程,为了达到这个目的,我们只需要将上面的srevlet_demo1放到源码的webapps目录下,将源码工程启动即可

注意这里有个坑,本人在搭建这个过程时,翻了很多资料也没有人能够解答这个问题,当时在部署tomcat源码的时候,还记得在本地重新创建了一个文件目录吗?将原来的tomcat源码文件全部拷贝过来进行编译打包的

在这里插入图片描述
事实上,上面的servlet_demo应该放到这个catalina-home的webapps下,因为这里才是实际源码调试的位置,之前我一直放到tomcat源码目录下的webapps中导致怎么也访问不了

在这里插入图片描述

按照上面的步骤操作之后,我们来到源码环境,找到Bootstrap这个类,按照之前调试源码的方式右键运行main即可完成在tomcat源码环境下启动tomcat的过程
在这里插入图片描述

源码调试过程与分析

参照上文中tomcat请求流程分析的时序图,下面通过断点来调试一下请求是如何执行的

还记得在tomcat源码初始化流程分析中谈到的,当全部组件的初始化执行到最后一步,是tomcat的endpoint准别完毕,等待外部的请求进行连接吗

那么分析请求执行流程时,我们找到这个endpoint的接收请求位置即可开始分析

来到NioEndPoint类,从Acceptor这个内部中run中的serverSock.accept()开始,从注释也可以看出来,这里表示接收请求开始的位置
在这里插入图片描述
继续跟进,来到该类中的processKey方法,进去来到processSocket方法
在这里插入图片描述
在这个方法中我们注意到,有一个executor.execute(sc)的方法,在时序图中有交代,请求到来的时候,不会直接去处理这个请求,在tomcat8的默认线程模型中,会创建一个线程池,交给线程池去处理
在这里插入图片描述
既然请求要交给线程池处理,不管是线程也好,线程池也好,最终都是要执行里面derun方法的,这样去找到响应的run方法,看看都做了什么事,来到SocketProcessorBase类
在这里插入图片描述
在doRun这个方法中,前面的部分很好理解,做http握手的工作
在这里插入图片描述
在这个方法中,来到process方法
在这里插入图片描述

从这里开始,差不多就看到了处理请求连接的影子了,来到process方式实现类AbstratProtocol中的process
在这里插入图片描述
在这个方法中,其实最重要的工作就是将连接进来的请求处理并包装为request请求,以及后续将响应的请求转为response对象

断点继续往下,来到:processor.process,即在这个方法的实现中,解析请求并包装为request请求
在这里插入图片描述

继续进入,
在这里插入图片描述
来到这个方法中的service方法
在这里插入图片描述
继续跟进,从这里可以看到,这里首先构造了一个request对象,然后将传入过来的socket包装对象进行解析,填充request
在这里插入图片描述
断点往下来到该方法的:getAdapter().service(request, response)
在这里插入图片描述
在这里插入图片描述

对这个图想必大家一定不陌生,当请求对象都准备好了以后,就要开始处理请求了,具体怎么处理呢?当然是需要有一个组件能够根据你请求中的URL路径找到一个具体的servlet来处理啊

这就是Adapter的作用,翻译过来就是请求处理的适配器,通过getAdapter获取合适的处理器,然后调用service方法进行后续的处理

在这里插入图片描述

往下走来到invoke方法,从这里开始,可以猜想后面的逻辑就要到容器组件层了,
在这里插入图片描述
也就是到达图中的 container模块了
在这里插入图片描述

继续往下,来到StandardEngineValve中的invoke方法,这里进入容器之后,大家一定要联想到server.xml中的容器组件层级结构,就很好理解执行的逻辑,参见下面这幅图,后面的代码逻辑就是按照这个层级进行步步解析操作
在这里插入图片描述

在这里插入图片描述
继续往下走,在方法:host.getPipeline().getFirst().invoke(request, response)进入,来到StandardHostValve中
在这里插入图片描述
通过request获取host主机下面配置的context对象,就可以获取到我们部署的web应用
在这里插入图片描述
来到: context.getPipeline().getFirst().invoke(request, response),沿着上面的思路,这时候应该就要到context的处理逻辑中,
在这里插入图片描述
在这里插入图片描述

继续往下,来到:wrapper.getPipeline().getFirst().invoke(request, response),接下来就走到了context中的wrapper处理器中
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在上面的容器层级结构图中发现,在Wrapper中存放的就是Servlet,在这个方法中往下,我们看到了Servlet
在这里插入图片描述
来到下面这里,通过wrapper得到一个servlet,给上面初始化的servlet赋值,并且得到的servlet正好就是Demo中自己编写的DemoServlet
在这里插入图片描述
servlet得到了之后,就要走到时序图中的filterChain那一串逻辑了,即通过得到的servlet构造出一个filterChain的对象
在这里插入图片描述
对于servlet中的filterChain有必要简单解释下,容器这样做个人理解可以说是提升了容器的扩展性和安全性,设想没有过滤器,所有的请求都可以访问后端的资源,这样就无法保证应用的安全性,通过过滤器,对请求的参数等进行过滤,拦截之后再访问真正的资源确保了后端资源的安全性

同时,我们在开发web项目时,经常会自定义一些过滤器,只需要实现Filter接口重新里面的逻辑即可,最终都要在真正由servlet执行逻辑之前加入到filterChain之中,filterChain是由多个Filter的组合,各个filter负责各自的业务逻辑,但是任何一个filter执行失败,都可以阻止继续往下

回到源码,继续往下,走到:filterChain.doFilter方法,
在这里插入图片描述

在这里插入图片描述

最终来到:servlet.service(request, response)方法处
在这里插入图片描述

继续进入service方法,在这个方法可以发现,根据请求类型的不同执行响应的业务逻辑模块代码,
在这里插入图片描述

当执行doGet时候,由于上面已经定位到我们自己的DemoServlet了,这时就会去执行自定义的servlet的doGet方法
在这里插入图片描述
通过控制台可以看到输出的结果
在这里插入图片描述

到这里整个请求到达servlet并通过servlet处理之后响应结果的流程就完成了,当然response中的请求结果是如何输出的有兴趣的同学可以通过断点再一路返回就可以知道了

本篇主要通过源码的跟踪方式了解了tomcat的请求是如何到达servlet进行处理的,篇幅较长,跟着断点走,相信对整个请求的执行链和底层的逻辑会有一个更深的认识

本篇到此结束,最后感谢观看!

猜你喜欢

转载自blog.csdn.net/zhangcongyi420/article/details/111338879