Tomcat 请求过程源码解析(二)

来源:互联网 发布:网络热度榜翻译成英文 编辑:程序博客网 时间:2024/04/20 05:05

Tomcat 请求过程源码解析(二)

前几天学习了一下tomcat的启动过程做的一些事情,这次抽点时间来学习一下tomcat启动之后,一个请求进来到返回都做了什么事情(主要从源码的角度来学习一下)。之前知道在tomcat启动的时候会初始化并启动Connector,此时Connector会通过一个ProtocolHandler来启动一个Endpoint实例,这个Endpoint是个比较关键的角色,它是用来启动专门的线程来监听相应协议的请求的(我们知道tomcat默认支持两种协议 Http和AJP,这次主要关注一下接收http请求的JIoEndpoint),其中JIoEndpoint是基于Java ServerSocket接收http请求。那就从Connector启动Endpoint这个切入点来看一下源代码,首先Connector初始化的时候会装在两个协议的protocolHandler,分别是org.apache.coyote.http11.Http11AprProtocol和org.apache.coyote.ajp.AjpAprProtocol(这里的这两个协议的包名为什么都是coyote下的,其实这个coyote是个Connector框架,主要作用就是解析http或ajp协议,然后传递给tomcat容器,这个不多说,之后学习在详细了解),Http11AprProtocol是AbstractHttp11Protocol的一个子类而AbstractHttp11Protocol又继承了实现ProtocolHandler接口的AbstractProtocol抽象类(AJP连接器同理)。Connector初始化的关键代码如下:

 

 

初始化完Http协议相关的Connector,启动过程就会去启动这个Connector,基于Connector相关生命周期的知识(生命周期抽时间具体研究一下),我们知道会调用start方法:


     从这个Connector的start方法中可以看到调用了一个startInternal方法,这个方法中就通过相关的ProtocolHandler启动了Endpoint,startInternal方法源码如下:


下面是protocolHandler的start方法:


下面是是endpoint start方法里调用的startInternal,这里跳了一步:



 

       上面的贴出来的一堆源码解释一下:首先protocolHandler启动了endpoint,endpoint 默认启动了8调poller线程、8条comet poller线程、一条sendfile线程以及一条acceptor线程,这些线程都是监听tcp发过来的连接请求,区别在于交由不同的processor去处理(具体这些线程池的具体区别有时间仔细研究,谁理解比较深刻的话也可以发出来学习下)。当发送一条http请求例如:http://localhost:8080/examples/servlets/servlet/HelloWorldExample(这个是tomcat自带的一个example,我跑了一下)。首先是AprEndpoint的acceptor 拿到了这个请求,然后他把这个请求交给了poller线程池的一个poller线程去处理(可见acceptor线程和poller线程是有交互的,这个目测了一下,估计是异步处理的东西,acceptor负责接收然异步的去调用具体的处理线程,但具体是怎样的还没研究明白,有待研究),poller接收到这个请求之后将请求交给了AbstractHttp11Processor,经过一番解析设置之后将请求交给了CoyoteAdapter:


       这里在提一嘴这个coyote,在coyoteAdapter处理这个请求之前,都是Connector在接收tcp传过来的请求,然后经过处理封装交由容器,这个Connector就是基于coyote框架的。Coyote处理底层socket的细节,然后将http请求,相应等信息封装成org.apache.coyote.Request和org.apache.coyote.Response的数据结构供tomcat的容器使用。但是tomcat的servlet容器需要数据结构是org.apache.catalina.connector.Request及org.apache.catalina.connector.Response,即需要将coyote的数据结构及封装细节转换成catalina的东西,所以就需要了这个coyoteAdapter转换器类。


      在coyoteAdapter的service方法里面,这两部调用是比较重要的,第一个画红框的部分是根据coyote解析到的http信息转换并初始化了Catalina Connector的容器包括Host,Context,Wrapper以及设置了一些额外的参数信息。


在CoyoteAdapter的postParseRequest的方法中初始化了context、以及wrapper,从这段代码中我们只看到了context以及wrapper的初始化,其实host的初始化是在调用Request的getHost方法的时候再去转换的:

      

而context和wrapper则是显示的创建好了,这个是差别。在这个postParseRequest方法中还会试图解析sessionId,设置一下基础性的参数信息,细节这里先不谈,因为太多了。做完解析以及请求设计的容器的初始化之后,就开始了tomcat中经典的管道调用:


       首先调用的第一级Valve是org.apache.catalina.core.StandardEngineValve,在standardEngineValve里又开始调用了Host容器的Valve:


首先进入的是AccessLogValve,可见host的第一级Valve是AccessLogValve,然后继续往后调用,调用了ErrorReportValve,上面那个logValve肯定是记录日志的,下面这个ErrorReportValve那肯定是用来报告错误的,然后就进入了StandardHostValve,在这个StandartHostValve从Request中拿到了Context,并开始条用了Context容器级别的Valve:


然后中间经过了一个权限校验之后进入了StandardContextValve:


看到这里,其实可以明白为什么WEB-INF和META-INF下的数据不能直接访问的原因,因为这里做了屏蔽。之后这个StantardContextValve会从request更下级的容器Wrapper,然后调用Wrapper容器的pipeline:

      

然后直接进入了StandardWrapperValve,在StandardWrapperValve中去获取了其中封装的servlet,同时也会构造filterChain并执行filterChain:



在执行完FilterChain的请求链路是会执行servlet的service方法:


然后根据我们请求的方式去执行相应的DoXX方法:


从源码中可以看出,我们平时写servlet的时候,通常只写doPost和doGet,其实还是有多种请求方式的,这里就是按照http协议来实现的。


进入doxxx方法就进入了我们自定义的Servlet的方法里面了。我这里直接用了tomcat自带的Example,这个Example是doGet方法,具体的内容如下:


      

这个servlet的左右就是讲一串符合html标准的内容写到了response的PrintWriter里面,然后就完事了,之后的这个Response里面的内容又是怎么写会浏览器的呢?我们再看一下:      

       首先是执行完filterChain的回路,然后执行pipeline的回路,首先是继续执行StandardWrapperValve的逻辑,首先是由StandardWrapperValve来释放servlet以及filterChain的资源:

      

可想而知其他的pipeline回路也是在做类似的事情,这里就不多说了,就是讲让面的pipeline链路反向的执行一下剩余的逻辑。执行完tomcat的容器的pipeline之后,又将控制权返还给了coyoteAdapter:

这里调用了request以及response的finish方法。

在response的finishResponse的方法里会调用outputBuffer的close方法。


其实奥秘就在这个close方法里面了,这个close方法里回调用coyoteResponse的finish的方法将数据流有coyote框架写出(至于写出的细节,这里先不研究)。

可见这个CoyoteAdapter很好的完成了连接器以及tomcat容器的衔接。

原创粉丝点击