主頁 > 後端開發 > Tomcat視角看一次http請求

Tomcat視角看一次http請求

2020-10-25 21:25:14 後端開發

一次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集合重復性?

下一篇:JSP+Servlet+JDBC+mysql實作的學生成績管理系統

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more