Tomcat源码五部曲(三)、 请求处理过程
Tomcat处理Http请求又是非常复杂的一步,至少我是这么认为。对于这一流程,个人实在没有精力深入研究了(怕了怕了),所以在这里只总结一下大概,或许有什么地方可能不太正确,请多多指正。
一、主要类简介
处理请求主要涉及到这么几个类,Endpoint、Acceptor、Mapper、CoyoteAdapter、Processor、ProtocolHandler等还有很多。
Endpoint:通信监听的接口,具体Socket的接收处理类,对传输层的抽象,根据I/O方式的不同,提供了NioEndpoint、AprEndpoint、Nio2Endpoint三个实现。Acceptor:在第一章也提到过,当Connector启动时,也会启动他内部的Endpoint,Endpoint并行运行多个线程,每个线程运行着一个Acceptor实例,Acceptor中监听端口通信,也就是用于接受新连接,并将新连接封装一下,也就是将Socket封装为SocketWrapper,并交给SocketProcessor对象处理(此过程由线程池异步处理)。根据I/O的不同当然有不同处理方式,如NIO采用轮询方式检测SelectKey是否就绪,如果就绪,则获取一个有效的SocketProcessor对象提交到线程池。
Mapper:维护着映射。
CoyoteAdapter:将Connector和Mapper、Container联系起来。
Processor:负责构造Request、Response对象,并通过Adapter交给Catalina容器处理,对应用层的抽象。Processor是单线程的,Tomcat在同一次链接中复用Processor。并按照协议的不同,分为AjpProcessor(AJP)、Http11Processor(HTTP/1.1)、StreamProcessor(HTTP/2.0)ProtocolHandler:封装AbstractEndpoint和Processor,实现针对具体协议的处理功能,按照协议和I/O提供了6个实现类。
二、跟踪调用栈
要想跟踪请求的调用栈,可以直接在HttpServlet的service方法上打断点,调用过程说多不多,说少也不少。最上边是WrappingRunnable,自身也是实现了Runnable接口,内部又包装了一个Runnable,自身run被启动时候也会直接调用内部Runnable的run方法,只不过用try包装了一下,避免一些错误。
但是如果从这开始的分析的话,还不是头,还需要往前走。(以下以NIO方式分析)。
三、请求处理过程
首先是NioEndpoint的startInternal方法,主要就是创建线程池、启动Poller轮询、启动Acceptor接收。
public void startInternal() throws Exception {
......
/**
* 创建线程池
*/
if (getExecutor() == null) {
createExecutor();
}
/**
* 开启轮训线程
*/
poller = new Poller();
.....
pollerThread.start();
/**
* 开启接收线程
*/
startAcceptorThread();
}
Acceptor启动后会阻塞在 ServerSocketChannel.accept()方法处,当有新连接到达时,返回一个 SocketChannel。然后封装为SocketWrapper实例,并交给SocketProcessor处理(此过程是由线程池异步处理)。
SocketProcessor是一个线程池Worker实例,每一个I/O方式都有自己的实现。
至于SocketProcessor我们要看子类实现的doRun方法,如NioEndpoint.SocketProcessor下的doRun中会调用ConnectionHandler的process方法进行处理。其中的getProtocol().createProcessor()会创建一个 Http11Processor,它用来解析 Socket,将 Socket 中的内容封装到 Request 中,但这个 Request 是临时使用的一个类,它的全类名是 org.apache.coyote.Request,也就是在Tomcat源码中有两个Request。
protected static class ConnectionHandler<S> implements AbstractEndpoint.Handler<S> {
public SocketState process(SocketWrapperBase<S> wrapper, SocketEvent status) {
if (processor == null) {
//返回Http11Processor
processor = getProtocol().createProcessor();
//调用Http11Processor的process
state = processor.process(wrapper, status);
}
}
}
接着就是Http11Processor的process方法,由于自身没有重写,所以到了父类AbstractProcessorLight中。在父类的process方法中判断如果是SocketEvent.OPEN_READ事件,则交给子类实现的service方法中处理。所以还是回到了Http11Processor的service方法
public abstract class AbstractProcessorLight implements Processor {
public SocketState process(SocketWrapperBase<?> socketWrapper, SocketEvent status)
throws IOException {
if (dispatches != null) {
//.........
} else if (status == SocketEvent.OPEN_READ) {
//交给子类实现
state = service(socketWrapper);
}
}
在Http11Processor中依靠Http11InputBuffer来读取,也就是读取请求行、请求头,对应的方法依次是parseRequestLine、parseHeaders。
当读取完成后,开始下一步交给CoyoteAdapter处理。
@Override
public SocketState service(SocketWrapperBase<?> socketWrapper){
inputBuffer.parseRequestLine();
inputBuffer.parseHeaders();
//.........
getAdapter().service(request, response);
//............
}
四、CoyoteAdapter
请求映射就在这个过程中,具体也分为两步,一步分在postParseRequest中,一步分在Mapper.map中。这个过程也是非常复杂,恕我无能,没有去深究。
@Override
public void service(org.apache.coyote.Request req, org.apache.coyote.Response res){
/**
*根据参数生成一系列实例,其中包含最重要的wrapper
*/
postParseSuccess = postParseRequest(req, request, res, response);
/**
* 交给容器处理,第一个容器是Engine Valve
*/
Service service = connector.getService();
Engine container = service.getContainer();
Pipeline pipeline = container.getPipeline();
Valve first = pipeline.getFirst();
first.invoke(request,response);
//connector.getService().getContainer().getPipeline().getFirst().invoke(request,response);
}
在postParseRequest中重要的也是这一句,getMapper返回的就是Mapper,Mapper维护着请求链接于Host、Context、Warpper等的映射。
最终将结果放在org.apache.catalina.connector.Request.mappingData对象中,mappingData中的wrapper属性就保存着能处理这个请求的Servlet,还有其他如所属的Host实例、所属的Context实例。
protected boolean postParseRequest(org.apache.coyote.Request req, Request request,
org.apache.coyote.Response res, Response response) throws IOException, ServletException {
connector.getService().getMapper().map(serverName, decodedURI, version, request.getMappingData());
}
但如果没有找到,他就返回DefaultServlet处理,这对于项目路径匹配到了,而项目下没有能处理这个请求路径的Servlet是这样的,但如果项目路径也没匹配到,则都返回null,后续会直接return。
之后是交给第一个容器处理。
connector.getService().getContainer().getPipeline().getFirst().invoke(request,response);
五、Pipleline、Valve
Tomcat中每个容器通过一个责任链来处理具体的请求,于是定义了Pipeline(管道)和Valve(阀)两个接口,前者用于构造责任链,后者代表责任链上的每个处理器,还有Pipleline中维护这一个基础的Valve,它始终在Pipleline的末端,也就是他会最后执行。
所以,在ContainerBase中有个Pipleline(另外四个容器都会继承ContainerBase),通过pipeline.setBasic
可以为这个Pipleline设置一个基本的Valve,他将在最后执行。在另外四个容器的构造方法中都会调用
pipeline.setBasic()
来设置基本的Valve,但有的容器还在其他地方调用pipeline.addValve
添加其他的Valve,这样的话这些Valve会优先于Basic。并且,这个Valve处理完之后要手动调用getNext().invoke(request,response);交给下一个处理。
下面是这四个容器基本的Valve。
上述代码中的
connector.getService().getContainer().getPipeline()
也就是返回就是StandardEngineValve,这也不是绝对,如果这个容器上没有添加过其他Valve的话。
public StandardEngine() {
super();
/**
* 设置基本处理器
*/
pipeline.setBasic(new StandardEngineValve());
}
也就是下面这一部分会跟各种Valve打交道。
六、StandardEngineValve
所以继续跟踪代码来到StandardEngineValve的invoke方法,这里面就是获取到Host,然后获取Host下的Pipeline,如果Host中没有添加其他Valve的话,第一个也是最后一个,也就是StandardHostValve。
(Tomcat中调用下一个时都是写在了一行,不方便调试,你可以把他拆分出来)
@Override
public final void invoke(Request request, Response response)
throws IOException, ServletException {
/**
* 获取对应主机
*/
Host host = request.getHost();
if (host == null) {
return;
}
/**
* 获取Host下的Pipeline,并交给第一个Valve处理
*/
Pipeline pipeline = host.getPipeline();
Valve first = pipeline.getFirst();
first.invoke(request,response);
//host.getPipeline().getFirst().invoke(request,response);
}
注意此时的Host从何而来?点击去可以看到是mappingData的一个字段,而他在上面请求映射的时候被赋值,这里在上面说过了。但其实StandardHostValve不是第一个,第一个是ErrorReportValve,他在StandardHost的startInternal方法中被添加。
@Override
protected synchronized void startInternal() throws LifecycleException {
String errorValve = getErrorReportValveClass();
Valve valve =(Valve) Class.forName(errorValve).getConstructor().newInstance();
getPipeline().addValve(valve);
}
理所应当我们走到ErrorReportValve的invoke下。
@Override
public void invoke(Request request, Response response) throws IOException, ServletException {
//交给下一个处理
getNext().invoke(request, response);
//如果已经提交
if (response.isCommitted()) {
.............
return;
}
report(request, response, throwable);
在此过程中你可以找到这个错误页面的生成过程,也就是report方法。看到这么多拼接应该能知道干什么吧。
接着是StandardHostValve的invoke方法,同样的重点也是在下面这句。拿到Context的第一个Valve。如果没有做其他配置,第一个是NonLoginAuthenticator,然后才是基本处理者StandardContextValve。
现在可以理解为什么这里要判断为null吧?这是在项目路径也匹配不到时候直接返回。
举个例子,现在有个项目Demo,Demo下有个Servlet,地址为/TestServlet,访问他完整的路径就是http://xxx/Demo/TestServlet,但你不小心打成了http://xxx/Demxx/TestServlet,这时候Context就为空,直接返回。
Context context = request.getContext();
if (context == null) {
return;
}
Pipeline pipeline = context.getPipeline();
Valve first = pipeline.getFirst();
first.invoke(request,response);
//context.getPipeline().getFirst().invoke(request, response);
其他就不看了,各个内置的Valve都做了一些不为人知的骚操作。
七、过滤器配置
直接到StandardWrapperValve的invoke方法,这里面就可以看到过滤器的创建以及执行过程,也就是这句,内部先找到所有过滤器,然后根据匹配规则筛选出能为这个Servlet过滤的过滤器。
ApplicationFilterChain filterChain =
ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);
//...........
filterChain.doFilter(request.getRequest(), response.getResponse());
然后执行filterChain.doFilter开始依次过滤,doFilter的核心方法也在internalDoFilter中。通过把自己传递给我们编写的过滤器,我们处理完成后必须调用filterChain.doFilter(servletRequest,servletResponse);
继续下一个。
八、service
当我们所有过滤都执行完成后,调用Servlet的service方法,由service调用doGet或者doPost。
这其实也是个递归过程,当pos<n时继续下一个过滤器,n为过滤器的数量,pos是当前过滤器位置。
private void internalDoFilter(ServletRequest request,
ServletResponse response)
throws IOException, ServletException {
if (pos < n) {
ApplicationFilterConfig filterConfig = filters[pos++];
Filter filter = filterConfig.getFilter();
//调用我们的过滤器
filter.doFilter(request, response, this);
return;
}
//过滤器都执行完成后调用我们servlet的service方法,由service调用doGet或者doPost。
servlet.service(request, response);
}
还没说这个servlet实例从哪来,其实也是在StandardWrapperValve的invoke下。ValveBase有个字段是container,保存着这个Valve所属的容器。对于StandardWrapperValve自然而然是StandardWrapper,既然拿到了StandardWrapper,那就好办了,在前几章说过内部会保存着这个Servlet的实例和完整类名,可以通过其allocate方法初始化。
servlet = wrapper.allocate();
这一流程是真的复杂.........end
原文始发于微信公众号(十四个字节):Tomcat源码五部曲(三)、 请求处理过程