經過這一系列的前三部分看到了一次客戶端鏈接在 Tomcat 內部被轉換成了請求對象(org.apache.catalina.connector.Request
類的實例),並在該請求對象內部將與本次請求相關的 Host、Context、Wrapper 對象的引用。本文主要分析該請求對象在容器內部流轉的通過。apache
再來看一下 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 方法:bash
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 組件的引用。session
看下第 47 行:app
connector.getService().getContainer().getPipeline().getFirst().invoke(request, response);
複製代碼
雖然只有一行,但調用了一堆方法,這裏對這些方法逐個分析一下:jvm
connector.getService() 獲取的是當前 connector 關聯的 Service 組件,默認狀況下得到的就是org.apache.catalina.core.StandardService
的對象。其 getContainer 方法得到的是org.apache.catalina.core.StandardEngine
的對象,這段的由來在前面講 Digester 的解析文章時,createStartDigester 方法中的這段代碼:jsp
digester.addRuleSet(new EngineRuleSet("Server/Service/"));
複製代碼
在 EngineRuleSet 類的 addRuleInstances 方法中的這一段代碼:async
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 ,看下該類中這個變量的聲明代碼:oop
/**
* 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
這個接口,看下這個接口中定義的方法:
全部的閥類都會實現org.apache.catalina.Valve
這個接口,看下這個接口中定義的方法:
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 的引用,若是管道內已有普通閥,則把新加的閥加到全部普通閥鏈條末端,而且將該閥的下一個閥的引用設置爲管道的基礎閥。這樣管道內的閥結構以下圖所示:
即 Pipeline 內部維護 first 和 basic 兩個閥,其它相關閥經過 getNext 來獲取。看下 getFirst 方法的實現:
public Valve getFirst() {
if (first != null) {
return first;
}
return basic;
}
複製代碼
若是管道中有普通閥則返回普通閥鏈條最開始的那個,不然就返回基礎閥。
在 Tomcat 7 中全部做爲普通閥的類的 invoke 方法實現中都會有這段代碼:
getNext().invoke(request, response);
複製代碼
經過這種機制來保證調用管道最開頭一端的閥的 invoke 方法,最終會執行完該管道相關的全部閥的 invoke 方法,而且最後執行的一定是該管道基礎閥的 invoke 方法。
再回到connector.getService().getContainer().getPipeline().getFirst().invoke(request, response)
這段代碼的解釋,這裏將會執行 StandardEngine 類的管道中的全部閥(包括普通閥和基礎閥)的 invoke 方法,而且最後會執行基礎閥的 invoke 方法。
Tomcat 7 在默認狀況下 Engine 節點沒有普通閥,若是想要添加普通閥的話,能夠經過在 server.xml 文件的 engine 節點下添加 Valve 節點,參加該文件中的普通閥配置的示例:
<Valve className="org.apache.catalina.authenticator.SingleSignOn" />
複製代碼
那麼就來看看 StandardEngine 類的管道中的基礎閥的代碼實現。先看下該基礎閥設置的代碼,在org.apache.catalina.core.StandardEngine
對象的構造函數中:
1 public StandardEngine() {
2
3 super();
4 pipeline.setBasic(new StandardEngineValve());
5 /* Set the jmvRoute using the system property jvmRoute */
6 try {
7 setJvmRoute(System.getProperty("jvmRoute"));
8 } catch(Exception ex) {
9 log.warn(sm.getString("standardEngine.jvmRouteFail"));
10 }
11 // By default, the engine will hold the reloading thread
12 backgroundProcessorDelay = 10;
13
14 }
複製代碼
第 4 行即設置基礎閥。因此connector.getService().getContainer().getPipeline().getFirst().invoke(request, response)
會執行到 org.apache.catalina.core.StandardEngineValve 類的 invoke 方法:
1 public final void invoke(Request request, Response response)
2 throws IOException, ServletException {
3
4 // Select the Host to be used for this Request
5 Host host = request.getHost();
6 if (host == null) {
7 response.sendError
8 (HttpServletResponse.SC_BAD_REQUEST,
9 sm.getString("standardEngine.noHost",
10 request.getServerName()));
11 return;
12 }
13 if (request.isAsyncSupported()) {
14 request.setAsyncSupported(host.getPipeline().isAsyncSupported());
15 }
16
17 // Ask this Host to process this request
18 host.getPipeline().getFirst().invoke(request, response);
19
20 }
複製代碼
第 5 行,從請求對象中取出該請求關聯的 Host(默認狀況下是org.apache.catalina.core.StandardHost
對象),請求是如何找到關聯的 Host 的請本文以前的部分。通過上述代碼分析應該能夠看出第 18 行會執行 StandardHost 對象的管道內全部的閥的 invoke 方法。
看下 StandardHost 的構造方法的實現:
public StandardHost() {
super();
pipeline.setBasic(new StandardHostValve());
}
複製代碼
因此看下org.apache.catalina.core.StandardHostValve
類的 invoke 方法:
1 public final void invoke(Request request, Response response)
2 throws IOException, ServletException {
3
4 // Select the Context to be used for this Request
5 Context context = request.getContext();
6 if (context == null) {
7 response.sendError
8 (HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
9 sm.getString("standardHost.noContext"));
10 return;
11 }
12
13 // Bind the context CL to the current thread
14 if( context.getLoader() != null ) {
15 // Not started - it should check for availability first
16 // This should eventually move to Engine, it's generic.
17 if (Globals.IS_SECURITY_ENABLED) {
18 PrivilegedAction pa = new PrivilegedSetTccl(
19 context.getLoader().getClassLoader());
20 AccessController.doPrivileged(pa);
21 } else {
22 Thread.currentThread().setContextClassLoader
23 (context.getLoader().getClassLoader());
24 }
25 }
26 if (request.isAsyncSupported()) {
27 request.setAsyncSupported(context.getPipeline().isAsyncSupported());
28 }
29
30 // Don't fire listeners during async processing
31 // If a request init listener throws an exception, the request is
32 // aborted
33 boolean asyncAtStart = request.isAsync();
34 // An async error page may dispatch to another resource. This flag helps
35 // ensure an infinite error handling loop is not entered
36 boolean errorAtStart = response.isError();
37 if (asyncAtStart || context.fireRequestInitEvent(request)) {
38
39 // Ask this Context to process this request
40 try {
41 context.getPipeline().getFirst().invoke(request, response);
42 } catch (Throwable t) {
43 ExceptionUtils.handleThrowable(t);
44 if (errorAtStart) {
45 container.getLogger().error("Exception Processing " +
46 request.getRequestURI(), t);
47 } else {
48 request.setAttribute(RequestDispatcher.ERROR_EXCEPTION, t);
49 throwable(request, response, t);
50 }
51 }
52
53 // If the request was async at the start and an error occurred then
54 // the async error handling will kick-in and that will fire the
55 // request destroyed event *after* the error handling has taken
56 // place
57 if (!(request.isAsync() || (asyncAtStart &&
58 request.getAttribute(
59 RequestDispatcher.ERROR_EXCEPTION) != null))) {
60 // Protect against NPEs if context was destroyed during a
61 // long running request.
62 if (context.getState().isAvailable()) {
63 if (!errorAtStart) {
64 // Error page processing
65 response.setSuspended(false);
66
67 Throwable t = (Throwable) request.getAttribute(
68 RequestDispatcher.ERROR_EXCEPTION);
69
70 if (t != null) {
71 throwable(request, response, t);
72 } else {
73 status(request, response);
74 }
75 }
76
77 context.fireRequestDestroyEvent(request);
78 }
79 }
80 }
81
82 // Access a session (if present) to update last accessed time, based on a
83 // strict interpretation of the specification
84 if (ACCESS_SESSION) {
85 request.getSession(false);
86 }
87
88 // Restore the context classloader
89 if (Globals.IS_SECURITY_ENABLED) {
90 PrivilegedAction pa = new PrivilegedSetTccl(
91 StandardHostValve.class.getClassLoader());
92 AccessController.doPrivileged(pa);
93 } else {
94 Thread.currentThread().setContextClassLoader
95 (StandardHostValve.class.getClassLoader());
96 }
97 }
複製代碼
第 41 行,會調用該請求相關的 Context 的管道內全部的閥的 invoke 方法,默認狀況下 Context 是org.apache.catalina.core.StandardContext
類的對象,其構造方法中設置了管道的基礎閥:
public StandardContext() {
super();
pipeline.setBasic(new StandardContextValve());
broadcaster = new NotificationBroadcasterSupport();
// Set defaults
if (!Globals.STRICT_SERVLET_COMPLIANCE) {
// Strict servlet compliance requires all extension mapped servlets
// to be checked against welcome files
resourceOnlyServlets.add("jsp");
}
}
複製代碼
看下其基礎閥的 invoke 方法代碼:
1 public final void invoke(Request request, Response response)
2 throws IOException, ServletException {
3
4 // Disallow any direct access to resources under WEB-INF or META-INF
5 MessageBytes requestPathMB = request.getRequestPathMB();
6 if ((requestPathMB.startsWithIgnoreCase("/META-INF/", 0))
7 || (requestPathMB.equalsIgnoreCase("/META-INF"))
8 || (requestPathMB.startsWithIgnoreCase("/WEB-INF/", 0))
9 || (requestPathMB.equalsIgnoreCase("/WEB-INF"))) {
10 response.sendError(HttpServletResponse.SC_NOT_FOUND);
11 return;
12 }
13
14 // Select the Wrapper to be used for this Request
15 Wrapper wrapper = request.getWrapper();
16 if (wrapper == null || wrapper.isUnavailable()) {
17 response.sendError(HttpServletResponse.SC_NOT_FOUND);
18 return;
19 }
20
21 // Acknowledge the request
22 try {
23 response.sendAcknowledgement();
24 } catch (IOException ioe) {
25 container.getLogger().error(sm.getString(
26 "standardContextValve.acknowledgeException"), ioe);
27 request.setAttribute(RequestDispatcher.ERROR_EXCEPTION, ioe);
28 response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
29 return;
30 }
31
32 if (request.isAsyncSupported()) {
33 request.setAsyncSupported(wrapper.getPipeline().isAsyncSupported());
34 }
35 wrapper.getPipeline().getFirst().invoke(request, response);
36 }
複製代碼
最後的第 35 行,從請求中取出關聯的 wrapper 對象後調用其管道內全部閥的 invoke 方法。wrapper 對象默認是org.apache.catalina.core.StandardWrapper
類的實例,一樣是在該類的構造方法中設置的基礎閥:
public StandardWrapper() {
super();
swValve=new StandardWrapperValve();
pipeline.setBasic(swValve);
broadcaster = new NotificationBroadcasterSupport();
}
複製代碼
有興趣能夠看下基礎閥org.apache.catalina.core.StandardWrapperValve
的 invoke 方法,在這裏最終會調用請求的 url 所匹配的 Servlet 相關過濾器( filter )的 doFilter 方法及該 Servlet 的 service 方法(這段實現都是在過濾器鏈 ApplicationFilterChain 類的 doFilter 方法中),這裏再也不貼出代碼分析。
這裏能夠看出容器內的 Engine、Host、Context、Wrapper 容器組件的實現的共通點:
這些組件內部都有一個成員變量 pipeline ,由於它們都是從org.apache.catalina.core.ContainerBase
類繼承來的,pipeline 就定義在這個類中。因此每個容器內部都關聯了一個管道。
都是在類的構造方法中設置管道內的基礎閥。
全部的基礎閥的實現最後都會調用其下一級容器(直接從請求中獲取下一級容器對象的引用,在上面的分析中已經設置了與該請求相關的各級具體組件的引用)的 getPipeline().getFirst().invoke() 方法,直到 Wrapper 組件。由於 Wrapper 是對一個 Servlet 的包裝,因此它的基礎閥內部調用的過濾器鏈的 doFilter 方法和 Servlet 的 service 方法。
正是經過這種管道和閥的機制及上述的 3 點前提,使得請求能夠從鏈接器內一步一步流轉到具體 Servlet 的 service 方法中。這樣,關於Tomcat 7 中一次請求的分析介紹完畢,從中能夠看出在瀏覽器發出一次 Socket 鏈接請求以後 Tomcat 容器內運轉處理的大體流程。