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     }
View Code

 分析:

上述方法展示整个请求处理的核心过程,其中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     }
View Code

 在这个方法中,其实就是请求行请求方法、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     }
View Code

 这个方法由两部分逻辑组成: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请求解析-请求行和请求头的主要内容,如果未能解决你的问题,请参考以下文章

http的请求头,请求行和请求主体是同时发送的吗

JAVA -tomcat- 请求头太大

tomcat9以后对,请求消息头的严格字符要求

浏览器向服务器发送请求的请求头解析

HTTP中的请求头和响应头属性解析

从零开始写一个Tomcat(叁)--请求解析