上文分析到了org.apache.coyote.http11.AbstractHttp11Processor
類 process 方法,以解析請求頭的 getInputBuffer().parseRequestLine 方法呼叫為例,看到如何從 Socket 的 IO 流中取出位元組流資料,根據 Http 協議將位元組流組裝到 Tomcat 內部的org.apache.coyote.Request
物件的相關屬性中。
接下來將會解釋構造好的 Tomcat 的內部請求物件從 Connector 到 Engine 到 Host 到 Context 最後到 Servlet 的過程。
回到org.apache.coyote.http11.AbstractHttp11Processor
類 process 方法的原始碼:
1 public SocketState process(SocketWrapper<S> socketWrapper)
2 throws IOException {
3 RequestInfo rp = request.getRequestProcessor();
4 rp.setStage(org.apache.coyote.Constants.STAGE_PARSE);
5
6 // Setting up the I/O
7 setSocketWrapper(socketWrapper);
8 getInputBuffer().init(socketWrapper, endpoint);
9 getOutputBuffer().init(socketWrapper, endpoint);
10
11 // Flags
12 error = false;
13 keepAlive = true;
14 comet = false;
15 openSocket = false;
16 sendfileInProgress = false;
17 readComplete = true;
18 if (endpoint.getUsePolling()) {
19 keptAlive = false;
20 } else {
21 keptAlive = socketWrapper.isKeptAlive();
22 }
23
24 if (disableKeepAlive()) {
25 socketWrapper.setKeepAliveLeft(0);
26 }
27
28 while (!error && keepAlive && !comet && !isAsync() &&
29 upgradeInbound == null && !endpoint.isPaused()) {
30
31 // Parsing the request header
32 try {
33 setRequestLineReadTimeout();
34
35 if (!getInputBuffer().parseRequestLine(keptAlive)) {
36 if (handleIncompleteRequestLineRead()) {
37 break;
38 }
39 }
40
41 if (endpoint.isPaused()) {
42 // 503 - Service unavailable
43 response.setStatus(503);
44 error = true;
45 } else {
46 // Make sure that connectors that are non-blocking during
47 // header processing (NIO) only set the start time the first
48 // time a request is processed.
49 if (request.getStartTime() < 0) {
50 request.setStartTime(System.currentTimeMillis());
51 }
52 keptAlive = true;
53 // Set this every time in case limit has been changed via JMX
54 request.getMimeHeaders().setLimit(endpoint.getMaxHeaderCount());
55 // Currently only NIO will ever return false here
56 if (!getInputBuffer().parseHeaders()) {
57 // We've read part of the request, don't recycle it
58 // instead associate it with the socket
59 openSocket = true;
60 readComplete = false;
61 break;
62 }
63 if (!disableUploadTimeout) {
64 setSocketTimeout(connectionUploadTimeout);
65 }
66 }
67 } catch (IOException e) {
68 if (getLog().isDebugEnabled()) {
69 getLog().debug(
70 sm.getString("http11processor.header.parse"), e);
71 }
72 error = true;
73 break;
74 } catch (Throwable t) {
75 ExceptionUtils.handleThrowable(t);
76 UserDataHelper.Mode logMode = userDataHelper.getNextMode();
77 if (logMode != null) {
78 String message = sm.getString(
79 "http11processor.header.parse");
80 switch (logMode) {
81 case INFO_THEN_DEBUG:
82 message += sm.getString(
83 "http11processor.fallToDebug");
84 //$FALL-THROUGH$
85 case INFO:
86 getLog().info(message);
87 break;
88 case DEBUG:
89 getLog().debug(message);
90 }
91 }
92 // 400 - Bad Request
93 response.setStatus(400);
94 adapter.log(request, response, 0);
95 error = true;
96 }
97
98 if (!error) {
99 // Setting up filters, and parse some request headers
100 rp.setStage(org.apache.coyote.Constants.STAGE_PREPARE);
101 try {
102 prepareRequest();
103 } catch (Throwable t) {
104 ExceptionUtils.handleThrowable(t);
105 if (getLog().isDebugEnabled()) {
106 getLog().debug(sm.getString(
107 "http11processor.request.prepare"), t);
108 }
109 // 400 - Internal Server Error
110 response.setStatus(400);
111 adapter.log(request, response, 0);
112 error = true;
113 }
114 }
115
116 if (maxKeepAliveRequests == 1) {
117 keepAlive = false;
118 } else if (maxKeepAliveRequests > 0 &&
119 socketWrapper.decrementKeepAlive() <= 0) {
120 keepAlive = false;
121 }
122
123 // Process the request in the adapter
124 if (!error) {
125 try {
126 rp.setStage(org.apache.coyote.Constants.STAGE_SERVICE);
127 adapter.service(request, response);
128 // Handle when the response was committed before a serious
129 // error occurred. Throwing a ServletException should both
130 // set the status to 500 and set the errorException.
131 // If we fail here, then the response is likely already
132 // committed, so we can't try and set headers.
133 if(keepAlive && !error) { // Avoid checking twice.
134 error = response.getErrorException() != null ||
135 (!isAsync() &&
136 statusDropsConnection(response.getStatus()));
137 }
138 setCometTimeouts(socketWrapper);
139 } catch (InterruptedIOException e) {
140 error = true;
141 } catch (HeadersTooLargeException e) {
142 error = true;
143 // The response should not have been committed but check it
144 // anyway to be safe
145 if (!response.isCommitted()) {
146 response.reset();
147 response.setStatus(500);
148 response.setHeader("Connection", "close");
149 }
150 } catch (Throwable t) {
151 ExceptionUtils.handleThrowable(t);
152 getLog().error(sm.getString(
153 "http11processor.request.process"), t);
154 // 500 - Internal Server Error
155 response.setStatus(500);
156 adapter.log(request, response, 0);
157 error = true;
158 }
159 }
160
161 // Finish the handling of the request
162 rp.setStage(org.apache.coyote.Constants.STAGE_ENDINPUT);
163
164 if (!isAsync() && !comet) {
165 if (error) {
166 // If we know we are closing the connection, don't drain
167 // input. This way uploading a 100GB file doesn't tie up the
168 // thread if the servlet has rejected it.
169 getInputBuffer().setSwallowInput(false);
170 }
171 endRequest();
172 }
173
174 rp.setStage(org.apache.coyote.Constants.STAGE_ENDOUTPUT);
175
176 // If there was an error, make sure the request is counted as
177 // and error, and update the statistics counter
178 if (error) {
179 response.setStatus(500);
180 }
181 request.updateCounters();
182
183 if (!isAsync() && !comet || error) {
184 getInputBuffer().nextRequest();
185 getOutputBuffer().nextRequest();
186 }
187
188 if (!disableUploadTimeout) {
189 if(endpoint.getSoTimeout() > 0) {
190 setSocketTimeout(endpoint.getSoTimeout());
191 } else {
192 setSocketTimeout(0);
193 }
194 }
195
196 rp.setStage(org.apache.coyote.Constants.STAGE_KEEPALIVE);
197
198 if (breakKeepAliveLoop(socketWrapper)) {
199 break;
200 }
201 }
202
203 rp.setStage(org.apache.coyote.Constants.STAGE_ENDED);
204
205 if (error || endpoint.isPaused()) {
206 return SocketState.CLOSED;
207 } else if (isAsync() || comet) {
208 return SocketState.LONG;
209 } else if (isUpgrade()) {
210 return SocketState.UPGRADING;
211 } else {
212 if (sendfileInProgress) {
213 return SocketState.SENDFILE;
214 } else {
215 if (openSocket) {
216 if (readComplete) {
217 return SocketState.OPEN;
218 } else {
219 return SocketState.LONG;
220 }
221 } else {
222 return SocketState.CLOSED;
223 }
224 }
225 }
226 }
複製程式碼
概述一下這個方法做的事情:第 3 到 26 行主要是在初始化變數。關注接下來一大段的 while 迴圈裡面的程式碼,第 31 到 121 行在解析請求頭,第 123 到 159 行將請求交由介面卡( adapter )處理,第 161 到 200 行結束請求的處理(做一些收尾工作,比如廢棄剩下的無意義位元組流資料,設定相應狀態碼等)。
請求物件在容器中的流轉在第 127 行:
adapter.service(request, response);
複製程式碼
這裡的 adapter 物件是在 Http11Processor 物件建立的時候設定的,見org.apache.coyote.http11.Http11Protocol.Http11ConnectionHandler
類的 createProcessor 方法:
1 protected Http11Processor createProcessor() {
2 Http11Processor processor = new Http11Processor(
3 proto.getMaxHttpHeaderSize(), (JIoEndpoint)proto.endpoint,
4 proto.getMaxTrailerSize());
5 processor.setAdapter(proto.adapter);
6 processor.setMaxKeepAliveRequests(proto.getMaxKeepAliveRequests());
7 processor.setKeepAliveTimeout(proto.getKeepAliveTimeout());
8 processor.setConnectionUploadTimeout(
9 proto.getConnectionUploadTimeout());
10 processor.setDisableUploadTimeout(proto.getDisableUploadTimeout());
11 processor.setCompressionMinSize(proto.getCompressionMinSize());
12 processor.setCompression(proto.getCompression());
13 processor.setNoCompressionUserAgents(proto.getNoCompressionUserAgents());
14 processor.setCompressableMimeTypes(proto.getCompressableMimeTypes());
15 processor.setRestrictedUserAgents(proto.getRestrictedUserAgents());
16 processor.setSocketBuffer(proto.getSocketBuffer());
17 processor.setMaxSavePostSize(proto.getMaxSavePostSize());
18 processor.setServer(proto.getServer());
19 processor.setDisableKeepAlivePercentage(
20 proto.getDisableKeepAlivePercentage());
21 register(processor);
22 return processor;
23 } }
複製程式碼
可以看到 adapter 物件設定的是org.apache.coyote.http11.Http11Protocol
的 adapter 變數,而該變數是在 Connector 類的 initInternal 方法中設值的:
1 protected void initInternal() throws LifecycleException {
2
3 super.initInternal();
4
5 // Initialize adapter
6 adapter = new CoyoteAdapter(this);
7 protocolHandler.setAdapter(adapter);
8
9 // Make sure parseBodyMethodsSet has a default
10 if( null == parseBodyMethodsSet ) {
11 setParseBodyMethods(getParseBodyMethods());
12 }
13
14 if (protocolHandler.isAprRequired() &&
15 !AprLifecycleListener.isAprAvailable()) {
16 throw new LifecycleException(
17 sm.getString("coyoteConnector.protocolHandlerNoApr",
18 getProtocolHandlerClassName()));
19 }
20
21 try {
22 protocolHandler.init();
23 } catch (Exception e) {
24 throw new LifecycleException
25 (sm.getString
26 ("coyoteConnector.protocolHandlerInitializationFailed"), e);
27 }
28
29 // Initialize mapper listener
30 mapperListener.init();
31 }
複製程式碼
第 6、7 行就是初始化 adapter 物件並設值到 Http11Protocol 物件中的。
所以上面看到的adapter.service(request, response)
方法實際執行的是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 }
複製程式碼
這段程式碼中可以看到入參org.apache.coyote.Request
物件被轉成了org.apache.catalina.connector.Request
物件,後一型別的物件才是在 Tomcat 容器流轉時真正傳遞的物件。重點關注第 42 行和第 47 行。
在第 42 行呼叫了 postParseRequest 方法:
1 /**
2 * Parse additional request parameters.
3 */
4 protected boolean postParseRequest(org.apache.coyote.Request req,
5 Request request,
6 org.apache.coyote.Response res,
7 Response response)
8 throws Exception {
9
10 // XXX the processor may have set a correct scheme and port prior to this point,
11 // in ajp13 protocols dont make sense to get the port from the connector...
12 // otherwise, use connector configuration
13 if (! req.scheme().isNull()) {
14 // use processor specified scheme to determine secure state
15 request.setSecure(req.scheme().equals("https"));
16 } else {
17 // use connector scheme and secure configuration, (defaults to
18 // "http" and false respectively)
19 req.scheme().setString(connector.getScheme());
20 request.setSecure(connector.getSecure());
21 }
22
23 // FIXME: the code below doesnt belongs to here,
24 // this is only have sense
25 // in Http11, not in ajp13..
26 // At this point the Host header has been processed.
27 // Override if the proxyPort/proxyHost are set
28 String proxyName = connector.getProxyName();
29 int proxyPort = connector.getProxyPort();
30 if (proxyPort != 0) {
31 req.setServerPort(proxyPort);
32 }
33 if (proxyName != null) {
34 req.serverName().setString(proxyName);
35 }
36
37 // Copy the raw URI to the decodedURI
38 MessageBytes decodedURI = req.decodedURI();
39 decodedURI.duplicate(req.requestURI());
40
41 // Parse the path parameters. This will:
42 // - strip out the path parameters
43 // - convert the decodedURI to bytes
44 parsePathParameters(req, request);
45
46 // URI decoding
47 // %xx decoding of the URL
48 try {
49 req.getURLDecoder().convert(decodedURI, false);
50 } catch (IOException ioe) {
51 res.setStatus(400);
52 res.setMessage("Invalid URI: " + ioe.getMessage());
53 connector.getService().getContainer().logAccess(
54 request, response, 0, true);
55 return false;
56 }
57 // Normalization
58 if (!normalize(req.decodedURI())) {
59 res.setStatus(400);
60 res.setMessage("Invalid URI");
61 connector.getService().getContainer().logAccess(
62 request, response, 0, true);
63 return false;
64 }
65 // Character decoding
66 convertURI(decodedURI, request);
67 // Check that the URI is still normalized
68 if (!checkNormalize(req.decodedURI())) {
69 res.setStatus(400);
70 res.setMessage("Invalid URI character encoding");
71 connector.getService().getContainer().logAccess(
72 request, response, 0, true);
73 return false;
74 }
75
76 // Set the remote principal
77 String principal = req.getRemoteUser().toString();
78 if (principal != null) {
79 request.setUserPrincipal(new CoyotePrincipal(principal));
80 }
81
82 // Set the authorization type
83 String authtype = req.getAuthType().toString();
84 if (authtype != null) {
85 request.setAuthType(authtype);
86 }
87
88 // Request mapping.
89 MessageBytes serverName;
90 if (connector.getUseIPVHosts()) {
91 serverName = req.localName();
92 if (serverName.isNull()) {
93 // well, they did ask for it
94 res.action(ActionCode.REQ_LOCAL_NAME_ATTRIBUTE, null);
95 }
96 } else {
97 serverName = req.serverName();
98 }
99 if (request.isAsyncStarted()) {
100 //TODO SERVLET3 - async
101 //reset mapping data, should prolly be done elsewhere
102 request.getMappingData().recycle();
103 }
104
105 boolean mapRequired = true;
106 String version = null;
107
108 while (mapRequired) {
109 if (version != null) {
110 // Once we have a version - that is it
111 mapRequired = false;
112 }
113 // This will map the the latest version by default
114 connector.getMapper().map(serverName, decodedURI, version,
115 request.getMappingData());
116 request.setContext((Context) request.getMappingData().context);
117 request.setWrapper((Wrapper) request.getMappingData().wrapper);
118
119 // Single contextVersion therefore no possibility of remap
120 if (request.getMappingData().contexts == null) {
121 mapRequired = false;
122 }
123
124 // If there is no context at this point, it is likely no ROOT context
125 // has been deployed
126 if (request.getContext() == null) {
127 res.setStatus(404);
128 res.setMessage("Not found");
129 // No context, so use host
130 Host host = request.getHost();
131 // Make sure there is a host (might not be during shutdown)
132 if (host != null) {
133 host.logAccess(request, response, 0, true);
134 }
135 return false;
136 }
137
138 // Now we have the context, we can parse the session ID from the URL
139 // (if any). Need to do this before we redirect in case we need to
140 // include the session id in the redirect
141 String sessionID = null;
142 if (request.getServletContext().getEffectiveSessionTrackingModes()
143 .contains(SessionTrackingMode.URL)) {
144
145 // Get the session ID if there was one
146 sessionID = request.getPathParameter(
147 SessionConfig.getSessionUriParamName(
148 request.getContext()));
149 if (sessionID != null) {
150 request.setRequestedSessionId(sessionID);
151 request.setRequestedSessionURL(true);
152 }
153 }
154
155 // Look for session ID in cookies and SSL session
156 parseSessionCookiesId(req, request);
157 parseSessionSslId(request);
158
159 sessionID = request.getRequestedSessionId();
160
161 if (mapRequired) {
162 if (sessionID == null) {
163 // No session means no possibility of needing to remap
164 mapRequired = false;
165 } else {
166 // Find the context associated with the session
167 Object[] objs = request.getMappingData().contexts;
168 for (int i = (objs.length); i > 0; i--) {
169 Context ctxt = (Context) objs[i - 1];
170 if (ctxt.getManager().findSession(sessionID) != null) {
171 // Was the correct context already mapped?
172 if (ctxt.equals(request.getMappingData().context)) {
173 mapRequired = false;
174 } else {
175 // Set version so second time through mapping the
176 // correct context is found
177 version = ctxt.getWebappVersion();
178 // Reset mapping
179 request.getMappingData().recycle();
180 break;
181 }
182 }
183 }
184 if (version == null) {
185 // No matching context found. No need to re-map
186 mapRequired = false;
187 }
188 }
189 }
190 if (!mapRequired && request.getContext().getPaused()) {
191 // Found a matching context but it is paused. Mapping data will
192 // be wrong since some Wrappers may not be registered at this
193 // point.
194 try {
195 Thread.sleep(1000);
196 } catch (InterruptedException e) {
197 // Should never happen
198 }
199 // Reset mapping
200 request.getMappingData().recycle();
201 mapRequired = true;
202 }
203 }
204
205 // Possible redirect
206 MessageBytes redirectPathMB = request.getMappingData().redirectPath;
207 if (!redirectPathMB.isNull()) {
208 String redirectPath = urlEncoder.encode(redirectPathMB.toString());
209 String query = request.getQueryString();
210 if (request.isRequestedSessionIdFromURL()) {
211 // This is not optimal, but as this is not very common, it
212 // shouldn't matter
213 redirectPath = redirectPath + ";" +
214 SessionConfig.getSessionUriParamName(
215 request.getContext()) +
216 "=" + request.getRequestedSessionId();
217 }
218 if (query != null) {
219 // This is not optimal, but as this is not very common, it
220 // shouldn't matter
221 redirectPath = redirectPath + "?" + query;
222 }
223 response.sendRedirect(redirectPath);
224 request.getContext().logAccess(request, response, 0, true);
225 return false;
226 }
227
228 // Filter trace method
229 if (!connector.getAllowTrace()
230 && req.method().equalsIgnoreCase("TRACE")) {
231 Wrapper wrapper = request.getWrapper();
232 String header = null;
233 if (wrapper != null) {
234 String[] methods = wrapper.getServletMethods();
235 if (methods != null) {
236 for (int i=0; i<methods.length; i++) {
237 if ("TRACE".equals(methods[i])) {
238 continue;
239 }
240 if (header == null) {
241 header = methods[i];
242 } else {
243 header += ", " + methods[i];
244 }
245 }
246 }
247 }
248 res.setStatus(405);
249 res.addHeader("Allow", header);
250 res.setMessage("TRACE method is not allowed");
251 request.getContext().logAccess(request, response, 0, true);
252 return false;
253 }
254
255 return true;
256 }
複製程式碼
這段程式碼的主要作用是給org.apache.catalina.connector.Request
物件設值,其中第 113 到 117 行:
// This will map the the latest version by default
connector.getMapper().map(serverName, decodedURI, version,
request.getMappingData());
request.setContext((Context) request.getMappingData().context);
request.setWrapper((Wrapper) request.getMappingData().wrapper);
複製程式碼
看下 map 方法的程式碼,注意該方法的最後一個入參是 request.getMappingData() :
public void map(MessageBytes host, MessageBytes uri, String version,
MappingData mappingData)
throws Exception {
if (host.isNull()) {
host.getCharChunk().append(defaultHostName);
}
host.toChars();
uri.toChars();
internalMap(host.getCharChunk(), uri.getCharChunk(), version,
mappingData);
}
複製程式碼
可以看到這裡最後呼叫了org.apache.tomcat.util.http.mapper.Mapper
類的 internalMap 方法,並且該方法最後一個入參實際上是上一段程式碼提到的 request.getMappingData() 。看下 internalMap 方法裡面做了些什麼:
1 /**
2 * Map the specified URI.
3 */
4 private final void internalMap(CharChunk host, CharChunk uri,
5 String version, MappingData mappingData) throws Exception {
6
7 uri.setLimit(-1);
8
9 Context[] contexts = null;
10 Context context = null;
11 ContextVersion contextVersion = null;
12
13 int nesting = 0;
14
15 // Virtual host mapping
16 if (mappingData.host == null) {
17 Host[] hosts = this.hosts;
18 int pos = findIgnoreCase(hosts, host);
19 if ((pos != -1) && (host.equalsIgnoreCase(hosts[pos].name))) {
20 mappingData.host = hosts[pos].object;
21 contexts = hosts[pos].contextList.contexts;
22 nesting = hosts[pos].contextList.nesting;
23 } else {
24 if (defaultHostName == null) {
25 return;
26 }
27 pos = find(hosts, defaultHostName);
28 if ((pos != -1) && (defaultHostName.equals(hosts[pos].name))) {
29 mappingData.host = hosts[pos].object;
30 contexts = hosts[pos].contextList.contexts;
31 nesting = hosts[pos].contextList.nesting;
32 } else {
33 return;
34 }
35 }
36 }
37
38 // Context mapping
39 if (mappingData.context == null) {
40 int pos = find(contexts, uri);
41 if (pos == -1) {
42 return;
43 }
44
45 int lastSlash = -1;
46 int uriEnd = uri.getEnd();
47 int length = -1;
48 boolean found = false;
49 while (pos >= 0) {
50 if (uri.startsWith(contexts[pos].name)) {
51 length = contexts[pos].name.length();
52 if (uri.getLength() == length) {
53 found = true;
54 break;
55 } else if (uri.startsWithIgnoreCase("/", length)) {
56 found = true;
57 break;
58 }
59 }
60 if (lastSlash == -1) {
61 lastSlash = nthSlash(uri, nesting + 1);
62 } else {
63 lastSlash = lastSlash(uri);
64 }
65 uri.setEnd(lastSlash);
66 pos = find(contexts, uri);
67 }
68 uri.setEnd(uriEnd);
69
70 if (!found) {
71 if (contexts[0].name.equals("")) {
72 context = contexts[0];
73 }
74 } else {
75 context = contexts[pos];
76 }
77 if (context != null) {
78 mappingData.contextPath.setString(context.name);
79 }
80 }
81
82 if (context != null) {
83 ContextVersion[] contextVersions = context.versions;
84 int versionCount = contextVersions.length;
85 if (versionCount > 1) {
86 Object[] contextObjects = new Object[contextVersions.length];
87 for (int i = 0; i < contextObjects.length; i++) {
88 contextObjects[i] = contextVersions[i].object;
89 }
90 mappingData.contexts = contextObjects;
91 }
92
93 if (version == null) {
94 // Return the latest version
95 contextVersion = contextVersions[versionCount - 1];
96 } else {
97 int pos = find(contextVersions, version);
98 if (pos < 0 || !contextVersions[pos].name.equals(version)) {
99 // Return the latest version
100 contextVersion = contextVersions[versionCount - 1];
101 } else {
102 contextVersion = contextVersions[pos];
103 }
104 }
105 mappingData.context = contextVersion.object;
106 }
107
108 // Wrapper mapping
109 if ((contextVersion != null) && (mappingData.wrapper == null)) {
110 internalMapWrapper(contextVersion, uri, mappingData);
111 }
112
113 }
複製程式碼
說白了就是給該方法的入參 mappingData 的幾個例項變數設定值,比如 mappingData.host、mappingData.contextPath、mappingData.contexts、mappingData.wrapper ,回到上一段提到的 mappingData 變數實際上是org.apache.catalina.connector.Request
物件內建變數 mappingData 。回到上面提到的要重點關注的org.apache.catalina.connector.CoyoteAdapter
的postParseRequest 方法的 114 到 117行:
connector.getMapper().map(serverName, decodedURI, version,
request.getMappingData());
request.setContext((Context) request.getMappingData().context);
request.setWrapper((Wrapper) request.getMappingData().wrapper);
複製程式碼
上面之所以不厭其煩的把實現程式碼貼出來就是為了能夠看清楚這三行程式碼的具體含義,即通過 map 方法的呼叫設定 request 的成員變數 mappingData 的成員變數 host、context、warp 資訊,接著從 mappingData 中取出 context 和 wrapper ,直接設定到 request 物件的成員變數 context、wrapper 中。下圖是上面所描述的關鍵程式碼呼叫過程的時序圖:
本文不再仔細分析 host、context、warp 的匹配過程,請讀者自己閱讀org.apache.tomcat.util.http.mapper.Mapper
類原始碼,這裡大致說下其匹配原理,在 org.apache.tomcat.util.http.mapper.Mapper
類中有幾個內部類 Host、Context、Wrapper,Mapper 類內部分別有這幾種型別的成員變數,在 Tomcat 容器啟動的時候會呼叫org.apache.catalina.connector.Connector
類的 startInternal 方法(具體啟動過程分析參見前文),該方法最後一行:
mapperListener.start();
複製程式碼
這裡將會呼叫org.apache.catalina.connector.MapperListener
類的 startInternal 方法:
1 public void startInternal() throws LifecycleException {
2
3 setState(LifecycleState.STARTING);
4
5 // Find any components that have already been initialized since the
6 // MBean listener won't be notified as those components will have
7 // already registered their MBeans
8 findDefaultHost();
9
10 Engine engine = (Engine) connector.getService().getContainer();
11 addListeners(engine);
12
13 Container[] conHosts = engine.findChildren();
14 for (Container conHost : conHosts) {
15 Host host = (Host) conHost;
16 if (!LifecycleState.NEW.equals(host.getState())) {
17 // Registering the host will register the context and wrappers
18 registerHost(host);
19 }
20 }
21 }
複製程式碼
在第 18 行呼叫當前類的 registerHost 方法:
1 private void registerHost(Host host) {
2
3 String[] aliases = host.findAliases();
4 mapper.addHost(host.getName(), aliases, host);
5
6 for (Container container : host.findChildren()) {
7 if (container.getState().isAvailable()) {
8 registerContext((Context) container);
9 }
10 }
11 if(log.isDebugEnabled()) {
12 log.debug(sm.getString("mapperListener.registerHost",
13 host.getName(), domain, connector));
14 }
15 }
複製程式碼
第 8 行在 registerHost 方法中會呼叫 registerContext 方法,在 registerContext 方法中會呼叫 registerWrapper 方法。第4行看到呼叫了上述 mapper 物件的 addHost 方法,在 registerContext 方法中會呼叫 mapper 物件的 mapper.addContextVersion 方法,在 registerWrapper 方法中會呼叫 mapper 物件的 mapper.addWrapper 方法。
所以在 Tomcat 容器啟動過程中會將在用的 Host、Context、Wrapper 元件同時維護到與一個 Connector 相關的 Mapper 物件裡,這樣才會在容器接收到一次請求的時候可以根據請求的URL等資訊匹配到具體的 host、context、wrapper 。
本文中提到的 wrapper 實際上是 Tomcat 容器內部對於 Servlet 的封裝,可以認為是一對一的關係。看下 Tomcat 容器的元件結構圖:
在 Service 內只有一個 Engine ,但可能有多個 Connector ,在 Engine 內部 Engine 和 Host ,Host 和 Context,Context 和 Wrapper 都是一對多的關係。但瀏覽器發出一次請求連線並不需要也不可能讓部署在 Tomcat 中的所有 Web 應用的所有 Servlet 類都執行一遍,本文所說的 Map 機制就是為了 Connector 在接收到一次 Socket 連線時轉化成請求後,能夠找到 Engine 下具體哪個 Host、哪個 Context、哪個 Wrapper來執行這個請求。