tomcat9请求过程

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/ywlmsm1224811/article/details/91384813

谈起tomcat请求过程,我们就要从 Connector 组件说起

一、结构和职责

1、Connector 结构:

Connector 具体细化为 Connector(连接器)、Protocol(协议)、Processor(处理器)

2、职责:负责处理浏览器发送过来的tcp连接请求,创建request 和 response 对象用于和请求端交换数据,connector 会把 request 和 response 传给处理请求的 container 组件,该组件会创建一个线程来处理这个请求。

3、通过源码我们发现,tomcat9 已经废弃了BIO,只有NIO 模式了,支持的协议有 http 和 AJP ,http 对应的处理类是 org.apache.coyote.http11.Http11NioProtocol 和 org.apache.coyote.http11.Http11AprProtocol,AJP 对应的处理类是 org.apache.coyote.ajp.AjpAprProtocol 和 org.apache.coyote.ajp.AjpNioProtocol,默认是协议是http协议,如下图所示:

二、start 方法简介

1、由于请求分不同的协议,这里我们讲一下常见的http协议,也就是 NIoEndPoint 处理,NIO核心的模块有 Channel、Selector 和 Buffer, 下面简单梳理一下流程,总流程图:

下面看一下源码:

2、由以上过程我们可以得出,最终的逻辑都是在NioEndpoint中实现的,下面分析分析 NioEndpoint

2.1、结构,它主要有内部类 Poller、PollelEvent、NioSocketWrapper、SocketProcessor、SendfileData 组成,源码如下:

2.2 处理流程,NioEndpoint 中 有以下四个步骤

2.2.1、创建SocketProcessorBase处理器、PollerEvent 事件、NioChannel 缓存

2.2.2、创建 corePoolSize 10、maximumPoolSize 200、keepAliveTime 60s、LinkedBlockingQueue、 名称前缀是 TP-exec- 的 线程池

2.2.3、初始化最大连接数为 10000 的连接限制

2.2.4、开启数量是 2 和 CPU 核数中的较小值的 Poller 轮询器,线程开启后一直轮询处理 PollerEvent 事件,PollerEvent 事件执行就是将 NioSocketWrapper(NioChannel 和 NioEndpoint封装对象) 注册到 selector ,NioSocketWrapper 和 SelectionKey 绑定,事件执行完之后放入缓存,这样做为了避免频繁创建事件和销毁事件而消耗资源。

2.2.5、开启默认数量是1 的 Acceptor 接收器,开启后线程一直运行着,Socket接收连接,将NioChannel 和 NioEndpoint 封装成 NioSocketWrapper ,添加PollerEvent 事件到队列中

3、下面重点分析一下 Poller、PollerEvent 和 Acceptor

Poller

wakeupCounter

1)、统计Poller中有多少个新连接,这样当Poller进行 selector 时可以根据连接数量来决定是否需要阻塞来等待读写事件的到来

2)、标识Poller进行 selector 时是否有连接到达,如果有就会立刻返回,否则就会阻塞等待1s

keyCount

1)、注册到 Poller 的 NioChannel 中,I/O状态已经ok的个数

run方法源码:

PollerEvent

run方法:

Acceptor

Socket接收请求,run方法:

三、处理Socket请求

1、从 NioEndpoint 中的内部类 Poller 的 run 方法可以得知,处理Socket请求是从调用 processKey 方法开始的,具体的整个过程是 SocketProcessor(NioEndpoint内部类).doRun -> ConnectionHandler(AbstractProtocol内部类).process -> AbstractProcessorLight(实现了 Processor).process -> Http11processor(继承了 AbstractProcessor,它又继承了 AbstractProcessorLight).service -> CoyoteAdapter(实现了 Adapter).service,代码如下:

 SocketProcessorBase 的 run 方法直接调用了 SocketProcessor(NioEndpoint内部类) 中的 doRun 方法,代码如下:

核心部分就是 getHandler().process 方法调用,getHandler 就是 ConnectionHandler ,也就是调用的 ConnectionHandler(AbstractProtocol 内部类) 的 process方法,代码如下:

从上图看出,调用的是AbstractProcessorLight(继承了 Processor) 处理器 的 process 方法,process 方法 然后调用的是 Http11Processor 的 service方法,该方法中对 request 和 response 做了前期准备工作,最核心的就是调用CoyoteAdapter 的 service 方法处理 request 请求,代码如下:

经过上述的层层过程,在CoyoteAdapter(实现了 Adapter)的service方法中终于见到了我们熟悉的 HttpServletRequest 和 HttpServletResponse,postParseRequest 方法设置了 request 的一些特殊参数,比如 代理端口、请求类型(GET, HEAD, POST, PUT, DELETE, OPTIONS)、URL Decoding、编码设置、SessionId、版本(version)、重定向URL等等,代码如下:

接下来的重中之重就是理解整个责任链对请求的处理,也就是connector.getService().getContainer().getPipeline().getFirst().invoke(request, response) 这个方法的调用

四、Host、Context 和 Wrapper 的映射

CoyoteAdapter 把请求交给 Container 之前,会决定具体使用哪个 Host,哪个Context 和 哪个Wrapper,这个过程是怎样的呢?代码入口是CoyoteAdapter  service 方法中的postParseRequest(req, request, res, response) 调用,下面我们来分析一下:

1、根据 host 从 hosts 列表中获取需要使用的 Host

2、根据请求URL获取需要使用的 Context

3、根据请求URL获取需要使用的 Wrapper

上述数据来源都是从 Mapper 的 hosts 中获取的,那么这个对象的数据从哪里来的呢,下面看一下:

上述分析得知:Mapperhost列表是在 MapperListener 启动时候注册过来的,MapperListener 还起到监听器的作用,容器的变化都会引起 MapperListener 处理,刷新所有的Host、Context 和 Wrapper。

五、Pipeline(管道) & Valve(阀)

我们知道 Engine、Host、Context 和 Wrapper 四个 Container,采用了责任链模式,每一个 Container 都定义了一个 Pipeline(StandardPipeline),每一个 Pipeline 又定义了多个 Valve,比如:first、basic, 而 Valve 包含 nextValve,Pipeline 就像是控制逻辑总线一样控制着执行顺序,而Valve 就是具体的执行任务者,每到一个 Pipeline就会执行其挂着的多个 Valve,多个Valve 按照first 、 nextValve 和 basic 的顺序进行。具体总的执行过程如下:

StandardEgineValveStandardEngine 中唯一(basic)的阀门,主要用于从request 中选择其host映射的StandardHost容器

AccessLogValveStandardHost 中第一个(first)阀门,主要用于Pipeline执行结束后记录日志信息

ErrorReportValveStandardHost 中第二个(next)阀门,主要用于获取request中的错误信息,并将错误信息封装到response中返回给用户展示

StandardHostValveStandardHost 中最后(basic)一个阀门,主要用于从request 中选择其context映射的 StandardHost 容器 和获取 request 中的 Session

AuthenticatorValveStandContext 中第一个(first)阀门,主要用于用户权限的验证以及对response 设置header部分属性

StandardContextValveStandContext 中最后一个(basic)阀门,主要用于从request 中选择其 wrapper 映射的 StandardWrapper 容器以及控制禁止直接对  /META-INF/ 和 /WEB-INF/ 目录下资源的直接访问

StandardWrapperValveStandardWrapper 中唯一(basic)的阀门,主要用于调用StandardWrapper 的 loadServlet 方法获取 Servlet 实例、调用 ApplicationFilterFactory的createFilterChain 方法创建 request 过滤器链、记录请求次数、请求时间、请求最大时间、最小时间等

AppliactionFilterChain doFilter 方法中执行了过滤器链中每一个过滤器的职责,过滤器执行后就是调用请求 servlet 的 service 方法,也就是请求url最终要执行的逻辑,代码如下:

扩展:从上述代码可以看出,如果要实现自定义的过滤器,只需要实现 Filter,实现相应的方法即可,自定义过滤器中的doFilter方法需要调用 AppliactionFilterChain 的 doFilter方法,这样子才能让所有的过滤器执行,示例如下:

servlet.service 方法就是调用具体的 Servlet 的 service 方法处理请求,tomcat 默认的 Servlet 只有 DefaultServlet(处理静态资源,比如html、图片等) 和 JspServlet,其对应的路径分别为(conf/web.xml 文件中定义了):

从上述图可知,不同的请求URl对应不同的Servlet请求处理,其中最常用的就是 JspServlet,而DefaultServlet 只是处理一些静态资源,处理url 请求,代码如下:

这里扩展一下:Spring 中处理请求的 Servlet 是 DispatcherServlet (继承了HttpServlet),结构和代码如下:

请求返回:最终通过 CoyoteAdapter 中的 service 方法调用 response.finishResponse ,最后调用 NioSocketWrapper(NioEndpoint内部类) 中的 doWrite 方法写 response 返回结果

六、tomcat热部署(backgroundProcess )

tomcat 的热部署是通过 $CATALINA_HOME/conf/context.xml 配置文件中的属性控制的,即 reloadable="true"开启后tomcat所有应用都会支持热部署了,默认是reloadable="false",具体实现的方法是 backgroundProcess ,如下图:

如果是指定某一个应用需要热部署,就需要在 conf/server.xml 中context 中配置 <Context path="" docBase="/home/myproject/webapps/myproject" reloadable="true"></Context>,如下图:

1、backgroundProcess(后台线程)

backgroundProcess 方法中实现了热部署的功能,到底是怎么实现的呢,下面我们来具体看看:

1)、StandardEngine 实例的时候,设置了 backgroundProcessorDelay = 10(周期间隔10s),大于0才会开启后台线程,后台线程可以做很多事情,比如热部署、资源重加载等,代码如下:

2)、StandardEngine 容器启动的时候会调用 ContainerBase 的 startInternal 方法,该方法通过backgroundProcessorDelay 参数判断是否开启后台线程,代码如下:

3)、通过 ContainerBackgroundProcessorMonitor(ContainerBase内部类)-> ContainerBackgroundProcessor(ContainerBase内部类)来开启后台线程,代码:

4)、调用StandardContext 的 backgroundProcess 方法,代码:

5)、调用 WebappLoader 的 backgroundProcess 方法,代码:

6)、StandardContext 的 reload 方法实现了重新加载的逻辑,步骤就是 setPaused(true) -> stop -> start ->   setPaused(false),代码如下:

总结:

tomcat处理请求过程大致分为以下八个步骤(这里以http协议为例):

1、启动Server,Server启动 Service,Service启动 Connector,Connector连接器初始化 Socket连接(在NioEndpoint实现),绑定 ip(localhost) 和 port(8080)并监听 8080 端口

2、Connector 接收到这个请求,调用 ProtocolHandler 完成请求协议解析,然后交给 SocketProcessor(NioEndpoint 的内部类) 解析请求 header(Http11Processor实现),接着将请求转交给 CoyoteAdapter 处理

3、CoyoteAdapter  解析请求行和请求体,并根据请求URL 匹配 MapperListener 中注册的 hosts 的 Host,继而匹配 Context 和 Wrapper,将解析信息封装到request 和 response中,将请求交给 Container 处理

4、Container 容器从 StandardEngine 开始,使用责任链模式,按照 StandardEngineValve -> StandardHostValve -> StandardContextValve -> StandardWrapperValve 的顺序执行,最终是 StandardWrapperValve 处理 request 请求

5、StandardWrapperValve 依据请求获取到 Host、Context 和 Wrapper ,从 Wrapper 中拿到需要处理的 Servlet,然后创建了 ApplicationFilterChain(过滤器),对请求做些特殊处理等

6、ApplicationFilterChain 中调用 Servlet 的 service 方法对请求做最终的处理,处理后

CoyoteAdapter  对 request (finishRequest)和 response(finishResponse) 做最终处理

7、Container 容器将 response 交给 Connnector,Connector 将请求结果返回给浏览器

8、浏览器解析 response报文,展示内容给用户

至此,tomcat整个请求的过程我们就讲完了。。。。。

猜你喜欢

转载自blog.csdn.net/ywlmsm1224811/article/details/91384813