Tomcat一次请求处理的前世今生 (三) 请求与容器具体组件的匹配

前一篇文章分析到了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方法的源码: 

Java代码   收藏代码
  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行:

Java代码   收藏代码
  1. adapter.service(request, response);  

这里的adapter对象是在Http11Processor对象创建的时候设置的,见org.apache.coyote.http11.Http11Protocol.Http11ConnectionHandler类的createProcessor方法:

Java代码   收藏代码
  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方法中设值的:

Java代码   收藏代码
  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.     ifnull == 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方法: 

Java代码   收藏代码
  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方法:

Java代码   收藏代码
  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, 0true);  
  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, 0true);  
  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, 0true);  
  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, 0true);  
  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, 0true);  
  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, 0true);  
  252.         return false;  
  253.     }  
  254.   
  255.     return true;  
  256. }  

这段代码的主要作用是给org.apache.catalina.connector.Request对象设值,其中第113到117行:

Java代码   收藏代码
  1. // This will map the the latest version by default  
  2. connector.getMapper().map(serverName, decodedURI, version,  
  3.                           request.getMappingData());  
  4. request.setContext((Context) request.getMappingData().context);  
  5. request.setWrapper((Wrapper) request.getMappingData().wrapper);  

看下map方法的代码,注意该方法的最后一个入参是request.getMappingData():

Java代码   收藏代码
  1. public void map(MessageBytes host, MessageBytes uri, String version,  
  2.                 MappingData mappingData)  
  3.     throws Exception {  
  4.   
  5.     if (host.isNull()) {  
  6.         host.getCharChunk().append(defaultHostName);  
  7.     }  
  8.     host.toChars();  
  9.     uri.toChars();  
  10.     internalMap(host.getCharChunk(), uri.getCharChunk(), version,  
  11.             mappingData);  
  12.   
  13. }  

可以看到这里最后调用了org.apache.tomcat.util.http.mapper.Mapper类的internalMap方法,并且该方法最后一个入参实际上是上一段代码提到的request.getMappingData()。看下internalMap方法里面做了些什么:

Java代码   收藏代码
  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行:

Java代码   收藏代码
  1. connector.getMapper().map(serverName, decodedURI, version,  
  2.                           request.getMappingData());  
  3. request.setContext((Context) request.getMappingData().context);  
  4. 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方法(具体启动过程分析参见前文),该方法最后一行:

Java代码   收藏代码
  1. mapperListener.start();  

这里将会调用org.apache.catalina.connector.MapperListener类的startInternal方法:

Java代码   收藏代码
  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方法:

Java代码   收藏代码
  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来执行这个请求。下一篇文章会看到容器是如果一步一步让请求在与本次请求相关的组件中流转的。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值