Tomcat 7 的一次请求分析(三)请求与容器中具体组件的匹配

上文分析到了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 中。下图是上面所描述的关键代码调用过程的时序图:

![]https://user-gold-cdn.xitu.io/2018/1/31/1614b9087ade3e55?imageView2/0/w/1280/h/960/format/jpeg/ignore-error/1)

本文不再仔细分析 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来执行这个请求。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值