tomcat中有两个比较重要的流程,第一是启动流程,二是请求流程。启动流程即刚开始运行tomcat时自身的初始化流程;而请求流程是指tomcat装配启动完毕,用户开始发起http请求的流程。
这篇文章主要讲解请求流程。
假设tomcat已经启动好,各个组件已经就位。由于tomcat启动是根据配置文件来驱动的,我们以一个最简单的http connector为例。
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" />
这里不得不先提及Connector装配的过程。在初始化Connector时,根据配置的protocol(这里是HTTP/1.1),会创建出该协议对应的handler,该handler事实上会处理真正的请求。ProtocolHandler是一个接口,为什么这里要用接口,因为你可以选择到底是哪个协议实现在实际运行,比如,还可以配置一个AJP的Connector。
以Http11Protocol这个handler为例,我们来了解一下handler具体是如何工作的。在Http11Protocol中,有三个重要的实例,分别是Http11ConnectionHandler,JIoEndpoint和CoyoteAdapter,他们各司其职。如图:
Http11ConnectionHandler存在的意义主要是为了对重复利用创建好的Http11Processor。我自己在编写线程安全的程序时也经常有这样的困惑:如果我将类设计为一个实例,那多个线程的调用必然会涉及到同步及锁的问题,问题的重点在于,一旦加锁,则效率方面就会折扣;取而代之,我在每个多线程中就创建一个实例,就能避免这个问题,但是随之而来的是大量线程的创建消亡必然导致大量该实例的创建消亡,要知道,该实例有可能是一个重型的、引用了其他很多类的大对象,那么重复创建销毁实例就是一种浪费了,这里,tomcat中采用的做法就是在Http11ConnectionHandler中用队列实现类似于对象池的东西。以下代码去掉了捕获异常部分。
protected ConcurrentLinkedQueue<Http11Processor> recycledProcessors =
new ConcurrentLinkedQueue<Http11Processor>() {
};
public boolean process(Socket socket) {
Http11Processor processor = recycledProcessors.poll();
if (processor == null) {
processor = createProcessor();
}
if (processor instanceof ActionHook) {
((ActionHook) processor).action(ActionCode.ACTION_START, null);
}
if (proto.isSSLEnabled() && (proto.sslImplementation != null)) {
processor.setSSLSupport
(proto.sslImplementation.getSSLSupport(socket));
} else {
processor.setSSLSupport(null);
}
processor.process(socket);
return false;
finally {
// if(proto.adapter != null) proto.adapter.recycle();
// processor.recycle();
if (processor instanceof ActionHook) {
((ActionHook) processor).action(ActionCode.ACTION_STOP, null);
}
recycledProcessors.offer(processor);
}
return false;
}
再来说说JIoEndPoint,我们可以打开其他非NIO的ProtocolHandler实现,会发现他们都保存一个JIoEndPoint实例,这里不得不佩服tomcat的设计者,能够将不同的协议中间共同的东西抽象出来并封装,这个共同的东西是什么呢?就是一个连接器的监听线程,以及每个请求来时的工作线程。JIoEndPoint就负责创建并执行这些线程,在这里将线程的执行抽象出来,而将具体如何执行委托给Http11ConnectionHandler,这样,就分离了抽象和实现。
所以现在这部分的流程就清楚了,初始化Http11Protocol时同时也会初始化Http11ConnectionHandler和JIoEndPoint,同时JIoEndPoint持有Http11ConnectionHandler,当请求来时,事实上是从JIoEndPoint首先开始的,因为JIoEndPoint负责监听。当一个请求过来时,监听线程将工作线程启动起来,并由工作线程调用Http11ConnectionHandler的process方法,由于传入process的参数是Socket,故Handler可以做到通用。
再来说CoyoteAdapter,顾名思义,adapter是一个适配器,他适配了org.apache.coyote.Request和org.apache.catalina.connector.Request,后者正是继承了HttpServletRequest。不知道你有没有跟我相同的疑问,为什么需要这样一个适配器,这不是多此一举吗?事实上,这里适配器起到了隔离内外的作用,对于coyote内部,用coyote.Request可以做一些request本身职责以外的事情(但是又不能不在request中实现,因为生命周期就是request,如设置开始结束时间,日志记录等等),但是这些事情不需要给开发者看到,有可能会被误用导致coyote出错,于是暴露给开发者的是connector.Request,中间使用adapter来适配。其实这个Adapter可以追溯到Connector,在Connector初始化时就已经将Adapter创建好并且作为他的实例变量,然后通过ProtocolHandler,Http11ConnectionHandler,Http11Processor层层赋值,最终在Http11Processor的process方法中被调用。之所以说Adapter重要,原因在于process方法做了各种准备工作,目的就是调用adapter.service(request, response);来真正开始后续服务。Http11Processor的代码值得研究一下,比如prepareRequest,他准备request到底干了些啥?process方法如何处理异常,又是如何收尾service调用的。另外,这里留个问题,为什么在Http11Processor中使用Adapter,却要在Connector中创建并麻烦的层层赋值呢?
请求的sequence图如下:
从Adapter开始,请求流向就变得有层次并且递归。请看CoyoteAdapter的service调用流程
connector.getContainer().getPipeline().getFirst().invoke(request, response);
container实际上是从StandardServer给每一个Connector的,containter到底是什么呢?在tomcat中,container有4种对象,分别是Engine, Host, Context, Wrapper,这里正常情况下应该得到一个engine的实例。
pipeline是什么呢?在tomcat中,pipeline就是一个水管管道,当request流过水管时,上面可以打开一个一个的valve(阀),valve处理完request并将其继续下送,直到送达最后一个称为basic的valve,这个valve通常是tomcat自己指定的。另外,container既是一个Pipeline,又hold住一个Pipeline,实际上就是一个代理模式,container是实际pipeline的代理,但是为什么要这么设计呢?因为pipeline本身并不负责事件的发送,但是container必须得将事件层层下送(即代理事件发送)。
Pipeline.getFirst()会取出管道中第一个valve并且调用,那调完第一个valve咋办呢,事实上,大多数valve都有这样一个方法getNext().invoke(request, response);即顺序调用其后的valve。直到第一个Container的basic valve即StandardEngineValve。原来StandardEngineValve会再重复调用host管道中的第一个valve直到最后一个。当然,可以想象,每个上级容器中basic valve都会将request流经下级pipeline,直到最后一个StandardWrapperValve。
public final void invoke(Request request, Response response)
throws IOException, ServletException {
Host host = request.getHost();
if (host == null) {
response.sendError
(HttpServletResponse.SC_BAD_REQUEST,
sm.getString("standardEngine.noHost",
request.getServerName()));
return;
}
host.getPipeline().getFirst().invoke(request, response);
}
在StandardWrapperValve的invoke方法中,会创建一个ApplicationFilterChain,并且使用该chain进行filter,大家一定猜到了,这里filterchain里面的filter就是配置在web.xml中的Filter实现,将filter实现以一个链的形式串在一起形成我们的ApplicationFilterChain,但是奇怪的是整段代码读下来都没有发现调用servlet的service方法。这里servlet其实被传入chain中保存,在所有filter完成过滤之后doFilter方法会接着调用该servlet的service方法。
ApplicationFilterChain filterChain =
factory.createFilterChain(request, wrapper, servlet);
filterChain.doFilter(request.getRequest(),
response.getResponse());
至此,整个调用流程结束(response流程与上面只是相反的过程)。
可以看出来。
一,valve和filter都能做过滤,但是过滤范围不同,valve针对的是每一级container,而filter只在最后一级wrapper时才有用,也就是说,假如你有两个web应用,两个你都想加入日志以便追踪他们的调用情况,应该用哪个你懂的。
二,pipeline是每一个container都包含一个,每个pipeline上面可以有多个valve,所以,一个container下面可以配置多个valve。