本文主要研究一下tomcat的keepAlive参数
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
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
org/apache/tomcat/util/net/SocketWrapperBase.java
public abstract class SocketWrapperBase {
private static final Log log = LogFactory.getLog(SocketWrapperBase.class);
protected static final StringManager sm = StringManager.getManager(SocketWrapperBase.class);
private E socket;
private final AbstractEndpoint 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方法
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,如果没有则给添加上
org/apache/tomcat/util/net/AbstractEndpoint.java
public abstract class AbstractEndpoint {
//......
/**
* 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()的值
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())
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()的值
)。