正文
通过这一系列的前三部分看到了一次客户端连接在 Tomcat 内部被转换成了请求对象(
org.apache.catalina.connector.Request
类的实例),并在该请求对象内部将与本次请求相关的 Host、Context、Wrapper 对象的引用。本文主要分析该请求对象在容器内部流转的经过。
再来看一下 Tomcat 7 内部的组件结构图:
其实这张图已经给出了答案,在 Connector 接收到一次连接并转化成请求( Request )后,会将请求传递到 Engine 的管道( Pipeline )的阀( ValveA )中。请求在 Engine 的管道中最终会传递到 Engine Valve 这个阀中。接着请求会从 Engine Valve 传递到一个 Host 的管道中,在该管道中最后传递到 Host Valve 这个阀里。接着从 Host Valve 传递到一个 Context 的管道中,在该管道中最后传递到 Context Valve 中。接下来请求会传递到 Wrapper C 内的管道所包含的阀 Wrapper Valve 中,在这里会经过一个过滤器链( Filter Chain ),最终送到一个 Servlet 中。
如果你不了解上面这段文字描述中所谓的管道( Pipeline )和阀( Valve )的概念,别急,下面会讲到这个。先从源码层面看下这段文字描述的经过。上面提到的
org.apache.catalina.connector.CoyoteAdapter
类的 service 方法:
1 public void service(org.apache.coyote.Request req,
2 org.apache.coyote.Response res)
3 throws Exception {
4
5 Request request = (Request) req.getNote(ADAPTER_NOTES);
6 Response response = (Response) res.getNote(ADAPTER_NOTES);
7
8 if (request == null) {
9
10 // Create objects
11 request = connector.createRequest();
12 request.setCoyoteRequest(req);
13 response = connector.createResponse();
14 response.setCoyoteResponse(res);
15
16 // Link objects
17 request.setResponse(response);
18 response.setRequest(request);
19
20 // Set as notes
21 req.setNote(ADAPTER_NOTES, request);
22 res.setNote(ADAPTER_NOTES, response);
23
24 // Set query string encoding
25 req.getParameters().setQueryStringEncoding
26 (connector.getURIEncoding());
27
28 }
29
30 if (connector.getXpoweredBy()) {
31 response.addHeader("X-Powered-By" , POWERED_BY);
32 }
33
34 boolean comet = false ;
35 boolean async = false ;
36
37 try {
38
39 // Parse and set Catalina and configuration specific
40 // request parameters
41 req.getRequestProcessor().setWorkerThreadName(Thread.currentThread().getName());
42 boolean postParseSuccess = postParseRequest(req, request, res, response);
43 if (postParseSuccess) {
44 //check valves if we support async
45 request.setAsyncSupported(connector.getService().getContainer().getPipeline().isAsyncSupported());
46 // Calling the container
47 connector.getService().getContainer().getPipeline().getFirst().invoke(request, response);
48
49 if (request.isComet()) {
50 if (!response.isClosed() && !response.isError()) {
51 if (request.getAvailable() || (request.getContentLength() > 0 && (!request.isParametersParsed()))) {
52 // Invoke a read event right away if there are available bytes
53 if (event(req, res, SocketStatus.OPEN)) {
54 comet = true ;
55 res.action(ActionCode.COMET_BEGIN, null);
56 }
57 } else {
58 comet = true ;
59 res.action(ActionCode.COMET_BEGIN, null);
60 }
61 } else {
62 // Clear the filter chain, as otherwise it will not be reset elsewhere
63 // since this is a Comet request
64 request.setFilterChain(null);
65 }
66 }
67
68 }
69 AsyncContextImpl asyncConImpl = (AsyncContextImpl)request.getAsyncContext();
70 if (asyncConImpl != null) {
71 async = true ;
72 } else if (!comet) {
73 request.finishRequest();
74 response.finishResponse();
75 if (postParseSuccess &&
76 request.getMappingData().context != null) {
77 // Log only if processing was invoked.
78 // If postParseRequest() failed, it has already logged it.
79 // If context is null this was the start of a comet request
80 // that failed and has already been logged.
81 ((Context) request.getMappingData().context).logAccess(
82 request, response,
83 System.currentTimeMillis() - req.getStartTime(),
84 false );
85 }
86 req.action(ActionCode.POST_REQUEST , null);
87 }
88
89 } catch (IOException e) {
90 // Ignore
91 } finally {
92 req.getRequestProcessor().setWorkerThreadName(null);
93 // Recycle the wrapper request and response
94 if (!comet && !async) {
95 request.recycle();
96 response.recycle();
97 } else {
98 // Clear converters so that the minimum amount of memory
99 // is used by this processor
100 request.clearEncoders();
101 response.clearEncoders();
102 }
103 }
104
105 }
之前主要分析了第 42 行的代码,通过 postParseRequest 方法的调用请求对象内保存了关于本次请求的具体要执行的 Host、Context、Wrapper 组件的引用。
看下第 47 行:
connector.getService().getContainer().getPipeline().getFirst().invoke(request, response);
虽然只有一行,但调用了一堆方法,这里对这些方法逐个分析一下:
connector.getService() 获取的是当前 connector 关联的 Service 组件,默认情况下获得的就是
org.apache.catalina.core.StandardService
的对象。其 getContainer 方法获得的是
org.apache.catalina.core.StandardEngine
的对象,这段的由来在
前面讲 Digester 的解析文章
时,createStartDigester 方法中的这段代码:
digester.addRuleSet(new EngineRuleSet("Server/Service/" ));
在 EngineRuleSet 类的 addRuleInstances 方法中的这一段代码:
public void addRuleInstances(Digester digester) {
digester.addObjectCreate(prefix + "Engine" ,
"org.apache.catalina.core.StandardEngine" ,
"className" );
digester.addSetProperties(prefix + "Engine" );
digester.addRule(prefix + "Engine" ,
new LifecycleListenerRule
("org.apache.catalina.startup.EngineConfig" ,
"engineConfigClass" ));
digester.addSetNext(prefix + "Engine" ,
"setContainer" ,
"org.apache.catalina.Container" );
结合上一段代码可以看出 Tomcat 启动时,如果碰到 server.xml 里的 Server/Service/Engine 节点,先实例化一个
org.apache.catalina.core.StandardEngine
对象,在第 11 到 13 行,会以 StandardEngine 对象为入参调用
org.apache.catalina.core.StandardService
的 setContainer 方法。
所以上面 connector.getService().getContainer() 方法得到的实际上是 StandardEngine 对象。紧接着的 getPipeline 方法返回的是 StandardEngine 类的父类
org.apache.catalina.core.ContainerBase
类的成员变量 pipeline ,看下该类中这个变量的声明代码:
/**
* The Pipeline object with which this Container is associated.
*/
protected Pipeline pipeline = new StandardPipeline(this);
所以 connector.getService().getContainer().getPipeline() 方法返回的是
org.apache.catalina.core.StandardPipeline
类的对象,该对象就是本部分开头部分提到的管道( Pipeline )。
下面讲一下 Tomcat 7 中的管道和阀的概念和实现:
所有的管道类都会实现
org.apache.catalina.Pipeline
这个接口,看下这个接口中定义的方法:
Tomat 7 中一个管道包含多个阀( Valve ),这些阀共分为两类,一类叫基础阀(通过 getBasic、setBasic 方法调用),一类是普通阀(通过 addValve、removeValve 调用)。管道都是包含在一个容器当中,所以 API 里还有 getContainer 和 setContainer 方法。一个管道一般有一个基础阀(通过 setBasic 添加),可以有 0 到多个普通阀(通过 addValve 添加)。
所有的阀类都会实现
org.apache.catalina.Valve
这个接口,看下这个接口中定义的方法:
重点关注 setNext、getNext、invoke 这三个方法,通过setNext设置该阀的下一阀,通过 getNext 返回该阀的下一个阀的引用,invoke 方法则执行该阀内部自定义的请求处理代码。
Tomcat 7 里 Pipeline 的默认实现类是
org.apache.catalina.core.StandardPipeline
,其内部有三个成员变量:basic、first、container 。
/**
* The basic Valve (if any) associated with this Pipeline.
*/
protected Valve basic = null;
/**
* The Container with which this Pipeline is associated.
*/
protected Container container = null;
/**
* The first valve associated with this Pipeline.
*/
protected Valve first = null;
看下该类的 addValve 方法:
1 public void addValve(Valve valve) {
2
3 // Validate that we can add this Valve
4 if (valve instanceof Contained)
5 ((Contained) valve).setContainer(this.container);
6
7 // Start the new component if necessary
8 if (getState().isAvailable()) {
9 if (valve instanceof Lifecycle) {
10 try {
11 ((Lifecycle) valve).start();
12 } catch (LifecycleException e) {
13 log.error("StandardPipeline.addValve: start: " , e);
14 }
15 }
16 }
17
18 // Add this Valve to the set associated with this Pipeline
19 if (first == null) {
20 first = valve;
21 valve.setNext(basic);
22 } else {
23 Valve current = first;
24 while (current != null) {
25 if (current.getNext() == basic) {
26 current.setNext(valve);
27 valve.setNext(basic);
28 break ;
29 }
30 current = current.getNext();
31 }
32 }
33
34 container.fireContainerEvent(Container.ADD_VALVE_EVENT, valve);
35 }
在第 18 到 32 行,每次给管道添加一个普通阀的时候如果管道内原来没有普通阀则将新添加的阀作为该管道的成员变量 first 的引用,如果管道内已有普通阀,则把新加的阀加到所有普通阀链条末端,并且将该阀的下一个阀的引用设置为管道的基础阀。这样管道内的阀结构如下图所示: