IDEA2023.1.3破解,IDEA破解,IDEA 2023.1破解,最新IDEA激活码

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

IDEA2023.1.3破解,IDEA破解,IDEA 2023.1破解,最新IDEA激活码

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

63_1.png本文不再仔细分析 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 容器的组件结构图:

63_2.png在 Service 内只有一个 Engine ,但可能有多个 Connector ,在 Engine 内部 Engine 和 Host ,Host 和 Context,Context 和 Wrapper 都是一对多的关系。但浏览器发出一次请求连接并不需要也不可能让部署在 Tomcat 中的所有 Web 应用的所有 Servlet 类都执行一遍,本文所说的 Map 机制就是为了 Connector 在接收到一次 Socket 连接时转化成请求后,能够找到 Engine 下具体哪个 Host、哪个 Context、哪个 Wrapper来执行这个请求。

文章永久链接:https://tech.souyunku.com/?p=35907


Warning: A non-numeric value encountered in /data/wangzhan/tech.souyunku.com.wp/wp-content/themes/dux/functions-theme.php on line 1154
赞(93) 打赏



未经允许不得转载:搜云库技术团队 » Tomcat 7 的一次请求分析(三)请求与容器中具体组件的匹配

IDEA2023.1.3破解,IDEA破解,IDEA 2023.1破解,最新IDEA激活码
IDEA2023.1.3破解,IDEA破解,IDEA 2023.1破解,最新IDEA激活码

评论 抢沙发

大前端WP主题 更专业 更方便

联系我们联系我们

觉得文章有用就打赏一下文章作者

微信扫一扫打赏

微信扫一扫打赏


Fatal error: Uncaught Exception: Cache directory not writable. Comet Cache needs this directory please: `/data/wangzhan/tech.souyunku.com.wp/wp-content/cache/comet-cache/cache/https/tech-souyunku-com/index.q`. Set permissions to `755` or higher; `777` might be needed in some cases. in /data/wangzhan/tech.souyunku.com.wp/wp-content/plugins/comet-cache/src/includes/traits/Ac/ObUtils.php:367 Stack trace: #0 [internal function]: WebSharks\CometCache\Classes\AdvancedCache->outputBufferCallbackHandler() #1 /data/wangzhan/tech.souyunku.com.wp/wp-includes/functions.php(5109): ob_end_flush() #2 /data/wangzhan/tech.souyunku.com.wp/wp-includes/class-wp-hook.php(303): wp_ob_end_flush_all() #3 /data/wangzhan/tech.souyunku.com.wp/wp-includes/class-wp-hook.php(327): WP_Hook->apply_filters() #4 /data/wangzhan/tech.souyunku.com.wp/wp-includes/plugin.php(470): WP_Hook->do_action() #5 /data/wangzhan/tech.souyunku.com.wp/wp-includes/load.php(1097): do_action() #6 [internal function]: shutdown_action_hook() #7 {main} thrown in /data/wangzhan/tech.souyunku.com.wp/wp-content/plugins/comet-cache/src/includes/traits/Ac/ObUtils.php on line 367