美文网首页
聊聊tomcat的keepAlive参数

聊聊tomcat的keepAlive参数

作者: go4it | 来源:发表于2023-11-26 10:21 被阅读0次

    本文主要研究一下tomcat的keepAlive参数

    maxKeepAliveRequests

    org/apache/tomcat/util/net/AbstractEndpoint.java

        /**
         * Max keep alive requests
         */
        private int maxKeepAliveRequests=100; // as in Apache HTTPD server
        public int getMaxKeepAliveRequests() {
            // Disable keep-alive if the server socket is not bound
            if (bindState.isBound()) {
                return maxKeepAliveRequests;
            } else {
                return 1;
            }
        }
        public void setMaxKeepAliveRequests(int maxKeepAliveRequests) {
            this.maxKeepAliveRequests = maxKeepAliveRequests;
        }
    

    AbstractEndpoint定义了maxKeepAliveRequests属性,默认为100

    Http11Processor

    org/apache/coyote/http11/Http11Processor.java

    public class Http11Processor extends AbstractProcessor {
    
        private static final Log log = LogFactory.getLog(Http11Processor.class);
    
        public SocketState service(SocketWrapperBase<?> socketWrapper)
            throws IOException {
            RequestInfo rp = request.getRequestProcessor();
            rp.setStage(org.apache.coyote.Constants.STAGE_PARSE);
    
            // Setting up the I/O
            setSocketWrapper(socketWrapper);
    
            // Flags
            keepAlive = true;
            openSocket = false;
            readComplete = true;
            boolean keptAlive = false;
            SendfileState sendfileState = SendfileState.DONE;
    
            //......
    
            while (!getErrorState().isError() && keepAlive && !isAsync() && upgradeToken == null &&
                    sendfileState == SendfileState.DONE && !protocol.isPaused()) {
    
                //......
    
                int maxKeepAliveRequests = protocol.getMaxKeepAliveRequests();
                if (maxKeepAliveRequests == 1) {
                    keepAlive = false;
                } else if (maxKeepAliveRequests > 0 &&
                        socketWrapper.decrementKeepAlive() <= 0) {
                    keepAlive = false;
                }   
    
                //......
    
                rp.setStage(org.apache.coyote.Constants.STAGE_KEEPALIVE);
    
                sendfileState = processSendfile(socketWrapper);            
            }         
        }        
    
    }    
    

    Http11Processor的service方法会从protocol获取maxKeepAliveRequests,若为1则重置keepAlive为false,若大于0则执行socketWrapper.decrementKeepAlive(),若小于等于0则重置keepAlive为false,跳出循环,requestInfo的stage不是STAGE_KEEPALIVE

    decrementKeepAlive

    org/apache/tomcat/util/net/SocketWrapperBase.java

    public abstract class SocketWrapperBase<E> {
    
        private static final Log log = LogFactory.getLog(SocketWrapperBase.class);
    
        protected static final StringManager sm = StringManager.getManager(SocketWrapperBase.class);
    
        private E socket;
        private final AbstractEndpoint<E,?> endpoint;
    
        protected final AtomicBoolean closed = new AtomicBoolean(false);
    
        // Volatile because I/O and setting the timeout values occurs on a different
        // thread to the thread checking the timeout.
        private volatile long readTimeout = -1;
        private volatile long writeTimeout = -1;
    
        private volatile int keepAliveLeft = 100;
    
        //......
    
        public void setKeepAliveLeft(int keepAliveLeft) { this.keepAliveLeft = keepAliveLeft; }
        public int decrementKeepAlive() { return (--keepAliveLeft); }
    
    }    
    

    SocketWrapperBase定义了keepAliveLeft属性,默认为100,它提供了setKeepAliveLeft方法以及decrementKeepAlive方法

    prepareResponse

    org/apache/coyote/http11/Http11Processor.java

        protected final void prepareResponse() throws IOException {
    
            boolean entityBody = true;
            contentDelimitation = false;
    
            OutputFilter[] outputFilters = outputBuffer.getFilters();
    
            if (http09 == true) {
                // HTTP/0.9
                outputBuffer.addActiveFilter(outputFilters[Constants.IDENTITY_FILTER]);
                outputBuffer.commit();
                return;
            }
    
            //......
    
            // If we know that the request is bad this early, add the
            // Connection: close header.
            if (keepAlive && statusDropsConnection(statusCode)) {
                keepAlive = false;
            }
            if (!keepAlive) {
                // Avoid adding the close header twice
                if (!connectionClosePresent) {
                    headers.addValue(Constants.CONNECTION).setString(
                            Constants.CLOSE);
                }
            } else if (!getErrorState().isError()) {
                if (!http11) {
                    headers.addValue(Constants.CONNECTION).setString(Constants.KEEP_ALIVE_HEADER_VALUE_TOKEN);
                }
    
                if (protocol.getUseKeepAliveResponseHeader()) {
                    boolean connectionKeepAlivePresent =
                        isConnectionToken(request.getMimeHeaders(), Constants.KEEP_ALIVE_HEADER_VALUE_TOKEN);
    
                    if (connectionKeepAlivePresent) {
                        int keepAliveTimeout = protocol.getKeepAliveTimeout();
    
                        if (keepAliveTimeout > 0) {
                            String value = "timeout=" + keepAliveTimeout / 1000L;
                            headers.setValue(Constants.KEEP_ALIVE_HEADER_NAME).setString(value);
    
                            if (http11) {
                                // Append if there is already a Connection header,
                                // else create the header
                                MessageBytes connectionHeaderValue = headers.getValue(Constants.CONNECTION);
                                if (connectionHeaderValue == null) {
                                    headers.addValue(Constants.CONNECTION).setString(Constants.KEEP_ALIVE_HEADER_VALUE_TOKEN);
                                } else {
                                    connectionHeaderValue.setString(
                                            connectionHeaderValue.getString() + ", " + Constants.KEEP_ALIVE_HEADER_VALUE_TOKEN);
                                }
                            }
                        }
                    }
                }
            }
    
            //......        
        }    
    
        /**
         * Determine if we must drop the connection because of the HTTP status
         * code.  Use the same list of codes as Apache/httpd.
         */
        private static boolean statusDropsConnection(int status) {
            return status == 400 /* SC_BAD_REQUEST */ ||
                   status == 408 /* SC_REQUEST_TIMEOUT */ ||
                   status == 411 /* SC_LENGTH_REQUIRED */ ||
                   status == 413 /* SC_REQUEST_ENTITY_TOO_LARGE */ ||
                   status == 414 /* SC_REQUEST_URI_TOO_LONG */ ||
                   status == 500 /* SC_INTERNAL_SERVER_ERROR */ ||
                   status == 503 /* SC_SERVICE_UNAVAILABLE */ ||
                   status == 501 /* SC_NOT_IMPLEMENTED */;
        }    
    

    Http11Processor的prepareResponse方法在statusDropsConnection时会设置keepalive为false,对于为false的会判断是否已经有Connection: close的header,如果没有则给添加上

    keepAliveTimeout

    org/apache/tomcat/util/net/AbstractEndpoint.java

    public abstract class AbstractEndpoint<S,U> {
    
        //......
    
        /**
         * Keepalive timeout, if not set the soTimeout is used.
         */
        private Integer keepAliveTimeout = null;
        public int getKeepAliveTimeout() {
            if (keepAliveTimeout == null) {
                return getConnectionTimeout();
            } else {
                return keepAliveTimeout.intValue();
            }
        }
        public void setKeepAliveTimeout(int keepAliveTimeout) {
            this.keepAliveTimeout = Integer.valueOf(keepAliveTimeout);
        }
    
        /**
         * Socket timeout.
         *
         * @return The current socket timeout for sockets created by this endpoint
         */
        public int getConnectionTimeout() { return socketProperties.getSoTimeout(); }
        public void setConnectionTimeout(int soTimeout) { socketProperties.setSoTimeout(soTimeout); }
        //......
    }
    

    AbstractEndpoint定义了keepAliveTimeout熟悉,默认为null取的是socketProperties.getSoTimeout()的值

    protocol.getKeepAliveTimeout()

    org/apache/coyote/http11/Http11Processor.java

        public SocketState service(SocketWrapperBase<?> socketWrapper)
            throws IOException {
            RequestInfo rp = request.getRequestProcessor();
            rp.setStage(org.apache.coyote.Constants.STAGE_PARSE);
    
            // Setting up the I/O
            setSocketWrapper(socketWrapper);
    
            // Flags
            keepAlive = true;
            openSocket = false;
            readComplete = true;
            boolean keptAlive = false;
            SendfileState sendfileState = SendfileState.DONE;
    
            while (!getErrorState().isError() && keepAlive && !isAsync() && upgradeToken == null &&
                    sendfileState == SendfileState.DONE && !protocol.isPaused()) {
    
                // Parsing the request header
                try {
                    if (!inputBuffer.parseRequestLine(keptAlive, protocol.getConnectionTimeout(),
                            protocol.getKeepAliveTimeout())) {
                        if (inputBuffer.getParsingRequestLinePhase() == -1) {
                            return SocketState.UPGRADING;
                        } else if (handleIncompleteRequestLineRead()) {
                            break;
                        }
                    }
                }
                
                //......
            }
        }            
    

    Http11Processor的service方法在keepalive为true时会执行inputBuffer.parseRequestLine(keptAlive, protocol.getConnectionTimeout(),protocol.getKeepAliveTimeout())

    parseRequestLine

    org/apache/coyote/http11/Http11InputBuffer.java

        /**
         * Read the request line. This function is meant to be used during the
         * HTTP request header parsing. Do NOT attempt to read the request body
         * using it.
         *
         * @throws IOException If an exception occurs during the underlying socket
         * read operations, or if the given buffer is not big enough to accommodate
         * the whole line.
         *
         * @return true if data is properly fed; false if no data is available
         * immediately and thread should be freed
         */
        boolean parseRequestLine(boolean keptAlive, int connectionTimeout, int keepAliveTimeout)
                throws IOException {
    
            // check state
            if (!parsingRequestLine) {
                return true;
            }
            //
            // Skipping blank lines
            //
            if (parsingRequestLinePhase < 2) {
                do {
                    // Read new bytes if needed
                    if (byteBuffer.position() >= byteBuffer.limit()) {
                        if (keptAlive) {
                            // Haven't read any request data yet so use the keep-alive
                            // timeout.
                            wrapper.setReadTimeout(keepAliveTimeout);
                        }
                        if (!fill(false)) {
                            // A read is pending, so no longer in initial state
                            parsingRequestLinePhase = 1;
                            return false;
                        }
                        // At least one byte of the request has been received.
                        // Switch to the socket timeout.
                        wrapper.setReadTimeout(connectionTimeout);
                    }
                    if (!keptAlive && byteBuffer.position() == 0 && byteBuffer.limit() >= CLIENT_PREFACE_START.length - 1) {
                        boolean prefaceMatch = true;
                        for (int i = 0; i < CLIENT_PREFACE_START.length && prefaceMatch; i++) {
                            if (CLIENT_PREFACE_START[i] != byteBuffer.get(i)) {
                                prefaceMatch = false;
                            }
                        }
                        if (prefaceMatch) {
                            // HTTP/2 preface matched
                            parsingRequestLinePhase = -1;
                            return false;
                        }
                    }
                    // Set the start time once we start reading data (even if it is
                    // just skipping blank lines)
                    if (request.getStartTime() < 0) {
                        request.setStartTime(System.currentTimeMillis());
                    }
                    chr = byteBuffer.get();
                } while ((chr == Constants.CR) || (chr == Constants.LF));
                byteBuffer.position(byteBuffer.position() - 1);
    
                parsingRequestLineStart = byteBuffer.position();
                parsingRequestLinePhase = 2;
            }
            if (parsingRequestLinePhase == 2) {
                //
                // Reading the method name
                // Method name is a token
                //
                boolean space = false;
                while (!space) {
                    // Read new bytes if needed
                    if (byteBuffer.position() >= byteBuffer.limit()) {
                        if (!fill(false)) // request line parsing
                            return false;
                    }
                    // Spec says method name is a token followed by a single SP but
                    // also be tolerant of multiple SP and/or HT.
                    int pos = byteBuffer.position();
                    chr = byteBuffer.get();
                    if (chr == Constants.SP || chr == Constants.HT) {
                        space = true;
                        request.method().setBytes(byteBuffer.array(), parsingRequestLineStart,
                                pos - parsingRequestLineStart);
                    } else if (!HttpParser.isToken(chr)) {
                        // Avoid unknown protocol triggering an additional error
                        request.protocol().setString(Constants.HTTP_11);
                        String invalidMethodValue = parseInvalid(parsingRequestLineStart, byteBuffer);
                        throw new IllegalArgumentException(sm.getString("iib.invalidmethod", invalidMethodValue));
                    }
                }
                parsingRequestLinePhase = 3;
            }
            if (parsingRequestLinePhase == 3) {
                // Spec says single SP but also be tolerant of multiple SP and/or HT
                boolean space = true;
                while (space) {
                    // Read new bytes if needed
                    if (byteBuffer.position() >= byteBuffer.limit()) {
                        if (!fill(false)) // request line parsing
                            return false;
                    }
                    chr = byteBuffer.get();
                    if (!(chr == Constants.SP || chr == Constants.HT)) {
                        space = false;
                        byteBuffer.position(byteBuffer.position() - 1);
                    }
                }
                parsingRequestLineStart = byteBuffer.position();
                parsingRequestLinePhase = 4;
            }
            if (parsingRequestLinePhase == 4) {
                // Mark the current buffer position
    
                int end = 0;
                //
                // Reading the URI
                //
                boolean space = false;
                while (!space) {
                    // Read new bytes if needed
                    if (byteBuffer.position() >= byteBuffer.limit()) {
                        if (!fill(false)) // request line parsing
                            return false;
                    }
                    int pos = byteBuffer.position();
                    prevChr = chr;
                    chr = byteBuffer.get();
                    if (prevChr == Constants.CR && chr != Constants.LF) {
                        // CR not followed by LF so not an HTTP/0.9 request and
                        // therefore invalid. Trigger error handling.
                        // Avoid unknown protocol triggering an additional error
                        request.protocol().setString(Constants.HTTP_11);
                        String invalidRequestTarget = parseInvalid(parsingRequestLineStart, byteBuffer);
                        throw new IllegalArgumentException(sm.getString("iib.invalidRequestTarget", invalidRequestTarget));
                    }
                    if (chr == Constants.SP || chr == Constants.HT) {
                        space = true;
                        end = pos;
                    } else if (chr == Constants.CR) {
                        // HTTP/0.9 style request. CR is optional. LF is not.
                    } else if (chr == Constants.LF) {
                        // HTTP/0.9 style request
                        // Stop this processing loop
                        space = true;
                        // Set blank protocol (indicates HTTP/0.9)
                        request.protocol().setString("");
                        // Skip the protocol processing
                        parsingRequestLinePhase = 7;
                        if (prevChr == Constants.CR) {
                            end = pos - 1;
                        } else {
                            end = pos;
                        }
                    } else if (chr == Constants.QUESTION && parsingRequestLineQPos == -1) {
                        parsingRequestLineQPos = pos;
                    } else if (parsingRequestLineQPos != -1 && !httpParser.isQueryRelaxed(chr)) {
                        // Avoid unknown protocol triggering an additional error
                        request.protocol().setString(Constants.HTTP_11);
                        // %nn decoding will be checked at the point of decoding
                        String invalidRequestTarget = parseInvalid(parsingRequestLineStart, byteBuffer);
                        throw new IllegalArgumentException(sm.getString("iib.invalidRequestTarget", invalidRequestTarget));
                    } else if (httpParser.isNotRequestTargetRelaxed(chr)) {
                        // Avoid unknown protocol triggering an additional error
                        request.protocol().setString(Constants.HTTP_11);
                        // This is a general check that aims to catch problems early
                        // Detailed checking of each part of the request target will
                        // happen in Http11Processor#prepareRequest()
                        String invalidRequestTarget = parseInvalid(parsingRequestLineStart, byteBuffer);
                        throw new IllegalArgumentException(sm.getString("iib.invalidRequestTarget", invalidRequestTarget));
                    }
                }
                if (parsingRequestLineQPos >= 0) {
                    request.queryString().setBytes(byteBuffer.array(), parsingRequestLineQPos + 1,
                            end - parsingRequestLineQPos - 1);
                    request.requestURI().setBytes(byteBuffer.array(), parsingRequestLineStart,
                            parsingRequestLineQPos - parsingRequestLineStart);
                } else {
                    request.requestURI().setBytes(byteBuffer.array(), parsingRequestLineStart,
                            end - parsingRequestLineStart);
                }
                // HTTP/0.9 processing jumps to stage 7.
                // Don't want to overwrite that here.
                if (parsingRequestLinePhase == 4) {
                    parsingRequestLinePhase = 5;
                }
            }
            if (parsingRequestLinePhase == 5) {
                // Spec says single SP but also be tolerant of multiple and/or HT
                boolean space = true;
                while (space) {
                    // Read new bytes if needed
                    if (byteBuffer.position() >= byteBuffer.limit()) {
                        if (!fill(false)) // request line parsing
                            return false;
                    }
                    byte chr = byteBuffer.get();
                    if (!(chr == Constants.SP || chr == Constants.HT)) {
                        space = false;
                        byteBuffer.position(byteBuffer.position() - 1);
                    }
                }
                parsingRequestLineStart = byteBuffer.position();
                parsingRequestLinePhase = 6;
    
                // Mark the current buffer position
                end = 0;
            }
            if (parsingRequestLinePhase == 6) {
                //
                // Reading the protocol
                // Protocol is always "HTTP/" DIGIT "." DIGIT
                //
                while (!parsingRequestLineEol) {
                    // Read new bytes if needed
                    if (byteBuffer.position() >= byteBuffer.limit()) {
                        if (!fill(false)) // request line parsing
                            return false;
                    }
    
                    int pos = byteBuffer.position();
                    prevChr = chr;
                    chr = byteBuffer.get();
                    if (chr == Constants.CR) {
                        // Possible end of request line. Need LF next.
                    } else if (prevChr == Constants.CR && chr == Constants.LF) {
                        end = pos - 1;
                        parsingRequestLineEol = true;
                    } else if (!HttpParser.isHttpProtocol(chr)) {
                        String invalidProtocol = parseInvalid(parsingRequestLineStart, byteBuffer);
                        throw new IllegalArgumentException(sm.getString("iib.invalidHttpProtocol", invalidProtocol));
                    }
                }
    
                if ((end - parsingRequestLineStart) > 0) {
                    request.protocol().setBytes(byteBuffer.array(), parsingRequestLineStart,
                            end - parsingRequestLineStart);
                    parsingRequestLinePhase = 7;
                }
                // If no protocol is found, the ISE below will be triggered.
            }
            if (parsingRequestLinePhase == 7) {
                // Parsing is complete. Return and clean-up.
                parsingRequestLine = false;
                parsingRequestLinePhase = 0;
                parsingRequestLineEol = false;
                parsingRequestLineStart = 0;
                return true;
            }
            throw new IllegalStateException(sm.getString("iib.invalidPhase", Integer.valueOf(parsingRequestLinePhase)));
        }
    

    Http11InputBuffer的parseRequestLine方法对于读不到请求数据时会设置SocketWrapperBase的readTimeout为keepAliveTimeout

    小结

    tomcat提供了maxKeepAliveRequests及keepAliveTimeout两个keepalive相关的参数,其中在SocketWrapperBase维护了keepAliveLeft及readTimeout属性,Http11Processor的service方法会在protocol的maxKeepAliveRequests大于0则执行socketWrapper.decrementKeepAlive(),若小于等于0则重置keepAlive为false;Http11Processor的prepareResponse方法在statusDropsConnection时会设置keepalive为false,对于为false的会判断是否已经有Connection: close的header,如果没有则给添加上。

    Http11Processor的service方法在keepalive为true时会执行inputBuffer.parseRequestLine,对于读不到请求数据时会设置SocketWrapperBase的readTimeout为keepAliveTimeout(默认为null取的是socketProperties.getSoTimeout()的值)。

    相关文章

      网友评论

          本文标题:聊聊tomcat的keepAlive参数

          本文链接:https://www.haomeiwen.com/subject/nydawdtx.html