Tomcat 7 的一次請求分析(三)請求與容器中具體元件的匹配

預流發表於2018-02-01

上文分析到了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 中。下圖是上面所描述的關鍵程式碼呼叫過程的時序圖:

Tomcat 7 的一次請求分析(三)請求與容器中具體元件的匹配
本文不再仔細分析 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 容器的元件結構圖:

Tomcat 7 的一次請求分析(三)請求與容器中具體元件的匹配
在 Service 內只有一個 Engine ,但可能有多個 Connector ,在 Engine 內部 Engine 和 Host ,Host 和 Context,Context 和 Wrapper 都是一對多的關係。但瀏覽器發出一次請求連線並不需要也不可能讓部署在 Tomcat 中的所有 Web 應用的所有 Servlet 類都執行一遍,本文所說的 Map 機制就是為了 Connector 在接收到一次 Socket 連線時轉化成請求後,能夠找到 Engine 下具體哪個 Host、哪個 Context、哪個 Wrapper來執行這個請求。

相關文章