An http request from the perspective of Tomcat

One HTTP request

 

 

As shown in the figure above, a complete HTT request roughly includes the following steps:

  • Domain name resolution
  • TCP connection (three handshakes)
  • Send request data after connection established
  • Server processing request
  • Send response data
  • Browser parsing response data
  • Browser rendering front page

The http request process seems simple, but if you are curious about anything, you need to understand the above links from the bottom. In fact, it is very complex and requires a lot of knowledge, covering all the professional subjects you have studied in University? Let's take a slow look at it later). Today, we stand in the perspective of Tomcat to understand what http requests have gone through in Tomcat.

We will focus on the following steps: Tomcat receiving request, request data reading, container processing business logic, how to connect multiple requests, simple response, etc.

 

Introduction to underlying concepts

1. About the network

  

The well-known middleware (mysql, redis, kafka, rabbitmq, dubbo, etc.) is actually a running service on the network. They receive data from the customer service end and return it to the client after complex logical processing. Of course, Tomcat is no exception. It can receive requests from various protocols on the client side, including HTTP, APR, etc. Receiving and returning requests here naturally involve network programming. In java, network programming is encapsulated as Socket interface by JDK. We can understand Tomcat as a Socket server program that runs all the time after startup. It constantly listens to requests from the outside world (in fact, TCP requests, whether you understand HTTP or other application layer protocols, in fact Are the underlying TCP requests? ). Those who are familiar with Socket must ask, is Tomcat BIO or NIO? Yes, as long as it's network transmission, it can't escape this problem. First of all, simply add that the early Tomcat is BIO, and the specific version is the default BIO before 7.0 (including), which supports the configuration of NIO. 8.0 starts to fully default NIO. Of course, there is a difference between NIO after 8.0 and NIO before, which I will mention in the following articles. The process flow of BIO model is as follows:

 

2. Underlying data storage

Data is transmitted to Tomcat's server through the network. First, it will be in the operating system buffer revbuf, then Tomcat will read data from revbuf, and finally it will be stored in its own JVM. This step may seem simple, but it is a classic theory of modern operating system. The data stored in the operating system and Tomcat are byte arrays. Let's take the byte data buf in Tomcat as an example:

 

After the data arrives in Tomcat, it will go through the above byte array, and then it will be encapsulated as a request object and passed to the container.

 

Source code analysis

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      * Connection receive class to accept all requests from clients
 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                 // Inner loop, temporary interrupt processing
20                 while (paused && running) {
21                     state = AcceptorState.PAUSED;
22                     try {
23                         Thread.sleep(50);
24                     } catch (InterruptedException e) {
25                         // Ignore
26                     }
27                 }
28 
29                 //close
30                 if (!running) {
31                     break;
32                 }
33                 state = AcceptorState.RUNNING;
34 
35                 try {
36                     //if we have reached max connections, wait
37                     // inherit AQS,The total number of control connections cannot exceed the maximum
38                     countUpOrAwaitConnection();
39 
40                     Socket socket = null;
41                     try {
42                         // Accept the next incoming connection from the server
43                         // socket
44                         // receive socket Request, the most important entrance!!!
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 inherits the internal class acceptor of AbstractEndpoint class, which implements the Runnable interface, indicating that it is a multithreaded task class and can be put into the thread pool. After Tomcat starts, the outer acceptor begins to execute the run method.

Line 11 implements the run method in the Runnable interface. You can see that the run method is a while loop. Only when running=false can the loop jump out.

Line 45, this is the key. After tomcat starts, if no request comes in, the current main thread will block here. Until the first request arrives, the socket of the current request is obtained.

59 lines, and the resulting socekt is handed over to the processSocket() method.

Other code is the judgment of some abnormal conditions, which is ignored here.

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             // Packed socketProcessor Throw thread pool
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     }

The process method wraps the socket as a SocketWrapper and sets it to the property of SocketProcessor

In line 12, the SocketProcessor class implements the Runnable interface, which is equivalent to throwing the currently requested socket into the thread pool.

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                         // Deal with it here HTTPS Related logic
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 Handle 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     }

This class mainly deals with http requests received in buying a socket connection

45 lines, the socket is handed over to the handler for handling, and the others are exception handling

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                         // handle 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         }

Line 68, continue to pass the socket down

  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          * Set the InputStream and OutStream of socket for later reading data and response
 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          * Long connection correlation, judge whether the current socket continues to process the next request
 28          */
 29         if (disableKeepAlive()) {
 30             socketWrapper.setKeepAliveLeft(0);
 31         }
 32 
 33         /**
 34          * Handle the request in socket. In the long connection mode, the current loop will be executed all the time
 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,Set socket timeout
 44                  * 2,Read data from socket for the first time
 45                  */
 46                 setRequestLineReadTimeout();
 47 
 48                 // read HTTP Request line data
 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                     // Set request line and request header size, note that this is important!
 68                     request.getMimeHeaders().setLimit(endpoint.getMaxHeaderCount());
 69                     // Set as many as you can cookie number
 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                      * The encapsulated request and response objects are handed over to the container for processing
146                      * service-->host-->context-->wrapper-->servlet
147                      * This is very important. The servlet code we wrote is called here. It follows the servlet specification
148                      * After processing, the servlet developed by the programmer has been executed
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                  * Current request close out
204                  * Judge whether the request body has been read, if not, read it, and correct the 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                      * Initialize the array subscript according to the modified pos and lastValid to continue processing the next request
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     }

This method is the key in the key, and most of the logic is here

Line 11-12, get the InputStream and OutputStream of the socket. These two objects are respectively reading data from the operating system buffer and writing data to the buffer.

1 @Override
2     protected void init(SocketWrapper<Socket> socketWrapper,
3             AbstractEndpoint<Socket> endpoint) throws IOException {
4         inputStream = socketWrapper.getSocket().getInputStream();
5     }

Line 46, set the socket timeout, and read the data in the socket for the first time. Here, it may only read part of the data, which will be read several times later.

49 lines, read HTTP request line data

73 lines, read request header data

150 lines, pass the encapsulated request and response objects to the container, and finally to the servlet to process the business logic. This line is completed, indicating that the business logic developed by the programmer has been completed

Line 206, end of current request: if the servlet does not read to get only part of the request body, read it here and correct the following pos value

223 lines, initialize the subscript of buf array according to pos and lastValid values: lastValid = lastValid pos; pos=0

The processing of these steps is relatively complex, mainly including the following points:

1. while loop is used to ensure that a socket can handle multiple HTTP requests in the case of long connection.

2. buf array user stores all request data read from socket, including HTTP request line, request header, and request body. The evolution process is as follows

Initialize byte array buf with size of 8KB, subscript pos=0,lastValid=0

 

 

Each time, a certain length of data is read from the socket to the buf array. Note: there is no rule for the data read at a time. It may only be the request line, or it may include the request line and the request header, or even the request body.

     

 

 

 

One important point: HTTP request GET method, there is a limit to the size of the request input parameter. Many of us probably know this mechanism, but we don't know why it is limited and how big it is. In fact, when Tomcat reads the request data, there are two situations

1. Request line and request header

After Tomcat reads the request line and request header, the final array will become

When the request header is read, the array will contain two parts. If lastValid==buf.length is read, an exception will be thrown: iib.requestheadertoolarge.error! So it is. In fact, Tomcat wants the request line and the request header to be packed together in the array. As long as the size is no more than 8KB, it's OK. If it's over, it's OK to throw an exception. Now you see, it's not that there is a limit to the size of the GET method's request input parameter. Of course, it's the default size. The programmer can modify the configuration by himself.

2. Read request body

The above shows reading request lines and request headers, which are only completed in a buf array. The request body is different. We know that the data size of the request body can be arbitrary, so Tomcat can't make any restrictions

 

 

If the remaining size in buf is more than 4500, Tomcat will read the data with the size of buf.length-end from socket every time and put it into buf, pos=end, lastValid=pos + the length of the data actually read. Note that the storage position here starts from the end. It can be seen that the request body is overwritten when it is read, that is, the next cycle of the last read data is overwritten when it is read. Therefore, it can also be seen that if the business code is not saved, it cannot be read again. Remember!

When the remaining size of the buf array is less than 4500, Tomcat will reinitialize a new array and assign it to the variable buf (because the original array has no reference, it will be garbage collected by the JVM). The specific logic is the same as above.

Let's enjoy the code of data reading and feel the author's profound programming ideas

 1 protected boolean fill(boolean block) throws IOException {
 2 
 3         int nRead = 0;
 4 
 5         /**
 6          * The core is to read the data in the socket into the buffer buf, and then read it circularly
 7          * 1,Request line and request header: the buffer size cannot be exceeded (8KB by default). If it is exceeded, throw an exception. After reading it, set the parsengheader to false
 8          * 2,Request line: there is no size limit, read circularly. If there are less than 4500 bytes left, the buf array will be recreated and read from the beginning to the end. Note! BUF arrays, waiting for GC
 9          */
10         if (parsingHeader) {
11 
12             /**
13              * Reading data from socket is larger than the length of buffer buf in tomcat, and throwing exceptions directly. Here are two points
14              * 1,This is why many of us say that the get request url cannot be too long. In fact, the total size of header and url cannot exceed 8kb
15              * 2,The buffer here is very important. It is the attribute of InternalInputBuffer and a byte of data. Users temporarily store the data read from socket, such as request line, request header and request body
16              */
17             if (lastValid == buf.length) {
18                 throw new IllegalArgumentException
19                     (sm.getString("iib.requestheadertoolarge.error"));
20             }
21 
22             // take socket Data in read to buffer buf Middle, attention! This is it BIO What's so hard to understand is that it will block!
23             // This method will block. If there is no data readable, it will block all the time. If there is data, it will move lastValid position
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         // In principle, this method either blocks or returns true
47         return (nRead > 0);
48 
49     }

 

3. Request closure

If the request is a long connection, after one HTTP processing, the socket channel is not closed. How to process the data sent by the next request again?

We can imagine that if the connection is long, that is, after the client sends the request body of the previous request, it immediately sends the request line of the next request. So there are two groups:

1. Last read request body, just finished reading

pos=lastValid=0

2. Read the request body for the last time, and read some data of the next request by the way

 

It can be seen that: first, we need to modify pos to the actual cut-off position of the request body, and then initialize the subscript: lastvalid = lastvalid pos, pos=0.

Here, several requests have ended. If it is a long connection, it will go into the while loop again and continue to read the data sent by the client. If it is not a long connection, it will close the socket link and end the request!

 

summary

We have a deep understanding of HTTP request processing from the perspective of Tomcat, including network, operating system, memory and other knowledge. After this study, I have a deeper understanding of the following points

1. What is the application layer protocol? The essence of HTTP request is actually TCP transmission

2. The essence of long connection

3. Tomcat underlying data storage mode

4. If HTTP requests arrive at servlet s, this is the cornerstone of many MVC frameworks

5. Tomcat author's excellent design ideas and deep programming skills

Tags: Java socket Tomcat Apache network

Posted on Mon, 18 May 2020 01:50:33 -0400 by etrooper