Tomcat 7 的一次請求分析(二)Socket 轉換成內部請求物件

預流發表於2018-02-01

先拋開之前所看到的 Tomcat 原始碼不談,Tomcat 作為一個用 Java 實現的 Web 伺服器,如果讓你來實現,那麼從何入手?

這裡首先需要釐清的是 Web 伺服器的概念,谷歌了一下,發現這條解釋還算靠譜點,【在網路環境下可以向發出請求的瀏覽器提供文件的程式】。重點有兩條:1.網路環境下,2.能夠給出響應。用 Java 寫過網路通訊程式的都知道,這裡必然會用到 Socket 程式設計。我們自己要實現的伺服器程式作為 Socket 程式設計裡的服務端,瀏覽器作為 Socket 程式設計裡的客戶端。

要理解 Tomcat 原理,Socket 程式設計這塊的基本原理必須得了解,google 一把一大堆,這裡不再單獨做介紹。下面給出一個伺服器端最簡單的響應客戶端請求的虛擬碼示例:

ServerSocket serverSocket  = new ServerSocket(8080, 1,
		InetAddress.getByName(“localhost”));
Socket socket = null;
InputStream is = null;
OutputStream os = null;
try {
	socket = serverSocket.accept();//1.監聽到客戶端的連線
	is = socket.getInputStream();
	os = socket.getOutputStream();
	Request request = Util.getRequest(is);//2.從輸入流中讀取資料,並根據Http協議轉換成請求
	Response response = Util.service(request);//伺服器內部根據請求資訊給出響應資訊
	os.writeResponse(response);//3.將響應資訊寫到輸出流
} catch (Exception e) {
	e.printStackTrace();
} finally {//4.關閉輸入輸出流及連線
	if (is != null) {
		is.close();
	}
	if (os != null) {
		os.close();
	}
	socket.close();
}
複製程式碼

瀏覽器和 Web 伺服器的一次互動過程分四步:連線、請求、響應、關閉。前一篇文章分析到的接收器執行緒,如前文開始截圖裡的 http-bio-8080-Acceptor-0 ,這個執行緒的實現類org.apache.tomcat.util.net.JIoEndpoint.Acceptor,原始碼如下:

     1	    // --------------------------------------------------- Acceptor Inner Class
     2	    /**
     3	     * The background thread that listens for incoming TCP/IP connections and
     4	     * hands them off to an appropriate processor.
     5	     */
     6	    protected class Acceptor extends AbstractEndpoint.Acceptor {
     7	
     8	        @Override
     9	        public void run() {
    10	
    11	            int errorDelay = 0;
    12	
    13	            // Loop until we receive a shutdown command
    14	            while (running) {
    15	
    16	                // Loop if endpoint is paused
    17	                while (paused && running) {
    18	                    state = AcceptorState.PAUSED;
    19	                    try {
    20	                        Thread.sleep(50);
    21	                    } catch (InterruptedException e) {
    22	                        // Ignore
    23	                    }
    24	                }
    25	
    26	                if (!running) {
    27	                    break;
    28	                }
    29	                state = AcceptorState.RUNNING;
    30	
    31	                try {
    32	                    //if we have reached max connections, wait
    33	                    countUpOrAwaitConnection();
    34	
    35	                    Socket socket = null;
    36	                    try {
    37	                        // Accept the next incoming connection from the server
    38	                        // socket
    39	                        socket = serverSocketFactory.acceptSocket(serverSocket);
    40	                    } catch (IOException ioe) {
    41	                        countDownConnection();
    42	                        // Introduce delay if necessary
    43	                        errorDelay = handleExceptionWithDelay(errorDelay);
    44	                        // re-throw
    45	                        throw ioe;
    46	                    }
    47	                    // Successful accept, reset the error delay
    48	                    errorDelay = 0;
    49	
    50	                    // Configure the socket
    51	                    if (running && !paused && setSocketOptions(socket)) {
    52	                        // Hand this socket off to an appropriate processor
    53	                        if (!processSocket(socket)) {
    54	                            countDownConnection();
    55	                            // Close socket right away
    56	                            closeSocket(socket);
    57	                        }
    58	                    } else {
    59	                        countDownConnection();
    60	                        // Close socket right away
    61	                        closeSocket(socket);
    62	                    }
    63	                } catch (IOException x) {
    64	                    if (running) {
    65	                        log.error(sm.getString("endpoint.accept.fail"), x);
    66	                    }
    67	                } catch (NullPointerException npe) {
    68	                    if (running) {
    69	                        log.error(sm.getString("endpoint.accept.fail"), npe);
    70	                    }
    71	                } catch (Throwable t) {
    72	                    ExceptionUtils.handleThrowable(t);
    73	                    log.error(sm.getString("endpoint.accept.fail"), t);
    74	                }
    75	            }
    76	            state = AcceptorState.ENDED;
    77	        }
    78	    }
複製程式碼

第 39 行做的事就是上面虛擬碼示例裡的監聽客戶端連線,監聽到連線後(即瀏覽器向伺服器發起一次請求)在第 53 行由 processSocket 方法來處理這次接收到的 Socket 連線。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	            // During shutdown, executor may be null - avoid NPE
     7	            if (!running) {
     8	                return false;
     9	            }
    10	            getExecutor().execute(new SocketProcessor(wrapper));
    11	        } catch (RejectedExecutionException x) {
    12	            log.warn("Socket processing request was rejected for:"+socket,x);
    13	            return false;
    14	        } catch (Throwable t) {
    15	            ExceptionUtils.handleThrowable(t);
    16	            // This means we got an OOM or similar creating a thread, or that
    17	            // the pool and its queue are full
    18	            log.error(sm.getString("endpoint.process.fail"), t);
    19	            return false;
    20	        }
    21	        return true;
    22	    }
複製程式碼

該方法中先把 Socket 包裝成 SocketWrapper ,用以內部處理。重點是第 10 行:getExecutor().execute(new SocketProcessor(wrapper))。如果按照上面虛擬碼中的處理方式,在有併發請求的情況下,一個請求沒有處理完成,伺服器將無法立即響應另一個請求。這裡做了一下改進,即在接收到一次 Socket 連線後另啟一個執行緒處理該連線,使當前執行緒不阻塞。

下面跟著 SocketProcessor 這個執行緒來看看,一次 Socket 連線是如何在 Tomcat 7 中被轉成內部的 Request 的。看下該執行緒的 run 方法:

     1	        @Override
     2	        public void run() {
     3	            boolean launch = false;
     4	            synchronized (socket) {
     5	                try {
     6	                    SocketState state = SocketState.OPEN;
     7	
     8	                    try {
     9	                        // SSL handshake
    10	                        serverSocketFactory.handshake(socket.getSocket());
    11	                    } catch (Throwable t) {
    12	                        ExceptionUtils.handleThrowable(t);
    13	                        if (log.isDebugEnabled()) {
    14	                            log.debug(sm.getString("endpoint.err.handshake"), t);
    15	                        }
    16	                        // Tell to close the socket
    17	                        state = SocketState.CLOSED;
    18	                    }
    19	
    20	                    if ((state != SocketState.CLOSED)) {
    21	                        if (status == null) {
    22	                            state = handler.process(socket, SocketStatus.OPEN);
    23	                        } else {
    24	                            state = handler.process(socket,status);
    25	                        }
    26	                    }
    27	                    if (state == SocketState.CLOSED) {
    28	                        // Close socket
    29	                        if (log.isTraceEnabled()) {
    30	                            log.trace("Closing socket:"+socket);
    31	                        }
    32	                        countDownConnection();
    33	                        try {
    34	                            socket.getSocket().close();
    35	                        } catch (IOException e) {
    36	                            // Ignore
    37	                        }
    38	                    } else if (state == SocketState.OPEN ||
    39	                            state == SocketState.UPGRADING  ||
    40	                            state == SocketState.UPGRADED){
    41	                        socket.setKeptAlive(true);
    42	                        socket.access();
    43	                        launch = true;
    44	                    } else if (state == SocketState.LONG) {
    45	                        socket.access();
    46	                        waitingRequests.add(socket);
    47	                    }
    48	                } finally {
    49	                    if (launch) {
    50	                        try {
    51	                            getExecutor().execute(new SocketProcessor(socket, SocketStatus.OPEN));
    52	                        } catch (RejectedExecutionException x) {
    53	                            log.warn("Socket reprocessing request was rejected for:"+socket,x);
    54	                            try {
    55	                                //unable to handle connection at this time
    56	                                handler.process(socket, SocketStatus.DISCONNECT);
    57	                            } finally {
    58	                                countDownConnection();
    59	                            }
    60	
    61	
    62	                        } catch (NullPointerException npe) {
    63	                            if (running) {
    64	                                log.error(sm.getString("endpoint.launch.fail"),
    65	                                        npe);
    66	                            }
    67	                        }
    68	                    }
    69	                }
    70	            }
    71	            socket = null;
    72	            // Finish up this request
    73	        }
    74	
    75	    }
複製程式碼

預設情況下會走到第 22 行,呼叫 handler 物件的 process 方法,這裡 handler 物件實際上是 Http11ConnectionHandler 類的例項,該物件的初始化過程是在 org.apache.coyote.http11.Http11Protocol物件的構造方法中:

    public Http11Protocol() {
        endpoint = new JIoEndpoint();
        cHandler = new Http11ConnectionHandler(this);
        ((JIoEndpoint) endpoint).setHandler(cHandler);
        setSoLinger(Constants.DEFAULT_CONNECTION_LINGER);
        setSoTimeout(Constants.DEFAULT_CONNECTION_TIMEOUT);
        setTcpNoDelay(Constants.DEFAULT_TCP_NO_DELAY);
    }
複製程式碼

所以需要到org.apache.coyote.http11.Http11Protocol類的靜態內部類 Http11ConnectionHandler 中找到 process 方法的定義,但當前定義裡面沒有,這個方法是在其父類org.apache.coyote.AbstractProtocol.AbstractConnectionHandler中定義的:


     1	        public SocketState process(SocketWrapper<S> socket,
     2	                SocketStatus status) {
     3	            Processor<S> processor = connections.remove(socket.getSocket());
     4	
     5	            if (status == SocketStatus.DISCONNECT && processor == null) {
     6	                //nothing more to be done endpoint requested a close
     7	                //and there are no object associated with this connection
     8	                return SocketState.CLOSED;
     9	            }
    10	
    11	            socket.setAsync(false);
    12	
    13	            try {
    14	                if (processor == null) {
    15	                    processor = recycledProcessors.poll();
    16	                }
    17	                if (processor == null) {
    18	                    processor = createProcessor();
    19	                }
    20	
    21	                initSsl(socket, processor);
    22	
    23	                SocketState state = SocketState.CLOSED;
    24	                do {
    25	                    if (status == SocketStatus.DISCONNECT &&
    26	                            !processor.isComet()) {
    27	                        // Do nothing here, just wait for it to get recycled
    28	                        // Don't do this for Comet we need to generate an end
    29	                        // event (see BZ 54022)
    30	                    } else if (processor.isAsync() ||
    31	                            state == SocketState.ASYNC_END) {
    32	                        state = processor.asyncDispatch(status);
    33	                    } else if (processor.isComet()) {
    34	                        state = processor.event(status);
    35	                    } else if (processor.isUpgrade()) {
    36	                        state = processor.upgradeDispatch();
    37	                    } else {
    38	                        state = processor.process(socket);
    39	                    }
    40	    
    41	                    if (state != SocketState.CLOSED && processor.isAsync()) {
    42	                        state = processor.asyncPostProcess();
    43	                    }
    44	
    45	                    if (state == SocketState.UPGRADING) {
    46	                        // Get the UpgradeInbound handler
    47	                        UpgradeInbound inbound = processor.getUpgradeInbound();
    48	                        // Release the Http11 processor to be re-used
    49	                        release(socket, processor, false, false);
    50	                        // Create the light-weight upgrade processor
    51	                        processor = createUpgradeProcessor(socket, inbound);
    52	                        inbound.onUpgradeComplete();
    53	                    }
    54	                } while (state == SocketState.ASYNC_END ||
    55	                        state == SocketState.UPGRADING);
    56	
    57	                if (state == SocketState.LONG) {
    58	                    // In the middle of processing a request/response. Keep the
    59	                    // socket associated with the processor. Exact requirements
    60	                    // depend on type of long poll
    61	                    longPoll(socket, processor);
    62	                } else if (state == SocketState.OPEN) {
    63	                    // In keep-alive but between requests. OK to recycle
    64	                    // processor. Continue to poll for the next request.
    65	                    release(socket, processor, false, true);
    66	                } else if (state == SocketState.SENDFILE) {
    67	                    // Sendfile in progress. If it fails, the socket will be
    68	                    // closed. If it works, the socket will be re-added to the
    69	                    // poller
    70	                    release(socket, processor, false, false);
    71	                } else if (state == SocketState.UPGRADED) {
    72	                    // Need to keep the connection associated with the processor
    73	                    longPoll(socket, processor);
    74	                } else {
    75	                    // Connection closed. OK to recycle the processor.
    76	                    if (!(processor instanceof UpgradeProcessor)) {
    77	                        release(socket, processor, true, false);
    78	                    }
    79	                }
    80	                return state;
    81	            } catch(java.net.SocketException e) {
    82	                // SocketExceptions are normal
    83	                getLog().debug(sm.getString(
    84	                        "abstractConnectionHandler.socketexception.debug"), e);
    85	            } catch (java.io.IOException e) {
    86	                // IOExceptions are normal
    87	                getLog().debug(sm.getString(
    88	                        "abstractConnectionHandler.ioexception.debug"), e);
    89	            }
    90	            // Future developers: if you discover any other
    91	            // rare-but-nonfatal exceptions, catch them here, and log as
    92	            // above.
    93	            catch (Throwable e) {
    94	                ExceptionUtils.handleThrowable(e);
    95	                // any other exception or error is odd. Here we log it
    96	                // with "ERROR" level, so it will show up even on
    97	                // less-than-verbose logs.
    98	                getLog().error(
    99	                        sm.getString("abstractConnectionHandler.error"), e);
   100	            }
   101	            // Don't try to add upgrade processors back into the pool
   102	            if (!(processor instanceof UpgradeProcessor)) {
   103	                release(socket, processor, true, false);
   104	            }
   105	            return SocketState.CLOSED;
   106	        }
複製程式碼

重點在第 38 行,呼叫 processor 的 process 方法處理 socket 。而 processor 物件在第 18 行通過 createProcessor 方法建立出來的,createProcessor 方法在當前類裡面是抽象方法,預設情況下呼叫的具體實現類在上面提到的 Http11ConnectionHandler 類中:

     1	        @Override
     2	        protected Http11Processor createProcessor() {
     3	            Http11Processor processor = new Http11Processor(
     4	                    proto.getMaxHttpHeaderSize(), (JIoEndpoint)proto.endpoint,
     5	                    proto.getMaxTrailerSize());
     6	            processor.setAdapter(proto.adapter);
     7	            processor.setMaxKeepAliveRequests(proto.getMaxKeepAliveRequests());
     8	            processor.setKeepAliveTimeout(proto.getKeepAliveTimeout());
     9	            processor.setConnectionUploadTimeout(
    10	                    proto.getConnectionUploadTimeout());
    11	            processor.setDisableUploadTimeout(proto.getDisableUploadTimeout());
    12	            processor.setCompressionMinSize(proto.getCompressionMinSize());
    13	            processor.setCompression(proto.getCompression());
    14	            processor.setNoCompressionUserAgents(proto.getNoCompressionUserAgents());
    15	            processor.setCompressableMimeTypes(proto.getCompressableMimeTypes());
    16	            processor.setRestrictedUserAgents(proto.getRestrictedUserAgents());
    17	            processor.setSocketBuffer(proto.getSocketBuffer());
    18	            processor.setMaxSavePostSize(proto.getMaxSavePostSize());
    19	            processor.setServer(proto.getServer());
    20	            processor.setDisableKeepAlivePercentage(
    21	                    proto.getDisableKeepAlivePercentage());
    22	            register(processor);
    23	            return processor;
    24	        }
複製程式碼

此時的 processor 物件是 Http11Processor 類的例項,再看上一段提到的 processor.process 方法,最終會執行到 Http11Processor 類(因為該類中沒有定義 process 方法)的父類org.apache.coyote.http11.AbstractHttp11Processor中的 process 方法。

為了方便理解,下面的時序圖列出從 Acceptor 執行緒的 run 方法到 AbstractHttp11Processor 類的 process 方法的關鍵方法呼叫過程:

Tomcat 7 的一次請求分析(二)Socket 轉換成內部請求物件
接下來分析 org.apache.coyote.http11.AbstractHttp11Processor 類的 process 方法:


     1	    @Override
     2	    public SocketState process(SocketWrapper<S> socketWrapper)
     3	        throws IOException {
     4	        RequestInfo rp = request.getRequestProcessor();
     5	        rp.setStage(org.apache.coyote.Constants.STAGE_PARSE);
     6	
     7	        // Setting up the I/O
     8	        setSocketWrapper(socketWrapper);
     9	        getInputBuffer().init(socketWrapper, endpoint);
    10	        getOutputBuffer().init(socketWrapper, endpoint);
    11	
    12	        // Flags
    13	        error = false;
    14	        keepAlive = true;
    15	        comet = false;
    16	        openSocket = false;
    17	        sendfileInProgress = false;
    18	        readComplete = true;
    19	        if (endpoint.getUsePolling()) {
    20	            keptAlive = false;
    21	        } else {
    22	            keptAlive = socketWrapper.isKeptAlive();
    23	        }
    24	
    25	        if (disableKeepAlive()) {
    26	            socketWrapper.setKeepAliveLeft(0);
    27	        }
    28	
    29	        while (!error && keepAlive && !comet && !isAsync() &&
    30	                upgradeInbound == null && !endpoint.isPaused()) {
    31	
    32	            // Parsing the request header
    33	            try {
    34	                setRequestLineReadTimeout();
    35	
    36	                if (!getInputBuffer().parseRequestLine(keptAlive)) {
    37	                    if (handleIncompleteRequestLineRead()) {
    38	                        break;
    39	                    }
    40	                }
    41	
    42	                if (endpoint.isPaused()) {
    43	                    // 503 - Service unavailable
    44	                    response.setStatus(503);
    45	                    error = true;
    46	                } else {
    47	                    // Make sure that connectors that are non-blocking during
    48	                    // header processing (NIO) only set the start time the first
    49	                    // time a request is processed.
    50	                    if (request.getStartTime() < 0) {
    51	                        request.setStartTime(System.currentTimeMillis());
    52	                    }
    53	                    keptAlive = true;
    54	                    // Set this every time in case limit has been changed via JMX
    55	                    request.getMimeHeaders().setLimit(endpoint.getMaxHeaderCount());
    56	                    // Currently only NIO will ever return false here
    57	                    if (!getInputBuffer().parseHeaders()) {
    58	                        // We've read part of the request, don't recycle it
    59	                        // instead associate it with the socket
    60	                        openSocket = true;
    61	                        readComplete = false;
    62	                        break;
    63	                    }
    64	                    if (!disableUploadTimeout) {
    65	                        setSocketTimeout(connectionUploadTimeout);
    66	                    }
    67	                }
    68	            } catch (IOException e) {
    69	                if (getLog().isDebugEnabled()) {
    70	                    getLog().debug(
    71	                            sm.getString("http11processor.header.parse"), e);
    72	                }
    73	                error = true;
    74	                break;
    75	            } catch (Throwable t) {
    76	                ExceptionUtils.handleThrowable(t);
    77	                UserDataHelper.Mode logMode = userDataHelper.getNextMode();
    78	                if (logMode != null) {
    79	                    String message = sm.getString(
    80	                            "http11processor.header.parse");
    81	                    switch (logMode) {
    82	                        case INFO_THEN_DEBUG:
    83	                            message += sm.getString(
    84	                                    "http11processor.fallToDebug");
    85	                            //$FALL-THROUGH$
    86	                        case INFO:
    87	                            getLog().info(message);
    88	                            break;
    89	                        case DEBUG:
    90	                            getLog().debug(message);
    91	                    }
    92	                }
    93	                // 400 - Bad Request
    94	                response.setStatus(400);
    95	                adapter.log(request, response, 0);
    96	                error = true;
    97	            }
    98	
    99	            if (!error) {
   100	                // Setting up filters, and parse some request headers
   101	                rp.setStage(org.apache.coyote.Constants.STAGE_PREPARE);
   102	                try {
   103	                    prepareRequest();
   104	                } catch (Throwable t) {
   105	                    ExceptionUtils.handleThrowable(t);
   106	                    if (getLog().isDebugEnabled()) {
   107	                        getLog().debug(sm.getString(
   108	                                "http11processor.request.prepare"), t);
   109	                    }
   110	                    // 400 - Internal Server Error
   111	                    response.setStatus(400);
   112	                    adapter.log(request, response, 0);
   113	                    error = true;
   114	                }
   115	            }
   116	
   117	            if (maxKeepAliveRequests == 1) {
   118	                keepAlive = false;
   119	            } else if (maxKeepAliveRequests > 0 &&
   120	                    socketWrapper.decrementKeepAlive() <= 0) {
   121	                keepAlive = false;
   122	            }
   123	
   124	            // Process the request in the adapter
   125	            if (!error) {
   126	                try {
   127	                    rp.setStage(org.apache.coyote.Constants.STAGE_SERVICE);
   128	                    adapter.service(request, response);
   129	                    // Handle when the response was committed before a serious
   130	                    // error occurred.  Throwing a ServletException should both
   131	                    // set the status to 500 and set the errorException.
   132	                    // If we fail here, then the response is likely already
   133	                    // committed, so we can't try and set headers.
   134	                    if(keepAlive && !error) { // Avoid checking twice.
   135	                        error = response.getErrorException() != null ||
   136	                                (!isAsync() &&
   137	                                statusDropsConnection(response.getStatus()));
   138	                    }
   139	                    setCometTimeouts(socketWrapper);
   140	                } catch (InterruptedIOException e) {
   141	                    error = true;
   142	                } catch (HeadersTooLargeException e) {
   143	                    error = true;
   144	                    // The response should not have been committed but check it
   145	                    // anyway to be safe
   146	                    if (!response.isCommitted()) {
   147	                        response.reset();
   148	                        response.setStatus(500);
   149	                        response.setHeader("Connection", "close");
   150	                    }
   151	                } catch (Throwable t) {
   152	                    ExceptionUtils.handleThrowable(t);
   153	                    getLog().error(sm.getString(
   154	                            "http11processor.request.process"), t);
   155	                    // 500 - Internal Server Error
   156	                    response.setStatus(500);
   157	                    adapter.log(request, response, 0);
   158	                    error = true;
   159	                }
   160	            }
   161	
   162	            // Finish the handling of the request
   163	            rp.setStage(org.apache.coyote.Constants.STAGE_ENDINPUT);
   164	
   165	            if (!isAsync() && !comet) {
   166	                if (error) {
   167	                    // If we know we are closing the connection, don't drain
   168	                    // input. This way uploading a 100GB file doesn't tie up the
   169	                    // thread if the servlet has rejected it.
   170	                    getInputBuffer().setSwallowInput(false);
   171	                }
   172	                endRequest();
   173	            }
   174	
   175	            rp.setStage(org.apache.coyote.Constants.STAGE_ENDOUTPUT);
   176	
   177	            // If there was an error, make sure the request is counted as
   178	            // and error, and update the statistics counter
   179	            if (error) {
   180	                response.setStatus(500);
   181	            }
   182	            request.updateCounters();
   183	
   184	            if (!isAsync() && !comet || error) {
   185	                getInputBuffer().nextRequest();
   186	                getOutputBuffer().nextRequest();
   187	            }
   188	
   189	            if (!disableUploadTimeout) {
   190	                if(endpoint.getSoTimeout() > 0) {
   191	                    setSocketTimeout(endpoint.getSoTimeout());
   192	                } else {
   193	                    setSocketTimeout(0);
   194	                }
   195	            }
   196	
   197	            rp.setStage(org.apache.coyote.Constants.STAGE_KEEPALIVE);
   198	
   199	            if (breakKeepAliveLoop(socketWrapper)) {
   200	                break;
   201	            }
   202	        }
   203	
   204	        rp.setStage(org.apache.coyote.Constants.STAGE_ENDED);
   205	
   206	        if (error || endpoint.isPaused()) {
   207	            return SocketState.CLOSED;
   208	        } else if (isAsync() || comet) {
   209	            return SocketState.LONG;
   210	        } else if (isUpgrade()) {
   211	            return SocketState.UPGRADING;
   212	        } else {
   213	            if (sendfileInProgress) {
   214	                return SocketState.SENDFILE;
   215	            } else {
   216	                if (openSocket) {
   217	                    if (readComplete) {
   218	                        return SocketState.OPEN;
   219	                    } else {
   220	                        return SocketState.LONG;
   221	                    }
   222	                } else {
   223	                    return SocketState.CLOSED;
   224	                }
   225	            }
   226	        }
   227	    }
複製程式碼

從這個方法中可以清晰的看出解析請求的過程:第 7 到 10 行從 Socket 中獲取輸入輸出流,第 32 到 97 行解析請求行和請求頭,第 99 到 115 行校驗和解析請求頭中的屬性,第 125 到 160 行呼叫介面卡的 service 方法,第 172 行請求處理結束。

上面就是根據 Http 協議解析請求的總體流程。要理解上面提到的請求行、請求頭等術語,需要熟悉 Http 協議,這裡簡單介紹下 Http 協議中的標準請求資訊資料的格式:

請求資訊包括以下三條

  • 請求行(request line)

例如GET /images/logo.gif HTTP/1.1,表示從/images目錄下請求logo.gif這個檔案。

  • 請求頭(request header),空行

例如Accept-Language: en

  • 其他訊息體

請求行和標題必須以<CR><LF>作為結尾。空行內必須只有<CR><LF>而無其他空格。在 HTTP/1.1 協議中,所有的請求頭,除 Host 外,都是可選的。

請求行、請求頭資料的格式具體看 Http 協議中的描述。所以在從輸入流中讀取到位元組流資料之後必須按照請求行、請求頭、訊息體的順序來解析。

這裡以請求行資料的解析為例,在 Http 協議中該行內容格式為:

Request-Line = Method SP Request-URI SP HTTP-Version CRLF

即請求型別、要訪問的資源( URI )以及使用的HTTP版本,中間以特殊字元空格來分隔,以\r\n字元結尾。

在上面列出的 AbstractHttp11Processor 類的 process 程式碼中的第 36 行,會呼叫抽象方法 getInputBuffer() ,當前該抽象方法的具體實現在子類org.apache.coyote.http11.Http11Processor中,該方法返回的是該類的例項變數 inputBuffer :

    protected AbstractInputBuffer<Socket> getInputBuffer() {
        return inputBuffer;
    }
複製程式碼

該例項變數在 Http11Processor 的構造方法中會被初始化:

    public Http11Processor(int headerBufferSize, JIoEndpoint endpoint,
            int maxTrailerSize) {

        super(endpoint);
        
        inputBuffer = new InternalInputBuffer(request, headerBufferSize);
        request.setInputBuffer(inputBuffer);

        outputBuffer = new InternalOutputBuffer(response, headerBufferSize);
        response.setOutputBuffer(outputBuffer);

        initializeFilters(maxTrailerSize);
    }
複製程式碼

所以 AbstractHttp11Processor 類的 process 方法的 36 行 getInputBuffer().parseRequestLine() 將會呼叫org.apache.coyote.http11.InternalInputBuffer類中的 parseRequestLine 方法:

     1	    public boolean parseRequestLine(boolean useAvailableDataOnly)
     2	    
     3	        throws IOException {
     4	
     5	        int start = 0;
     6	
     7	        //
     8	        // Skipping blank lines
     9	        //
    10	
    11	        byte chr = 0;
    12	        do {
    13	
    14	            // Read new bytes if needed
    15	            if (pos >= lastValid) {
    16	                if (!fill())
    17	                    throw new EOFException(sm.getString("iib.eof.error"));
    18	            }
    19	
    20	            chr = buf[pos++];
    21	
    22	        } while ((chr == Constants.CR) || (chr == Constants.LF));
    23	
    24	        pos--;
    25	
    26	        // Mark the current buffer position
    27	        start = pos;
    28	
    29	        //
    30	        // Reading the method name
    31	        // Method name is always US-ASCII
    32	        //
    33	
    34	        boolean space = false;
    35	
    36	        while (!space) {
    37	
    38	            // Read new bytes if needed
    39	            if (pos >= lastValid) {
    40	                if (!fill())
    41	                    throw new EOFException(sm.getString("iib.eof.error"));
    42	            }
    43	
    44	            // Spec says no CR or LF in method name
    45	            if (buf[pos] == Constants.CR || buf[pos] == Constants.LF) {
    46	                throw new IllegalArgumentException(
    47	                        sm.getString("iib.invalidmethod"));
    48	            }
    49	            // Spec says single SP but it also says be tolerant of HT
    50	            if (buf[pos] == Constants.SP || buf[pos] == Constants.HT) {
    51	                space = true;
    52	                request.method().setBytes(buf, start, pos - start);
    53	            }
    54	
    55	            pos++;
    56	
    57	        }
    58	
    59	        
    60	        // Spec says single SP but also says be tolerant of multiple and/or HT
    61	        while (space) {
    62	            // Read new bytes if needed
    63	            if (pos >= lastValid) {
    64	                if (!fill())
    65	                    throw new EOFException(sm.getString("iib.eof.error"));
    66	            }
    67	            if (buf[pos] == Constants.SP || buf[pos] == Constants.HT) {
    68	                pos++;
    69	            } else {
    70	                space = false;
    71	            }
    72	        }
    73	
    74	        // Mark the current buffer position
    75	        start = pos;
    76	        int end = 0;
    77	        int questionPos = -1;
    78	
    79	        //
    80	        // Reading the URI
    81	        //
    82	
    83	        boolean eol = false;
    84	
    85	        while (!space) {
    86	
    87	            // Read new bytes if needed
    88	            if (pos >= lastValid) {
    89	                if (!fill())
    90	                    throw new EOFException(sm.getString("iib.eof.error"));
    91	            }
    92	
    93	            // Spec says single SP but it also says be tolerant of HT
    94	            if (buf[pos] == Constants.SP || buf[pos] == Constants.HT) {
    95	                space = true;
    96	                end = pos;
    97	            } else if ((buf[pos] == Constants.CR) 
    98	                       || (buf[pos] == Constants.LF)) {
    99	                // HTTP/0.9 style request
   100	                eol = true;
   101	                space = true;
   102	                end = pos;
   103	            } else if ((buf[pos] == Constants.QUESTION) 
   104	                       && (questionPos == -1)) {
   105	                questionPos = pos;
   106	            }
   107	
   108	            pos++;
   109	
   110	        }
   111	
   112	        request.unparsedURI().setBytes(buf, start, end - start);
   113	        if (questionPos >= 0) {
   114	            request.queryString().setBytes(buf, questionPos + 1, 
   115	                                           end - questionPos - 1);
   116	            request.requestURI().setBytes(buf, start, questionPos - start);
   117	        } else {
   118	            request.requestURI().setBytes(buf, start, end - start);
   119	        }
   120	
   121	        // Spec says single SP but also says be tolerant of multiple and/or HT
   122	        while (space) {
   123	            // Read new bytes if needed
   124	            if (pos >= lastValid) {
   125	                if (!fill())
   126	                    throw new EOFException(sm.getString("iib.eof.error"));
   127	            }
   128	            if (buf[pos] == Constants.SP || buf[pos] == Constants.HT) {
   129	                pos++;
   130	            } else {
   131	                space = false;
   132	            }
   133	        }
   134	
   135	        // Mark the current buffer position
   136	        start = pos;
   137	        end = 0;
   138	
   139	        //
   140	        // Reading the protocol
   141	        // Protocol is always US-ASCII
   142	        //
   143	
   144	        while (!eol) {
   145	
   146	            // Read new bytes if needed
   147	            if (pos >= lastValid) {
   148	                if (!fill())
   149	                    throw new EOFException(sm.getString("iib.eof.error"));
   150	            }
   151	
   152	            if (buf[pos] == Constants.CR) {
   153	                end = pos;
   154	            } else if (buf[pos] == Constants.LF) {
   155	                if (end == 0)
   156	                    end = pos;
   157	                eol = true;
   158	            }
   159	
   160	            pos++;
   161	
   162	        }
   163	
   164	        if ((end - start) > 0) {
   165	            request.protocol().setBytes(buf, start, end - start);
   166	        } else {
   167	            request.protocol().setString("");
   168	        }
   169	        
   170	        return true;
   171	
   172	    }
複製程式碼

先看這個方法中第 16 行,呼叫了當前類的 fill 方法:

    protected boolean fill() throws IOException {
        return fill(true);
    }
複製程式碼

裡面呼叫了過載方法 fill :

     1	    protected boolean fill(boolean block) throws IOException {
     2	
     3	        int nRead = 0;
     4	
     5	        if (parsingHeader) {
     6	
     7	            if (lastValid == buf.length) {
     8	                throw new IllegalArgumentException
     9	                    (sm.getString("iib.requestheadertoolarge.error"));
    10	            }
    11	
    12	            nRead = inputStream.read(buf, pos, buf.length - lastValid);
    13	            if (nRead > 0) {
    14	                lastValid = pos + nRead;
    15	            }
    16	
    17	        } else {
    18	
    19	            if (buf.length - end < 4500) {
    20	                // In this case, the request header was really large, so we allocate a 
    21	                // brand new one; the old one will get GCed when subsequent requests
    22	                // clear all references
    23	                buf = new byte[buf.length];
    24	                end = 0;
    25	            }
    26	            pos = end;
    27	            lastValid = pos;
    28	            nRead = inputStream.read(buf, pos, buf.length - lastValid);
    29	            if (nRead > 0) {
    30	                lastValid = pos + nRead;
    31	            }
    32	
    33	        }
    34	
    35	        return (nRead > 0);
    36	
    37	    }    }
複製程式碼

在這裡可以看到從輸入流中讀取資料到緩衝區 buf 。按照上面列出的請求行資料格式,從字元流中將會按順序得到請求的型別( method )、請求的 URI 和 Http 版本。具體實現流程如下:

org.apache.coyote.http11.InternalInputBuffer類中的 parseRequestLine 方法,第 34 到 57 行根據請求頭協議的格式,從中取出表示請求方法的位元組資料並設定到內建例項變數 request 。第 60 到 72 行解析 method 和 uri 之間的空格位元組 SP ,第 83 到 119 行讀取表示請求的 URI 的位元組資料並放到 request 變數中。第 122 到 133 行解析 uri 和 http 協議版本之間的空格位元組 SP ,第 144 到第 168 行讀取表示請求的 Http 協議版本的位元組資料並放到 request 變數中。

以上是根據 Http 協議解析請求行( request line )的程式碼實現部分,解析請求頭的部分見 InternalInputBuffer 類的 parseHeader 方法,不再贅述。

至此可以看到在 Tomcat 中如何從一次 Socket 連線中取出請求的資料,將這些原始的字元流資料轉換成初步可以理解的 Tomcat 內建物件org.apache.coyote.Request的。

相關文章