一次HTTP請求

如上圖,一次完整的HTT請求,大致包含如下步驟:
- 域名決議
- TCP連接(三次握手)
- 建立連接后發送請求資料
- 服務器處理請求
- 發送回應資料
- 瀏覽器決議回應資料
- 瀏覽器渲染前端頁面
http請求程序看似簡單,但如果你對任何事情都抱有一定的好奇心,要從底層根本性的了解以上各個環節,其實及其復雜,需要的知識量也很豐富,基本涵蓋了你大學學的所有專業科目(不信?后面我們慢慢來看),今天我們站著Tomcat的視角,去了解http請求在Tomcat中到底經歷了些什么,
我們將圍繞以下步驟進行:Tomcat接收請求、請求資料讀取、容器處理業務邏輯、多次請求如何銜接、簡單回應等,
底層概念介紹
1、關于網路

我們所熟知的中間件(mysql、redis、kafka、rabbitmq、dubbo等)其實都是網路上的一個運行的服務,它們從客服端接收資料,通過復雜的邏輯處理后回傳客戶端,Tomcat當然也不例外,它可以接收來自客戶端的多種協議的請求,包括:HTTP、APR等,這里的接收請求和回傳請求,自然就涉及到網路編程,在java中網路編程被JDK封裝成了Socket介面,我們可以把tomcat理解成一個啟動后就一直在運行的Socket服務端程式,它在不停的監聽來自外界的請求(其實是TCP請求,是否理解了不管你是http還是其他應用層協議,其實底層都是TCP請求?),熟悉Socket的同學肯定要問了,那Tomcat是BIO還是NIO呢?沒錯,只要是網路傳輸,就逃不掉這個問題,這里先簡單說下加,早期的Tomcat是BIO的,具體的版本是7.0(包括)之前都是默認BIO,支持配置成NIO,8.0開始全面默認NIO,當然8.0后的NIO和之前的NIO是有區別的,后續文章我會提到,BIO模型處理流程如下圖:

2、底層資料存盤

資料通過網路傳輸到Tomcat所在的服務器,首先會在作業系統緩沖區revbuf中,然后tomcat會從revbuf中讀取資料,最后存放在自身JVM中,這個步驟看似簡單,但是確實作代作業系統的一個經典理論,作業系統和tomcat存放資料都是使用的位元組陣列,我們僅以Tomcat中位元組資料buf為例:

資料到達Tomcat中后,都會經歷以上的位元組陣列,而后再封裝成request物件傳遞給容器,
原始碼分析
1、Acceptor
1 /** 2 * The background thread that listens for incoming TCP/IP connections and 3 * hands them off to an appropriate processor. 4 */ 5 /** 6 * 連接接收類,用于接受來自客戶端的所有請求 7 */ 8 protected class Acceptor extends AbstractEndpoint.Acceptor { 9 10 @Override 11 public void run() { 12 13 int errorDelay = 0; 14 15 // Loop until we receive a shutdown command 16 while (running) { 17 18 // Loop if endpoint is paused 19 // 內層回圈,暫時中斷處理 20 while (paused && running) { 21 state = AcceptorState.PAUSED; 22 try { 23 Thread.sleep(50); 24 } catch (InterruptedException e) { 25 // Ignore 26 } 27 } 28 29 //關閉 30 if (!running) { 31 break; 32 } 33 state = AcceptorState.RUNNING; 34 35 try { 36 //if we have reached max connections, wait 37 // 繼承AQS,控制總連接數不能超過最大值 38 countUpOrAwaitConnection(); 39 40 Socket socket = null; 41 try { 42 // Accept the next incoming connection from the server 43 // socket 44 // 接收socket請求,最重要的入口!!! 45 socket = serverSocketFactory.acceptSocket(serverSocket); 46 } catch (IOException ioe) { 47 countDownConnection(); 48 // Introduce delay if necessary 49 errorDelay = handleExceptionWithDelay(errorDelay); 50 // re-throw 51 throw ioe; 52 } 53 // Successful accept, reset the error delay 54 errorDelay = 0; 55 56 // Configure the socket 57 if (running && !paused && setSocketOptions(socket)) { 58 // Hand this socket off to an appropriate processor 59 if (!processSocket(socket)) { 60 countDownConnection(); 61 // Close socket right away 62 closeSocket(socket); 63 } 64 } else { 65 countDownConnection(); 66 // Close socket right away 67 closeSocket(socket); 68 } 69 } catch (IOException x) { 70 if (running) { 71 log.error(sm.getString("endpoint.accept.fail"), x); 72 } 73 } catch (NullPointerException npe) { 74 if (running) { 75 log.error(sm.getString("endpoint.accept.fail"), npe); 76 } 77 } catch (Throwable t) { 78 ExceptionUtils.handleThrowable(t); 79 log.error(sm.getString("endpoint.accept.fail"), t); 80 } 81 } 82 state = AcceptorState.ENDED; 83 } 84 }
Acceptor繼承了AbstractEndpoint類的內部類Acceptor,這個Acceptor實作了Runnable介面,表明自己是一個多執行緒的任務類,可放入執行緒池,Tomcat啟動后,外層Acceptor就開始執行run方法,
11行,實作了Runnable介面中的run方法,可以看見該run方法中是一個while回圈,只有當running=false,回圈才跳出,
45行,這里是關鍵,tomcat啟動后,如果沒有請求進來,當前主執行緒將在此處進行阻塞,直到第一個請求到來,便獲取當前請求的socket,
59行,將得到的socekt交由processSocket()方法處理,
其他的代碼都是一些例外情況的判斷,這里忽略,
2、processSocket
1 protected boolean processSocket(Socket socket) { 2 // Process the request from this socket 3 try { 4 SocketWrapper<Socket> wrapper = new SocketWrapper<Socket>(socket); 5 wrapper.setKeepAliveLeft(getMaxKeepAliveRequests()); 6 wrapper.setSecure(isSSLEnabled()); 7 // During shutdown, executor may be null - avoid NPE 8 if (!running) { 9 return false; 10 } 11 // 將包裝好的socketProcessor扔執行緒池 12 getExecutor().execute(new SocketProcessor(wrapper)); 13 } catch (RejectedExecutionException x) { 14 log.warn("Socket processing request was rejected for:"+socket,x); 15 return false; 16 } catch (Throwable t) { 17 ExceptionUtils.handleThrowable(t); 18 // This means we got an OOM or similar creating a thread, or that 19 // the pool and its queue are full 20 log.error(sm.getString("endpoint.process.fail"), t); 21 return false; 22 } 23 return true; 24 }
process方法將socket包裝成SocketWrapper,再設定為SocketProcessor的屬性
12行,SocketProcessor類實作了Runnable介面,這里相當于將當前請求的socket扔到了執行緒池當中,
3、SocketProcessor
1 /** 2 * This class is the equivalent of the Worker, but will simply use in an 3 * external Executor thread pool. 4 */ 5 protected class SocketProcessor implements Runnable { 6 7 protected SocketWrapper<Socket> socket = null; 8 protected SocketStatus status = null; 9 10 public SocketProcessor(SocketWrapper<Socket> socket) { 11 if (socket==null) throw new NullPointerException(); 12 this.socket = socket; 13 } 14 15 public SocketProcessor(SocketWrapper<Socket> socket, SocketStatus status) { 16 this(socket); 17 this.status = status; 18 } 19 20 @Override 21 public void run() { 22 boolean launch = false; 23 synchronized (socket) { 24 try { 25 SocketState state = SocketState.OPEN; 26 27 try { 28 // SSL handshake 29 // 這里處理HTTPS相關的邏輯 30 serverSocketFactory.handshake(socket.getSocket()); 31 } catch (Throwable t) { 32 ExceptionUtils.handleThrowable(t); 33 if (log.isDebugEnabled()) { 34 log.debug(sm.getString("endpoint.err.handshake"), t); 35 } 36 // Tell to close the socket 37 state = SocketState.CLOSED; 38 } 39 40 /** 41 * handler處理socket 42 */ 43 if ((state != SocketState.CLOSED)) { 44 if (status == null) { 45 state = handler.process(socket, SocketStatus.OPEN_READ); 46 } else { 47 state = handler.process(socket,status); 48 } 49 } 50 if (state == SocketState.CLOSED) { 51 // Close socket 52 if (log.isTraceEnabled()) { 53 log.trace("Closing socket:"+socket); 54 } 55 countDownConnection(); 56 try { 57 socket.getSocket().close(); 58 } catch (IOException e) { 59 // Ignore 60 } 61 } else if (state == SocketState.OPEN || 62 state == SocketState.UPGRADING || 63 state == SocketState.UPGRADING_TOMCAT || 64 state == SocketState.UPGRADED){ 65 socket.setKeptAlive(true); 66 socket.access(); 67 launch = true; 68 } else if (state == SocketState.LONG) { 69 socket.access(); 70 waitingRequests.add(socket); 71 } 72 } finally { 73 if (launch) { 74 try { 75 getExecutor().execute(new SocketProcessor(socket, SocketStatus.OPEN_READ)); 76 } catch (RejectedExecutionException x) { 77 log.warn("Socket reprocessing request was rejected for:"+socket,x); 78 try { 79 //unable to handle connection at this time 80 handler.process(socket, SocketStatus.DISCONNECT); 81 } finally { 82 countDownConnection(); 83 } 84 85 86 } catch (NullPointerException npe) { 87 if (running) { 88 log.error(sm.getString("endpoint.launch.fail"), 89 npe); 90 } 91 } 92 } 93 } 94 } 95 socket = null; 96 // Finish up this request 97 } 98 99 }
這個類主要是處理買個socket連接中接收到的http請求
45行,將socket交由handler處理,其他的都是例外情況處理
4、handler
1 public SocketState process(SocketWrapper<S> wrapper, 2 SocketStatus status) { 3 23 24 try { 25 if (processor == null) { 26 processor = recycledProcessors.poll(); 27 } 28 if (processor == null) { 29 processor = createProcessor(); 30 } 31 32 initSsl(wrapper, processor); 33 34 SocketState state = SocketState.CLOSED; 35 do { 36 if (status == SocketStatus.DISCONNECT && 37 !processor.isComet()) { 38 // Do nothing here, just wait for it to get recycled 39 // Don't do this for Comet we need to generate an end 40 // event (see BZ 54022) 41 } else if (processor.isAsync() || state == SocketState.ASYNC_END) { 42 state = processor.asyncDispatch(status); 43 if (state == SocketState.OPEN) { 44 // release() won't get called so in case this request 45 // takes a long time to process, remove the socket from 46 // the waiting requests now else the async timeout will 47 // fire 48 getProtocol().endpoint.removeWaitingRequest(wrapper); 49 // There may be pipe-lined data to read. If the data 50 // isn't processed now, execution will exit this 51 // loop and call release() which will recycle the 52 // processor (and input buffer) deleting any 53 // pipe-lined data. To avoid this, process it now. 54 state = processor.process(wrapper); 55 } 56 } else if (processor.isComet()) { 57 state = processor.event(status); 58 } else if (processor.getUpgradeInbound() != null) { 59 state = processor.upgradeDispatch(); 60 } else if (processor.isUpgrade()) { 61 state = processor.upgradeDispatch(status); 62 } else { 63 // 處理socket 64 state = processor.process(wrapper); 65 } 66 67 if (processor.isAsync()) { 68 state = processor.asyncPostProcess(); 69 } 70 71 if (state == SocketState.UPGRADING) { 72 // Get the HTTP upgrade handler 73 HttpUpgradeHandler httpUpgradeHandler = 74 processor.getHttpUpgradeHandler(); 75 // Release the Http11 processor to be re-used 76 release(wrapper, processor, false, false); 77 // Create the upgrade processor 78 processor = createUpgradeProcessor( 79 wrapper, httpUpgradeHandler); 80 // Mark the connection as upgraded 81 wrapper.setUpgraded(true); 82 // Associate with the processor with the connection 83 connections.put(socket, processor); 84 // Initialise the upgrade handler (which may trigger 85 // some IO using the new protocol which is why the lines 86 // above are necessary) 87 // This cast should be safe. If it fails the error 88 // handling for the surrounding try/catch will deal with 89 // it. 90 httpUpgradeHandler.init((WebConnection) processor); 91 } else if (state == SocketState.UPGRADING_TOMCAT) { 92 // Get the UpgradeInbound handler 93 org.apache.coyote.http11.upgrade.UpgradeInbound inbound = 94 processor.getUpgradeInbound(); 95 // Release the Http11 processor to be re-used 96 release(wrapper, processor, false, false); 97 // Create the light-weight upgrade processor 98 processor = createUpgradeProcessor(wrapper, inbound); 99 inbound.onUpgradeComplete(); 100 } 101 if (getLog().isDebugEnabled()) { 102 getLog().debug("Socket: [" + wrapper + 103 "], Status in: [" + status + 104 "], State out: [" + state + "]"); 105 } 106 } while (state == SocketState.ASYNC_END || 107 state == SocketState.UPGRADING || 108 state == SocketState.UPGRADING_TOMCAT); 109 110 if (state == SocketState.LONG) { 111 // In the middle of processing a request/response. Keep the 112 // socket associated with the processor. Exact requirements 113 // depend on type of long poll 114 connections.put(socket, processor); 115 longPoll(wrapper, processor); 116 } else if (state == SocketState.OPEN) { 117 // In keep-alive but between requests. OK to recycle 118 // processor. Continue to poll for the next request. 119 connections.remove(socket); 120 release(wrapper, processor, false, true); 121 } else if (state == SocketState.SENDFILE) { 122 // Sendfile in progress. If it fails, the socket will be 123 // closed. If it works, the socket either be added to the 124 // poller (or equivalent) to await more data or processed 125 // if there are any pipe-lined requests remaining. 126 connections.put(socket, processor); 127 } else if (state == SocketState.UPGRADED) { 128 // Need to keep the connection associated with the processor 129 connections.put(socket, processor); 130 // Don't add sockets back to the poller if this was a 131 // non-blocking write otherwise the poller may trigger 132 // multiple read events which may lead to thread starvation 133 // in the connector. The write() method will add this socket 134 // to the poller if necessary. 135 if (status != SocketStatus.OPEN_WRITE) { 136 longPoll(wrapper, processor); 137 } 138 } else { 139 // Connection closed. OK to recycle the processor. Upgrade 140 // processors are not re-used but recycle is called to clear 141 // references. 142 connections.remove(socket); 143 if (processor.isUpgrade()) { 144 processor.getHttpUpgradeHandler().destroy(); 145 processor.recycle(true); 146 } else if (processor instanceof org.apache.coyote.http11.upgrade.UpgradeProcessor) { 147 // NO-OP 148 } else { 149 release(wrapper, processor, true, false); 150 } 151 } 152 return state; 153 } catch(java.net.SocketException e) { 154 // SocketExceptions are normal 155 getLog().debug(sm.getString( 156 "abstractConnectionHandler.socketexception.debug"), e); 157 } catch (java.io.IOException e) { 158 // IOExceptions are normal 159 getLog().debug(sm.getString( 160 "abstractConnectionHandler.ioexception.debug"), e); 161 } 162 // Future developers: if you discover any other 163 // rare-but-nonfatal exceptions, catch them here, and log as 164 // above. 165 catch (OutOfMemoryError oome) { 166 // Try and handle this here to give Tomcat a chance to close the 167 // connection and prevent clients waiting until they time out. 168 // Worst case, it isn't recoverable and the attempt at logging 169 // will trigger another OOME. 170 getLog().error(sm.getString("abstractConnectionHandler.oome"), oome); 171 } catch (Throwable e) { 172 ExceptionUtils.handleThrowable(e); 173 // any other exception or error is odd. Here we log it 174 // with "ERROR" level, so it will show up even on 175 // less-than-verbose logs. 176 getLog().error( 177 sm.getString("abstractConnectionHandler.error"), e); 178 } 179 // Make sure socket/processor is removed from the list of current 180 // connections 181 connections.remove(socket); 182 // Don't try to add upgrade processors back into the pool 183 if (!(processor instanceof org.apache.coyote.http11.upgrade.UpgradeProcessor) 184 && !processor.isUpgrade()) { 185 release(wrapper, processor, true, false); 186 } 187 return SocketState.CLOSED; 188 }
68行,繼續將socket往下傳
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 /** 9 * 設定socket的InputStream和OutStream,供后面讀取資料和回應使用 10 */ 11 getInputBuffer().init(socketWrapper, endpoint); 12 getOutputBuffer().init(socketWrapper, endpoint); 13 14 // Flags 15 keepAlive = true; 16 comet = false; 17 openSocket = false; 18 sendfileInProgress = false; 19 readComplete = true; 20 if (endpoint.getUsePolling()) { 21 keptAlive = false; 22 } else { 23 keptAlive = socketWrapper.isKeptAlive(); 24 } 25 26 /** 27 * 長連接相關,判斷當前socket是否繼續處理接下來的請求 28 */ 29 if (disableKeepAlive()) { 30 socketWrapper.setKeepAliveLeft(0); 31 } 32 33 /** 34 * 處理socket中的請求,在長連接的模式下,會一直執行當前回圈 35 */ 36 while (!getErrorState().isError() && keepAlive && !comet && !isAsync() && 37 upgradeInbound == null && 38 httpUpgradeHandler == null && !endpoint.isPaused()) { 39 40 // Parsing the request header 41 try { 42 /** 43 * 1、設定socket超時時間 44 * 2、第一次從socket中讀取資料 45 */ 46 setRequestLineReadTimeout(); 47 48 // 讀取HTTP請求行資料 49 if (!getInputBuffer().parseRequestLine(keptAlive)) { 50 if (handleIncompleteRequestLineRead()) { 51 break; 52 } 53 } 54 55 // Process the Protocol component of the request line 56 // Need to know if this is an HTTP 0.9 request before trying to 57 // parse headers. 58 prepareRequestProtocol(); 59 60 if (endpoint.isPaused()) { 61 // 503 - Service unavailable 62 response.setStatus(503); 63 setErrorState(ErrorState.CLOSE_CLEAN, null); 64 } else { 65 keptAlive = true; 66 // Set this every time in case limit has been changed via JMX 67 // 設定請求行和請求頭大小,注意,這個很重要! 68 request.getMimeHeaders().setLimit(endpoint.getMaxHeaderCount()); 69 // 設定做多可設定cookie數量 70 request.getCookies().setLimit(getMaxCookieCount()); 71 // Currently only NIO will ever return false here 72 // Don't parse headers for HTTP/0.9 73 if (!http09 && !getInputBuffer().parseHeaders()) { 74 // We've read part of the request, don't recycle it 75 // instead associate it with the socket 76 openSocket = true; 77 readComplete = false; 78 break; 79 } 80 if (!disableUploadTimeout) { 81 setSocketTimeout(connectionUploadTimeout); 82 } 83 } 84 } catch (IOException e) { 85 if (getLog().isDebugEnabled()) { 86 getLog().debug( 87 sm.getString("http11processor.header.parse"), e); 88 } 89 setErrorState(ErrorState.CLOSE_NOW, e); 90 break; 91 } catch (Throwable t) { 92 ExceptionUtils.handleThrowable(t); 93 UserDataHelper.Mode logMode = userDataHelper.getNextMode(); 94 if (logMode != null) { 95 String message = sm.getString( 96 "http11processor.header.parse"); 97 switch (logMode) { 98 case INFO_THEN_DEBUG: 99 message += sm.getString( 100 "http11processor.fallToDebug"); 101 //$FALL-THROUGH$ 102 case INFO: 103 getLog().info(message, t); 104 break; 105 case DEBUG: 106 getLog().debug(message, t); 107 } 108 } 109 // 400 - Bad Request 110 response.setStatus(400); 111 setErrorState(ErrorState.CLOSE_CLEAN, t); 112 getAdapter().log(request, response, 0); 113 } 114 115 if (!getErrorState().isError()) { 116 // Setting up filters, and parse some request headers 117 rp.setStage(org.apache.coyote.Constants.STAGE_PREPARE); 118 try { 119 prepareRequest(); 120 } catch (Throwable t) { 121 ExceptionUtils.handleThrowable(t); 122 if (getLog().isDebugEnabled()) { 123 getLog().debug(sm.getString( 124 "http11processor.request.prepare"), t); 125 } 126 // 500 - Internal Server Error 127 response.setStatus(500); 128 setErrorState(ErrorState.CLOSE_CLEAN, t); 129 getAdapter().log(request, response, 0); 130 } 131 } 132 133 if (maxKeepAliveRequests == 1) { 134 keepAlive = false; 135 } else if (maxKeepAliveRequests > 0 && 136 socketWrapper.decrementKeepAlive() <= 0) { 137 keepAlive = false; 138 } 139 140 // Process the request in the adapter 141 if (!getErrorState().isError()) { 142 try { 143 rp.setStage(org.apache.coyote.Constants.STAGE_SERVICE); 144 /** 145 * 將封裝好的請求和回應物件,交由容器處理 146 * service-->host-->context-->wrapper-->servlet 147 * 這里非常重要,我們所寫的servlet代碼正是這里在呼叫,它遵循了Servlet規范 148 * 這里處理完,代表程式員開發的servlet已經執行完畢 149 */ 150 adapter.service(request, response); 151 // Handle when the response was committed before a serious 152 // error occurred. Throwing a ServletException should both 153 // set the status to 500 and set the errorException. 154 // If we fail here, then the response is likely already 155 // committed, so we can't try and set headers. 156 if(keepAlive && !getErrorState().isError() && ( 157 response.getErrorException() != null || 158 (!isAsync() && 159 statusDropsConnection(response.getStatus())))) { 160 setErrorState(ErrorState.CLOSE_CLEAN, null); 161 } 162 setCometTimeouts(socketWrapper); 163 } catch (InterruptedIOException e) { 164 setErrorState(ErrorState.CLOSE_NOW, e); 165 } catch (HeadersTooLargeException e) { 166 getLog().error(sm.getString("http11processor.request.process"), e); 167 // The response should not have been committed but check it 168 // anyway to be safe 169 if (response.isCommitted()) { 170 setErrorState(ErrorState.CLOSE_NOW, e); 171 } else { 172 response.reset(); 173 response.setStatus(500); 174 setErrorState(ErrorState.CLOSE_CLEAN, e); 175 response.setHeader("Connection", "close"); // TODO: Remove 176 } 177 } catch (Throwable t) { 178 ExceptionUtils.handleThrowable(t); 179 getLog().error(sm.getString("http11processor.request.process"), t); 180 // 500 - Internal Server Error 181 response.setStatus(500); 182 setErrorState(ErrorState.CLOSE_CLEAN, t); 183 getAdapter().log(request, response, 0); 184 } 185 } 186 187 // Finish the handling of the request 188 rp.setStage(org.apache.coyote.Constants.STAGE_ENDINPUT); 189 190 if (!isAsync() && !comet) { 191 if (getErrorState().isError()) { 192 // If we know we are closing the connection, don't drain 193 // input. This way uploading a 100GB file doesn't tie up the 194 // thread if the servlet has rejected it. 195 getInputBuffer().setSwallowInput(false); 196 } else { 197 // Need to check this again here in case the response was 198 // committed before the error that requires the connection 199 // to be closed occurred. 200 checkExpectationAndResponseStatus(); 201 } 202 /** 203 * 當前請求收尾作業 204 * 判斷請求體是否讀取完畢,沒有則讀取完畢,并修正pos 205 */ 206 endRequest(); 207 } 208 209 rp.setStage(org.apache.coyote.Constants.STAGE_ENDOUTPUT); 210 211 // If there was an error, make sure the request is counted as 212 // and error, and update the statistics counter 213 if (getErrorState().isError()) { 214 response.setStatus(500); 215 } 216 request.updateCounters(); 217 218 if (!isAsync() && !comet || getErrorState().isError()) { 219 if (getErrorState().isIoAllowed()) { 220 /** 221 * 根據修正完的pos和lastValid,初始化陣列下標,以便繼續處理下一次請求 222 */ 223 getInputBuffer().nextRequest(); 224 getOutputBuffer().nextRequest(); 225 } 226 } 227 228 if (!disableUploadTimeout) { 229 if(endpoint.getSoTimeout() > 0) { 230 setSocketTimeout(endpoint.getSoTimeout()); 231 } else { 232 setSocketTimeout(0); 233 } 234 } 235 236 rp.setStage(org.apache.coyote.Constants.STAGE_KEEPALIVE); 237 238 if (breakKeepAliveLoop(socketWrapper)) { 239 break; 240 } 241 } 242 243 rp.setStage(org.apache.coyote.Constants.STAGE_ENDED); 244 245 if (getErrorState().isError() || endpoint.isPaused()) { 246 return SocketState.CLOSED; 247 } else if (isAsync() || comet) { 248 return SocketState.LONG; 249 } else if (isUpgrade()) { 250 return SocketState.UPGRADING; 251 } else if (getUpgradeInbound() != null) { 252 return SocketState.UPGRADING_TOMCAT; 253 } else { 254 if (sendfileInProgress) { 255 return SocketState.SENDFILE; 256 } else { 257 if (openSocket) { 258 if (readComplete) { 259 return SocketState.OPEN; 260 } else { 261 return SocketState.LONG; 262 } 263 } else { 264 return SocketState.CLOSED; 265 } 266 } 267 } 268 }
這個方法是關鍵中的關鍵,幾乎大部分的邏輯都在這里
11-12行,獲取socket的InputStream和OutputStream,這2個物件分別是從作業系統緩沖區中讀取資料以及寫入資料到緩沖區,
1 @Override 2 protected void init(SocketWrapper<Socket> socketWrapper, 3 AbstractEndpoint<Socket> endpoint) throws IOException { 4 inputStream = socketWrapper.getSocket().getInputStream(); 5 }
46行,設定socket超時時間,以及第一次讀取socket中資料,這里可能只是讀取部分資料,后面會多次讀取,
49行,讀取HTTP請求行資料
73行,讀取請求頭資料
150行,將封裝好的request和response物件傳給容器,最終給到servlet處理業務邏輯,這行執行完成,表明程式員開發的業務邏輯已經執行完畢
206行,當前請求收尾:如果servlet中沒有讀取獲取只讀取部分請求體,這里讀取完畢,并修正下面pos值
223行,根據pos和lastValid值,初始化buf陣列下下標:lastValid=lastValid-pos;pos=0
這幾個步驟處理比較復雜,主要有以下幾點:
1、while回圈用于保證長連接情況下一個socket能處理多個HTTP請求,
2、buf陣列用戶存放讀取自socket的所有請求資料,包括:HTTP請求行,請求頭,以及請求體,演變程序如下
初始化大小為8KB的位元組陣列buf,下標pos=0,lastValid=0

每次都是從socket中讀取一定長度的資料到buf陣列,注意:一次讀取的資料沒有任何規律可言,可能只有請求行,也可能包括請求行和請求頭,甚至還有可能包括請求體,

一個重點:HTTP請求GET方法,請求入參大小有限制,我們很多人大概都知道這個機制,可是并不知道,為啥又限制,以及限制到底是多大,其實Tomcat在讀取請求資料的時候,分了兩種情況
1、請求行和請求頭
Tomcat讀取請求行和請求頭后,最終陣列會變成

當請求頭讀完后,陣列會包含兩部分,如果讀取的程序中,lastValid==buf.length,這時會拋出例外:iib.requestheadertoolarge.error!原來如此,其實Tomcat是想請求行和請求頭一起打包放在陣列中,只要大小不超過8KB,都OK,如果超過就拋例外,現在明白了啊,并不是GET方法請求入參大小有限制,當然,這里都是默認的大小,程式員可以自行修改配置,
2、讀取請求體
上面演示了讀取請求行和請求頭的,它們只在一個buf陣列中完成,而請求體不一樣,我們知道,請求體的資料大小可以是任意的,因此Tomcat不能做任何限制

如果buf中剩余大小>4500,Tomcat每次回從socket中讀取大小為buf.length-end的資料放入buf中,pos=end,lastValid=pos+實際讀取資料的長度,注意這里每次存放的位置都是從end開始,可以看出請求體讀取的時候是采用覆寫的方式,即上次讀取的資料下個回圈在讀取的時候就被覆寫了,因此也可以看出請求體的讀取如果業務代碼不做保存,那就沒法再次讀取的,切記!
當buf陣列中剩余大小不足4500時,Tomcat便會重新初始化一個新的陣列賦給變數buf(原先的陣列由于沒有了參考,交由JVM垃圾回收),在新的buf中完成請求體的讀取,具體邏輯同上,
下面我們來欣賞一下資料讀取的代碼,感受下作者高深的編程思想
1 protected boolean fill(boolean block) throws IOException { 2 3 int nRead = 0; 4 5 /** 6 * 這個核心就是讀取socket中資料到緩沖區buf中,回圈讀取,2種情況 7 * 1、請求行和請求頭:不能超過緩沖區大小(默認8kb),如果超過,則拋例外,讀完后將parsingHeader設定為false 8 * 2、請求行:沒有任何大小限制,回圈讀取,如果剩下的少于4500個位元組,則會重新創建buf陣列,從頭開始讀取,直到讀完位置,注意!buf原先參考的陣列們,等待GC 9 */ 10 if (parsingHeader) { 11 12 /** 13 * 從socket中讀取資料大于tomcat中緩沖區buf的長度,直接拋例外,這里有兩點 14 * 1、這個就是我們很多時候很多人說的,get請求url不能過長的原因,其實是header和url等總大小不能超過8kb 15 * 2、這里的buf非常總要,它是InternalInputBuffer的屬性,是一個位元組資料,用戶暫存從socket中讀取的資料,比如:請求行,請求頭、請求體 16 */ 17 if (lastValid == buf.length) { 18 throw new IllegalArgumentException 19 (sm.getString("iib.requestheadertoolarge.error")); 20 } 21 22 // 將socket中的資料讀到緩沖區buf中,注意!這里就是BIO之所以難懂的關鍵所在,它會阻塞! 23 // 這個方法會阻塞,如果沒有資料可讀,則會一直阻塞,有資料,則移動lastValid位置 24 nRead = inputStream.read(buf, pos, buf.length - lastValid); 25 if (nRead > 0) { 26 lastValid = pos + nRead; 27 } 28 29 } else { 30 31 if (buf.length - end < 4500) { 32 // In this case, the request header was really large, so we allocate a 33 // brand new one; the old one will get GCed when subsequent requests 34 // clear all references 35 buf = new byte[buf.length]; 36 end = 0; 37 } 38 pos = end; 39 lastValid = pos; 40 nRead = inputStream.read(buf, pos, buf.length - lastValid); 41 if (nRead > 0) { 42 lastValid = pos + nRead; 43 } 44 45 } 46 // 原則上這個方法要么阻塞著,要么就回傳true 47 return (nRead > 0); 48 49 }
3、請求收尾
如果請求是長連接,那么一次HTTP處理完成后,socket通道并未關閉,那么怎么樣再次處理下個請求發送的資料呢?
我們可以想象,如果是長連接的,也就是客戶端在發送上一個請求的請求體后,立馬又發送了下一個請求的請求行,所以分兩組情況:
1、最后一次讀取請求體,剛好讀完

pos=lastValid=0
2、最后一次讀取請求體,順便讀出了部分下一次請求的資料

可以看出:首先要講pos修正為請求體實際截止的位置,然后再初始化下標:lastValid=lastValid-pos,pos=0,
到這里一次請求幾把已經結束,如果是長連接,便會再次進去while回圈,繼續讀取客戶端發送的資料;如果非長連接,則就關閉socket鏈接,請求結束!
總結
我們從Tomcat角度深入了解了HTTP請求的處理程序,其中涉及網路、作業系統、記憶體等知識,經過這次學習,我對以下幾點有了更深的理解
1、何為應用層協議,HTTP請求的本質其實也是TCP傳輸
2、長連接的本質
3、Tomcat底層資料存盤模式
4、HTTP請求是如果到達servlet的,這是很多MVC框架的基石
5、Tomcat作者過人的設計思想和高深的編程功底
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/190788.html
標籤:Java
上一篇:Set集合重復性?
