tomcat的keepAlive参数深入探究
作者:codecraft
序
本文主要研究一下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参数深入探究的详细内容,更多关于tomcat keepAlive参数的资料请关注脚本之家其它相关文章!