Tomcat请求解析-请求行和请求头
Posted runnable
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Tomcat请求解析-请求行和请求头相关的知识,希望对你有一定的参考价值。
一、前言
文章:https://www.cnblogs.com/runnable/p/12905401.html中介绍了Tomcat处理一次请求的大致过程,其中包括请求接收、请求数据处理以及请求响应。接下来用两篇文章详细分析请求数据解析:请求行和请求头的读取、请求体的读取。
在分析请求数据处理之前,再次回归一下2个概念
1、Tomcat中用于读取socket数据的缓存区字节数组buf。它是一个字节数组,Tomcat默认长度8KB。有2个重要的位置下标:pos和lastValid,pos标记下次读取位置,lastValid标记有效数据最后位置。
图中4种情况分别对应:初始数组;刚从操作系统中读取数据到buf;Tomcat解析过程中,已经读取第一位字节;本次从操作系统读取的数据已经全部解析完。
通过上面4种情况的了解,其实可以得出这样一个结论:Tomcat中对请求数据的处理,其实就是重复这个过程,把数据从操作系统读取到Tomcat缓存,然后逐个字节进行解析。我们后面详细分析。
2、字节块(ByteChunk),一种数据结构。有三个重要属性:字节数组buff,start,end。我们从三个属性可以看出,字节块是利用两个下标,标记了一个字节数组中的一段字节。在数据被使用时才把标记的字节转换成字符串,且相同的字节段,如果已经有字符串对应,则会共用该字符串。这样做最大的好处是提高效率、减少内存使用。如下图标记了字节块下标1-4的字节。
3、HTTP请求数据格式如下
整个请求数据的解析过程实际就是根据HTTP规范逐个字节分析,最终转换成请求对象的过程,因此有必要对HTTP格式有了解
下面我们进入主题,通过源码分析请求行和请求头的解析过程
二、请求行解析
首先进入HTTP11处理器中处理请求的入口:
1 @Override 2 public SocketState process(SocketWrapper<S> socketWrapper) 3 throws IOException { 4 RequestInfo rp = request.getRequestProcessor(); 5 rp.setStage(org.apache.coyote.Constants.STAGE_PARSE); 6 7 // Setting up the I/O 8 setSocketWrapper(socketWrapper); 9 /** 10 * 设置socket的InputStream和OutStream,供后面读取数据和响应使用 11 */ 12 getInputBuffer().init(socketWrapper, endpoint); 13 getOutputBuffer().init(socketWrapper, endpoint); 14 15 // Flags 16 keepAlive = true; 17 comet = false; 18 openSocket = false; 19 sendfileInProgress = false; 20 readComplete = true; 21 if (endpoint.getUsePolling()) { 22 keptAlive = false; 23 } else { 24 keptAlive = socketWrapper.isKeptAlive(); 25 } 26 27 /** 28 * 长连接相关,判断当前socket是否继续处理接下来的请求 29 */ 30 if (disableKeepAlive()) { 31 socketWrapper.setKeepAliveLeft(0); 32 } 33 34 /** 35 * 处理socket中的请求,在长连接的模式下,每次循环表示一个HTTP请求 36 */ 37 while (!getErrorState().isError() && keepAlive && !comet && !isAsync() && 38 upgradeInbound == null && 39 httpUpgradeHandler == null && !endpoint.isPaused()) { 40 41 // Parsing the request header 42 try { 43 /** 44 * 1、设置socket超时时间 45 * 2、第一次从socket中读取数据 46 */ 47 setRequestLineReadTimeout(); 48 49 /** 50 * 读取请求行 51 */ 52 if (!getInputBuffer().parseRequestLine(keptAlive)) { 53 if (handleIncompleteRequestLineRead()) { 54 break; 55 } 56 } 57 58 // Process the Protocol component of the request line 59 // Need to know if this is an HTTP 0.9 request before trying to 60 // parse headers. 61 prepareRequestProtocol(); 62 63 if (endpoint.isPaused()) { 64 // 503 - Service unavailable 65 response.setStatus(503); 66 setErrorState(ErrorState.CLOSE_CLEAN, null); 67 } else { 68 keptAlive = true; 69 // Set this every time in case limit has been changed via JMX 70 // 设置请求头数量 71 request.getMimeHeaders().setLimit(endpoint.getMaxHeaderCount()); 72 // 设置做多可设置cookie数量 73 request.getCookies().setLimit(getMaxCookieCount()); 74 // Currently only NIO will ever return false here 75 // Don‘t parse headers for HTTP/0.9 76 /** 77 * 读取请求头 78 */ 79 if (!http09 && !getInputBuffer().parseHeaders()) { 80 // We‘ve read part of the request, don‘t recycle it 81 // instead associate it with the socket 82 openSocket = true; 83 readComplete = false; 84 break; 85 } 86 if (!disableUploadTimeout) { 87 setSocketTimeout(connectionUploadTimeout); 88 } 89 } 90 } catch (IOException e) { 91 if (getLog().isDebugEnabled()) { 92 getLog().debug( 93 sm.getString("http11processor.header.parse"), e); 94 } 95 setErrorState(ErrorState.CLOSE_NOW, e); 96 break; 97 } catch (Throwable t) { 98 ExceptionUtils.handleThrowable(t); 99 UserDataHelper.Mode logMode = userDataHelper.getNextMode(); 100 if (logMode != null) { 101 String message = sm.getString( 102 "http11processor.header.parse"); 103 switch (logMode) { 104 case INFO_THEN_DEBUG: 105 message += sm.getString( 106 "http11processor.fallToDebug"); 107 //$FALL-THROUGH$ 108 case INFO: 109 getLog().info(message, t); 110 break; 111 case DEBUG: 112 getLog().debug(message, t); 113 } 114 } 115 // 400 - Bad Request 116 response.setStatus(400); 117 setErrorState(ErrorState.CLOSE_CLEAN, t); 118 getAdapter().log(request, response, 0); 119 } 120 121 if (!getErrorState().isError()) { 122 // Setting up filters, and parse some request headers 123 rp.setStage(org.apache.coyote.Constants.STAGE_PREPARE); 124 try { 125 prepareRequest(); 126 } catch (Throwable t) { 127 ExceptionUtils.handleThrowable(t); 128 if (getLog().isDebugEnabled()) { 129 getLog().debug(sm.getString( 130 "http11processor.request.prepare"), t); 131 } 132 // 500 - Internal Server Error 133 response.setStatus(500); 134 setErrorState(ErrorState.CLOSE_CLEAN, t); 135 getAdapter().log(request, response, 0); 136 } 137 } 138 139 if (maxKeepAliveRequests == 1) { 140 keepAlive = false; 141 } else if (maxKeepAliveRequests > 0 && 142 socketWrapper.decrementKeepAlive() <= 0) { 143 keepAlive = false; 144 } 145 146 // Process the request in the adapter 147 if (!getErrorState().isError()) { 148 try { 149 rp.setStage(org.apache.coyote.Constants.STAGE_SERVICE); 150 /** 151 * 将封装好的请求和响应对象,交由容器处理 152 * service-->host-->context-->wrapper-->servlet 153 * 这里非常重要,我们所写的servlet代码正是这里在调用,它遵循了Servlet规范 154 * 这里处理完,代表程序员开发的servlet已经执行完毕 155 */ 156 adapter.service(request, response); 157 // Handle when the response was committed before a serious 158 // error occurred. Throwing a ServletException should both 159 // set the status to 500 and set the errorException. 160 // If we fail here, then the response is likely already 161 // committed, so we can‘t try and set headers. 162 if(keepAlive && !getErrorState().isError() && ( 163 response.getErrorException() != null || 164 (!isAsync() && 165 statusDropsConnection(response.getStatus())))) { 166 setErrorState(ErrorState.CLOSE_CLEAN, null); 167 } 168 setCometTimeouts(socketWrapper); 169 } catch (InterruptedIOException e) { 170 setErrorState(ErrorState.CLOSE_NOW, e); 171 } catch (HeadersTooLargeException e) { 172 getLog().error(sm.getString("http11processor.request.process"), e); 173 // The response should not have been committed but check it 174 // anyway to be safe 175 if (response.isCommitted()) { 176 setErrorState(ErrorState.CLOSE_NOW, e); 177 } else { 178 response.reset(); 179 response.setStatus(500); 180 setErrorState(ErrorState.CLOSE_CLEAN, e); 181 response.setHeader("Connection", "close"); // TODO: Remove 182 } 183 } catch (Throwable t) { 184 ExceptionUtils.handleThrowable(t); 185 getLog().error(sm.getString("http11processor.request.process"), t); 186 // 500 - Internal Server Error 187 response.setStatus(500); 188 setErrorState(ErrorState.CLOSE_CLEAN, t); 189 getAdapter().log(request, response, 0); 190 } 191 } 192 193 // Finish the handling of the request 194 rp.setStage(org.apache.coyote.Constants.STAGE_ENDINPUT); 195 196 if (!isAsync() && !comet) { 197 if (getErrorState().isError()) { 198 // If we know we are closing the connection, don‘t drain 199 // input. This way uploading a 100GB file doesn‘t tie up the 200 // thread if the servlet has rejected it. 201 getInputBuffer().setSwallowInput(false); 202 } else { 203 // Need to check this again here in case the response was 204 // committed before the error that requires the connection 205 // to be closed occurred. 206 checkExpectationAndResponseStatus(); 207 } 208 /** 209 * 请求收尾工作 210 * 判断请求体是否读取完毕,没有则读取完毕,并修正pos 211 * 请求体读取分为两种: 212 * 1、程序员读取:在servlet中有程序员主动读取,这种方式读取数据不一定读取完整数据,取决于业务需求 213 * 2、Tomcat自己读取:如果servlet中没有读取,或者没有读取完全,则Tomcat负责读取剩余的请求体 214 * 1和2的差别在于,2中仅仅把数据从操作系统读取到buf中,尽管也用了字节块做标记,但是不会做其他的事情,而1中还会把字节块标记的数据拷贝到目标数组中 215 * 这个方法就是处理情况2中的请求体读取逻辑 216 */ 217 endRequest(); 218 } 219 220 rp.setStage(org.apache.coyote.Constants.STAGE_ENDOUTPUT); 221 222 // If there was an error, make sure the request is counted as 223 // and error, and update the statistics counter 224 if (getErrorState().isError()) { 225 response.setStatus(500); 226 } 227 request.updateCounters(); 228 229 if (!isAsync() && !comet || getErrorState().isError()) { 230 if (getErrorState().isIoAllowed()) { 231 /** 232 * 根据修正完的pos和lastValid,初始化数组下标,以便继续处理下一次请求 233 * 两种情况 234 * 1、读取请求体刚好读取完,将pos=lastValid=0,即都指向buf数组第一个位置,重新读取数据 235 * 2、读取请求体多读出了下次请求的数据,这个时候需要将下个请求的数据移动到buf数组头,以便处理下个请求 236 * 注意,buf数组中的数据没有删除,是直接覆盖,从而达到对buf数组的重复使用 237 */ 238 getInputBuffer().nextRequest(); 239 getOutputBuffer().nextRequest(); 240 } 241 } 242 243 if (!disableUploadTimeout) { 244 if(endpoint.getSoTimeout() > 0) { 245 setSocketTimeout(endpoint.getSoTimeout()); 246 } else { 247 setSocketTimeout(0); 248 } 249 } 250 251 rp.setStage(org.apache.coyote.Constants.STAGE_KEEPALIVE); 252 253 if (breakKeepAliveLoop(socketWrapper)) { 254 break; 255 } 256 } 257 258 rp.setStage(org.apache.coyote.Constants.STAGE_ENDED); 259 260 if (getErrorState().isError() || endpoint.isPaused()) { 261 return SocketState.CLOSED; 262 } else if (isAsync() || comet) { 263 return SocketState.LONG; 264 } else if (isUpgrade()) { 265 return SocketState.UPGRADING; 266 } else if (getUpgradeInbound() != null) { 267 return SocketState.UPGRADING_TOMCAT; 268 } else { 269 if (sendfileInProgress) { 270 return SocketState.SENDFILE; 271 } else { 272 if (openSocket) { 273 if (readComplete) { 274 return SocketState.OPEN; 275 } else { 276 return SocketState.LONG; 277 } 278 } else { 279 return SocketState.CLOSED; 280 } 281 } 282 } 283 }
分析:
上述方法展示整个请求处理的核心过程,其中52行开始处理请求行:getInputBuffer().parseRequestLine(keptAlive)
具体方法如下:
1 /** 2 * Read the request line. This function is meant to be used during the 3 * HTTP request header parsing. Do NOT attempt to read the request body 4 * using it. 5 * 6 * @throws IOException If an exception occurs during the underlying socket 7 * read operations, or if the given buffer is not big enough to accommodate 8 * the whole line. 9 */ 10 /** 11 * 读取请求行方法 12 * 请求行格式如下: 13 * ======================================== 14 * 请求方法 空格 URL 空格 协议版本 回车换行 15 * ======================================== 16 * @param useAvailableDataOnly 17 * @return 18 * @throws IOException 19 */ 20 @Override 21 public boolean parseRequestLine(boolean useAvailableDataOnly) 22 23 throws IOException { 24 25 int start = 0; 26 27 // 28 // Skipping blank lines 29 // 30 31 /** 32 * 过滤掉回车(CR)换行(LF)符,确定start位置 33 */ 34 do { 35 36 // Read new bytes if needed 37 if (pos >= lastValid) { 38 if (!fill()) 39 throw new EOFException(sm.getString("iib.eof.error")); 40 } 41 // Set the start time once we start reading data (even if it is 42 // just skipping blank lines) 43 if (request.getStartTime() < 0) { 44 request.setStartTime(System.currentTimeMillis()); 45 } 46 /** 47 * chr记录第一个非CRLF字节,后面读取请求头的时候用到 48 */ 49 chr = buf[pos++]; 50 } while (chr == Constants.CR || chr == Constants.LF); 51 52 pos--; 53 54 // Mark the current buffer position 55 start = pos; 56 57 // 58 // Reading the method name 59 // Method name is a token 60 // 61 62 boolean space = false; 63 64 /** 65 * 读取HTT请求方法:get/post/put.... 66 */ 67 while (!space) { 68 69 // Read new bytes if needed 70 if (pos >= lastValid) { 71 if (!fill()) 72 throw new EOFException(sm.getString("iib.eof.error")); 73 } 74 75 // Spec says method name is a token followed by a single SP but 76 // also be tolerant of multiple SP and/or HT. 77 if (buf[pos] == Constants.SP || buf[pos] == Constants.HT) { 78 space = true; 79 /** 80 * 设置HTTP请求方法,这里没有直接设置字符串,而是用了字节块ByteChunk 81 * ByteChunk中包含一个字节数据类型的属性buff,此处的setBytes方法就是将buff指向Tomcat的缓存buf。然后start和end标记为 82 * 此处方法的后两个入参,也就是将请求方法在buf中标记了出来,但是没有转换成字符串,等到使用的时候再使用ByteBuffer.wap方法 83 * 转换成字符串,且标记hasStrValue=true,如果再次获取就直接拿转换好的字符串,不用再次转换。效率考虑?牛逼! 84 * 因此,就算后面由于请求体过长,Tomcat重新开辟新的数组buf读取请求体。原buf也不会被GC,因为ByteChunk中的buff引用了原buf数组 85 * 什么时候原数组才会被GC?本次请求结束,request对象被GC后。。。 86 */ 87 request.method().setBytes(buf, start, pos - start); 88 } else if (!HttpParser.isToken(buf[pos])) { 89 String invalidMethodValue = parseInvalid(start, buf); 90 throw new IllegalArgumentException(sm.getString("iib.invalidmethod", invalidMethodValue)); 91 } 92 93 pos++; 94 95 } 96 97 // Spec says single SP but also be tolerant of multiple SP and/or HT 98 /** 99 * 过滤请求方法后面的空格(SP或者HT) 100 */ 101 while (space) { 102 // Read new bytes if needed 103 if (pos >= lastValid) { 104 if (!fill()) 105 throw new EOFException(sm.getString("iib.eof.error")); 106 } 107 if (buf[pos] == Constants.SP || buf[pos] == Constants.HT) { 108 pos++; 109 } else { 110 space = false; 111 } 112 } 113 114 // Mark the current buffer position 115 start = pos; 116 int end = 0; 117 int questionPos = -1; 118 119 // 120 // Reading the URI 121 // 122 123 boolean eol = false; 124 125 /** 126 * 读取URL 127 */ 128 while (!space) { 129 130 // Read new bytes if needed 131 if (pos >= lastValid) { 132 if (!fill()) 133 throw new EOFException(sm.getString("iib.eof.error")); 134 } 135 136 /** 137 * CR后面没有LF,不是HTTP0.9,抛异常 138 */ 139 if (buf[pos -1] == Constants.CR && buf[pos] != Constants.LF) { 140 // CR not followed by LF so not an HTTP/0.9 request and 141 // therefore invalid. Trigger error handling. 142 // Avoid unknown protocol triggering an additional error 143 request.protocol().setString(Constants.HTTP_11); 144 String invalidRequestTarget = parseInvalid(start, buf); 145 throw new IllegalArgumentException(sm.getString("iib.invalidRequestTarget", invalidRequestTarget)); 146 } 147 148 // Spec says single SP but it also says be tolerant of HT 149 if (buf[pos] == Constants.SP || buf[pos] == Constants.HT) { 150 /** 151 * 遇到空格(SP或者HT),URL读取结束 152 */ 153 space = true; 154 end = pos; 155 } else if (buf[pos] == Constants.CR) { 156 // HTTP/0.9 style request. CR is optional. LF is not. 157 } else if (buf[pos] == Constants.LF) { 158 // HTTP/0.9 style request 159 // Stop this processing loop 160 space = true; 161 // Set blank protocol (indicates HTTP/0.9) 162 request.protocol().setString(""); 163 // Skip the protocol processing 164 eol = true; 165 if (buf[pos - 1] == Constants.CR) { 166 end = pos - 1; 167 } else { 168 end = pos; 169 } 170 } else if ((buf[pos] == Constants.QUESTION) && (questionPos == -1)) { 171 questionPos = pos; 172 } else if (questionPos != -1 && !httpParser.isQueryRelaxed(buf[pos])) { 173 // %nn decoding will be checked at the point of decoding 174 String invalidRequestTarget = parseInvalid(start, buf); 175 throw new IllegalArgumentException(sm.getString("iib.invalidRequestTarget", invalidRequestTarget)); 176 } else if (httpParser.isNotRequestTargetRelaxed(buf[pos])) { 177 // This is a general check that aims to catch problems early 178 // Detailed checking of each part of the request target will 179 // happen in AbstractHttp11Processor#prepareRequest() 180 String invalidRequestTarget = parseInvalid(start, buf); 181 throw new IllegalArgumentException(sm.getString("iib.invalidRequestTarget", invalidRequestTarget)); 182 } 183 pos++; 184 } 185 /** 186 * 读取HTTP URL 187 */ 188 request.unparsedURI().setBytes(buf, start, end - start); 189 if (questionPos >= 0) { 190 /** 191 * 当有请求入参的时候 192 * 读取入参字符串 193 * 读取URI 194 */ 195 request.queryString().setBytes(buf, questionPos + 1, 196 end - questionPos - 1); 197 request.requestURI().setBytes(buf, start, questionPos - start); 198 } else { 199 /** 200 * 没有请求入参的时候,直接读取URI 201 */ 202 request.requestURI().setBytes(buf, start, end - start); 203 } 204 205 // Spec says single SP but also says be tolerant of multiple SP and/or HT 206 while (space && !eol) { 207 // Read new bytes if needed 208 if (pos >= lastValid) { 209 if (!fill()) 210 throw new EOFException(sm.getString("iib.eof.error")); 211 } 212 if (buf[pos] == Constants.SP || buf[pos] == Constants.HT) { 213 pos++; 214 } else { 215 space = false; 216 } 217 } 218 219 // Mark the current buffer position 220 start = pos; 221 end = 0; 222 223 // 224 // Reading the protocol 225 // Protocol is always "HTTP/" DIGIT "." DIGIT 226 // 227 /** 228 * 读取HTTP协议版本 229 */ 230 while (!eol) { 231 232 // Read new bytes if needed 233 if (pos >= lastValid) { 234 if (!fill()) 235 throw new EOFException(sm.getString("iib.eof.error")); 236 } 237 238 if (buf[pos] == Constants.CR) { 239 // Possible end of request line. Need LF next. 240 } else if (buf[pos - 1] == Constants.CR && buf[pos] == Constants.LF) { 241 end = pos - 1; 242 eol = true; 243 } else if (!HttpParser.isHttpProtocol(buf[pos])) { 244 String invalidProtocol = parseInvalid(start, buf); 245 throw new IllegalArgumentException(sm.getString("iib.invalidHttpProtocol", invalidProtocol)); 246 } 247 248 pos++; 249 250 } 251 252 /** 253 * 字节块标记协议版本 254 */ 255 if ((end - start) > 0) { 256 request.protocol().setBytes(buf, start, end - start); 257 } 258 259 /** 260 * 如果没有协议版本,无法处理请求,抛异常 261 */ 262 if (request.protocol().isNull()) { 263 throw new IllegalArgumentException(sm.getString("iib.invalidHttpProtocol")); 264 } 265 266 return true; 267 }
在这个方法中,其实就是请求行请求方法、url、协议版本这几个部分的读取。
分析:
34-50行:这个while循环是过滤行首的回车换行符,只要是回车换行符下标pos就往后移动一位,直到不是回车换行符,跳出循环。由于这里是先执行pos++,所以如果不满足条件,pos需要后移一位,也就是真正开始读取请求方法的位置,标记为start。
37-40行:这里是非常关键的几行代码,几乎贯穿整个请求处理部分。Tomcat接收请求,就是在接收客户端的请求数据,数据经过网络传输到Tomcat所在的服务操作系统缓冲区,Tomcat从操作系统读取到自己的缓冲区buf中。这几行代码主要就是干这个事情的。前面我们介绍了字节数在buf是通过pos和lastValid控制读取的。37行判断当pos>=lastValid,表示buf数组中读取自操作系统的数据已经解析完毕,调用fill()方法再次从操作系统读取。代码如下:
1 @Override 2 protected boolean fill(boolean block) throws IOException { 3 4 int nRead = 0; 5 6 /** 7 * 这个核心就是读取socket中数据到缓冲区buf中,循环读取,2种情况 8 * 1、请求行和请求头:不能超过缓冲区大小(默认8kb),如果超过,则抛异常,读完后将parsingHeader设置为false 9 * 2、请求行:没有任何大小限制,循环读取,如果剩下的少于4500个字节,则会重新创建buf数组,从头开始读取,直到读完位置,注意!buf原先引用的数组们,等待GC 10 */ 11 if (parsingHeader) { 12 13 /** 14 * 从socket中读取数据大于tomcat中缓冲区buf的长度,直接抛异常,这里有两点 15 * 1、这个就是我们很多时候很多人说的,get请求url不能过长的原因,其实是header和url等总大小不能超过8kb 16 * 2、这里的buf非常总要,它是InternalInputBuffer的属性,是一个字节数据,用户暂存从socket中读取的数据,比如:请求行,请求头、请求体 17 */ 18 if (lastValid == buf.length) { 19 throw new IllegalArgumentException 20 (sm.getString("iib.requestheadertoolarge.error")); 21 } 22 23 // 将socket中的数据读到缓冲区buf中,注意!这里就是BIO之所以难懂的关键所在,它会阻塞! 24 // 这个方法会阻塞,如果没有数据可读,则会一直阻塞,有数据,则移动lastValid位置 25 nRead = inputStream.read(buf, pos, buf.length - lastValid); 26 if (nRead > 0) { 27 lastValid = pos + nRead; 28 } 29 30 } else { 31 /** 32 * parsingHeader==false,请求行和请求头已经读取完毕,开始读取请求体 33 */ 34 35 if (buf.length - end < 4500) { 36 // In this case, the request header was really large, so we allocate a 37 // brand new one; the old one will get GCed when subsequent requests 38 // clear all references 39 /** 40 * 如果Tomcat缓存区buf读取完请求行和请求头后,剩余长度不足4500(可配置),新创建一个字节数组buf用于读取请求体 41 * 为什么要这么做,应该是考虑到如果剩余的数据长度较小,每次从操作系统缓存区读取的字节就比较少,读取次数就比较多? 42 * 注意,buf原先指向的字节数据会白GC么?应该不会,因为请求行和请求头有许多字节块(ByteChunk)指向了旧字节数据。 43 * 什么时候才会被GC?应该是一起request处理完毕后。 44 */ 45 buf = new byte[buf.length]; 46 end = 0; 47 } 48 /** 49 * 这里的end是请求头数据的后一位,从这里开始读取请求体数据。 50 * 从操作系统读取数据到buf中,下标pos开始,lastValid结束 51 * 注意:这里每次读取请求体数据的时候都会把pos重置为end(请求头数据的后一位)!!!!! 52 * 表示什么? 53 * 请求体数据每一次从操作系统缓存中读取到buf,然后读取到程序员自己的数组后,在下次再次从操作系统读取数据到buf时,就会把之前读取的请求体数据覆盖掉 54 * 也就是从end位置开始,后面的数据都只能读取一次,这个很重要!!! 55 * 为什么这么做?我的理解是因为请求体数据可以很大,为了单个请求不占用太大内存,所以设计成了覆盖的模式,真是秒啊! 56 */ 57 pos = end; 58 lastValid = pos; 59 60 /** 61 * 原则上这个方法要么阻塞着,要么nRead>0 62 */ 63 nRead = inputStream.read(buf, pos, buf.length - lastValid); 64 if (nRead > 0) { 65 lastValid = pos + nRead; 66 } 67 68 } 69 70 /** 71 * 注意,这里不出意外,只能返回true 72 */ 73 return (nRead > 0); 74 75 }
这个方法由两部分逻辑组成:parsingHeader=true或者false,这个变量表示读取的请求行和请求头,还是读取的请求体。变量名有点歧义,并不是只包含请求头,而是请求行和请求头。
11-30行:读取请求行和请求头数据,逻辑很简单:从操作系统读取数据到字节数组buf中,后移lastValid下标到buf数组最后一个字节的位置。在Tomcat解析完这部分数据后,会把parsingHeader置为false,且用end下标指向请求头后一个字节,以便后续可以读取请求体数据。
35-66行:读取请求体数据,逻辑比请求行和请求头读取稍微复杂点:判断buf数组剩余字节长度是否大于4500,反之重新创建数组。每次读取pos和lastValid都置为end,然后读取数据到buf数组中,lastValid后移。由于请求体数据可能比较大,且理论上没有上限限制,为了减少读取次数,buf剩余空间不能过小。每次读取数据到buf中,都是存放在end位置开始,每次都是覆盖上一次读取的数据,所以我们可以大胆猜测,请求体数据只能读取一次,程序员自己如果需要多次使用,必须自行保存。我想这是为了减少内存使用吧,你们看呢?
还有一个关键点:25行和63行代码:nRead = inputStream.read(buf, pos, buf.length - lastValid),这行代码是从操作系统读取字节,接触过socket编程的都知道read方法这里可能会阻塞的,当操作系统缓存中当前没有数据可读,等待网络传输的时候,read方法阻塞,直到有数据返回后再继续。
以上是关于Tomcat请求解析-请求行和请求头的主要内容,如果未能解决你的问题,请参考以下文章