package org.glassfish.grizzly.http;
import org.glassfish.grizzly.http.util.ContentType;
import org.glassfish.grizzly.filterchain.FilterChainEvent;
import org.glassfish.grizzly.http.util.Constants;
import org.glassfish.grizzly.Buffer;
import org.glassfish.grizzly.Connection;
import org.glassfish.grizzly.Grizzly;
import org.glassfish.grizzly.attributes.Attribute;
import org.glassfish.grizzly.filterchain.FilterChainContext;
import org.glassfish.grizzly.filterchain.NextAction;
import org.glassfish.grizzly.http.util.DataChunk;
import org.glassfish.grizzly.http.util.FastHttpDateFormat;
import org.glassfish.grizzly.http.util.Header;
import org.glassfish.grizzly.http.util.HttpStatus;
import org.glassfish.grizzly.http.util.MimeHeaders;
import org.glassfish.grizzly.memory.MemoryManager;
import org.glassfish.grizzly.utils.DelayedExecutor;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
import org.glassfish.grizzly.ThreadCache;
import static org.glassfish.grizzly.http.Method.PayloadExpectation;
import static org.glassfish.grizzly.http.util.HttpCodecUtils.*;
import org.glassfish.grizzly.http.util.HttpUtils;
public class HttpServerFilter extends HttpCodecFilter {
public static final String HTTP_SERVER_REQUEST_ATTR_NAME =
HttpServerFilter.class.getName() + ".HttpRequest";
public static final FilterChainEvent RESPONSE_COMPLETE_EVENT =
new HttpEvents.ResponseCompleteEvent();
private final Attribute<ServerHttpRequestImpl> httpRequestInProcessAttr;
private final Attribute<KeepAliveContext> keepAliveContextAttr;
private final DelayedExecutor.DelayQueue<KeepAliveContext> keepAliveQueue;
private final KeepAlive keepAlive;
private String defaultResponseContentType;
private byte[] defaultResponseContentTypeBytes;
private byte[] defaultResponseContentTypeBytesNoCharset;
private final boolean allowKeepAlive;
private final int ;
private final int ;
private boolean allowPayloadForUndefinedHttpMethods;
@Deprecated
public HttpServerFilter() {
this(true, DEFAULT_MAX_HTTP_PACKET_HEADER_SIZE, null, null);
}
@Deprecated
public HttpServerFilter(boolean chunkingEnabled,
int maxHeadersSize,
KeepAlive keepAlive,
DelayedExecutor executor) {
this(chunkingEnabled,
maxHeadersSize,
Constants.DEFAULT_RESPONSE_TYPE,
keepAlive,
executor);
}
@Deprecated
public HttpServerFilter(boolean chunkingEnabled,
int maxHeadersSize,
String defaultResponseContentType,
KeepAlive keepAlive,
DelayedExecutor executor) {
this(chunkingEnabled,
maxHeadersSize,
defaultResponseContentType,
keepAlive,
executor,
MimeHeaders.MAX_NUM_HEADERS_DEFAULT,
MimeHeaders.MAX_NUM_HEADERS_DEFAULT);
}
@Deprecated
public HttpServerFilter(boolean chunkingEnabled,
int maxHeadersSize,
String defaultResponseContentType,
KeepAlive keepAlive,
DelayedExecutor executor,
int maxRequestHeaders,
int maxResponseHeaders) {
super(chunkingEnabled, maxHeadersSize);
this.httpRequestInProcessAttr =
Grizzly.DEFAULT_ATTRIBUTE_BUILDER.
createAttribute(HTTP_SERVER_REQUEST_ATTR_NAME);
this.keepAliveContextAttr = Grizzly.DEFAULT_ATTRIBUTE_BUILDER.
createAttribute("HttpServerFilter.KeepAliveContext");
keepAliveQueue = executor != null ?
executor.createDelayQueue(
new KeepAliveWorker(keepAlive), new KeepAliveResolver()) :
null;
this.allowKeepAlive = keepAlive != null;
this.keepAlive = allowKeepAlive ? new KeepAlive(keepAlive) : null;
if (defaultResponseContentType != null && !defaultResponseContentType.isEmpty()) {
setDefaultResponseContentType(defaultResponseContentType);
}
this.maxRequestHeaders = maxRequestHeaders;
this.maxResponseHeaders = maxResponseHeaders;
}
@SuppressWarnings("UnusedDeclaration")
public String getDefaultResponseContentType() {
return defaultResponseContentType;
}
public final void setDefaultResponseContentType(final String contentType) {
this.defaultResponseContentType = contentType;
if (contentType != null) {
defaultResponseContentTypeBytes = toCheckedByteArray(contentType);
defaultResponseContentTypeBytesNoCharset =
ContentType.removeCharset(defaultResponseContentTypeBytes);
} else {
defaultResponseContentTypeBytes =
defaultResponseContentTypeBytesNoCharset = null;
}
}
public boolean isAllowPayloadForUndefinedHttpMethods() {
return allowPayloadForUndefinedHttpMethods;
}
public void setAllowPayloadForUndefinedHttpMethods(boolean allowPayloadForUndefinedHttpMethods) {
this.allowPayloadForUndefinedHttpMethods = allowPayloadForUndefinedHttpMethods;
}
@Override
public NextAction handleRead(final FilterChainContext ctx) throws IOException {
final Buffer input = ctx.getMessage();
final Connection connection = ctx.getConnection();
ServerHttpRequestImpl httpRequest = httpRequestInProcessAttr.get(connection);
if (httpRequest == null) {
final boolean isSecureLocal = isSecure(connection);
httpRequest = ServerHttpRequestImpl.create();
httpRequest.initialize(connection, this, input.position(), maxHeadersSize, maxRequestHeaders);
httpRequest.setSecure(isSecureLocal);
final HttpResponsePacket response = httpRequest.getResponse();
response.setSecure(isSecureLocal);
response.getHeaders().setMaxNumHeaders(maxResponseHeaders);
httpRequest.setResponse(response);
response.setRequest(httpRequest);
final HttpContext httpContext = HttpContext.newInstance(
connection, connection, connection, httpRequest)
.attach(ctx);
httpRequest.getProcessingState().setHttpContext(httpContext);
if (allowKeepAlive) {
KeepAliveContext keepAliveContext = keepAliveContextAttr.get(httpContext);
if (keepAliveContext == null) {
keepAliveContext = new KeepAliveContext(connection);
keepAliveContextAttr.set(httpContext, keepAliveContext);
} else if (keepAliveQueue != null) {
keepAliveQueue.remove(keepAliveContext);
}
final int requestsProcessed = keepAliveContext.requestsProcessed;
if (requestsProcessed > 0) {
KeepAlive.notifyProbesHit(keepAlive,
connection,
requestsProcessed);
}
}
httpRequestInProcessAttr.set(httpContext, httpRequest);
} else if (httpRequest.isContentBroken()) {
return ctx.getStopAction();
} else {
httpRequest.getProcessingState().getHttpContext().attach(ctx);
}
return handleRead(ctx, httpRequest);
}
@Override
final boolean (final FilterChainContext ctx,
final HttpPacketParsing httpPacket,
final HeaderParsingState parsingState,
final byte[] input,
final int end) {
final ServerHttpRequestImpl httpRequest = (ServerHttpRequestImpl) httpPacket;
final int arrayOffs = parsingState.arrayOffset;
final int reqLimit = arrayOffs + parsingState.packetLimit;
while(true) {
int subState = parsingState.subState;
switch(subState) {
case 0 : {
final int spaceIdx =
findSpace(input, arrayOffs + parsingState.offset, end, reqLimit);
if (spaceIdx == -1) {
parsingState.offset = end - arrayOffs;
return false;
}
httpRequest.getMethodDC().setBytes(input,
arrayOffs + parsingState.start,
spaceIdx);
parsingState.start = -1;
parsingState.offset = spaceIdx - arrayOffs;
parsingState.subState++;
}
case 1: {
final int nonSpaceIdx =
skipSpaces(input, arrayOffs + parsingState.offset,
end, reqLimit) - arrayOffs;
if (nonSpaceIdx < 0) {
parsingState.offset = end - arrayOffs;
return false;
}
parsingState.start = nonSpaceIdx;
parsingState.offset = nonSpaceIdx + 1;
parsingState.subState++;
}
case 2: {
if (!parseRequestURI(httpRequest, parsingState, input, end)) {
return false;
}
}
case 3: {
final int nonSpaceIdx =
skipSpaces(input, arrayOffs + parsingState.offset, end, reqLimit) - arrayOffs;
if (nonSpaceIdx < 0) {
parsingState.offset = end - arrayOffs;
return false;
}
parsingState.start = nonSpaceIdx;
parsingState.offset = nonSpaceIdx;
parsingState.subState++;
}
case 4: {
if (!findEOL(parsingState, input, end)) {
parsingState.offset = end - arrayOffs;
return false;
}
if (parsingState.checkpoint > parsingState.start) {
httpRequest.getProtocolDC().setBytes(
input, arrayOffs + parsingState.start,
arrayOffs + parsingState.checkpoint);
} else {
httpRequest.getProtocolDC().setString("");
}
parsingState.subState = 0;
parsingState.start = -1;
parsingState.checkpoint = -1;
onInitialLineParsed(httpRequest, ctx);
return true;
}
default: throw new IllegalStateException();
}
}
}
private static boolean (final ServerHttpRequestImpl httpRequest,
final HeaderParsingState state, final byte[] input, final int end) {
final int arrayOffs = state.arrayOffset;
final int limit = Math.min(end, arrayOffs + state.packetLimit);
int offset = arrayOffs + state.offset;
boolean found = false;
while (offset < limit) {
final byte b = input[offset];
if (b == Constants.SP || b == Constants.HT) {
found = true;
break;
} else if ((b == Constants.CR)
|| (b == Constants.LF)) {
found = true;
break;
} else if ((b == Constants.QUESTION)
&& (state.checkpoint == -1)) {
state.checkpoint = offset - arrayOffs;
}
offset++;
}
if (found) {
int requestURIEnd = offset;
if (state.checkpoint != -1) {
requestURIEnd = arrayOffs + state.checkpoint;
httpRequest.getQueryStringDC().setBytes(input,
requestURIEnd + 1, offset);
}
httpRequest.getRequestURIRef().init(input, arrayOffs + state.start, requestURIEnd);
state.start = -1;
state.checkpoint = -1;
state.subState++;
}
state.offset = offset - arrayOffs;
return found;
}
@Override
final boolean (final FilterChainContext ctx,
final HttpPacketParsing httpPacket,
final HeaderParsingState parsingState,
final Buffer input) {
final ServerHttpRequestImpl httpRequest = (ServerHttpRequestImpl) httpPacket;
final int reqLimit = parsingState.packetLimit;
while(true) {
int subState = parsingState.subState;
switch(subState) {
case 0 : {
final int spaceIdx =
findSpace(input, parsingState.offset, reqLimit);
if (spaceIdx == -1) {
parsingState.offset = input.limit();
return false;
}
httpRequest.getMethodDC().setBuffer(input,
parsingState.start, spaceIdx);
parsingState.start = -1;
parsingState.offset = spaceIdx;
parsingState.subState++;
}
case 1: {
final int nonSpaceIdx =
skipSpaces(input, parsingState.offset, reqLimit);
if (nonSpaceIdx == -1) {
parsingState.offset = input.limit();
return false;
}
parsingState.start = nonSpaceIdx;
parsingState.offset = nonSpaceIdx + 1;
parsingState.subState++;
}
case 2: {
if (!parseRequestURI(httpRequest, parsingState, input)) {
return false;
}
}
case 3: {
final int nonSpaceIdx =
skipSpaces(input, parsingState.offset, reqLimit);
if (nonSpaceIdx == -1) {
parsingState.offset = input.limit();
return false;
}
parsingState.start = nonSpaceIdx;
parsingState.offset = nonSpaceIdx;
parsingState.subState++;
}
case 4: {
if (!findEOL(parsingState, input)) {
parsingState.offset = input.limit();
return false;
}
if (parsingState.checkpoint > parsingState.start) {
httpRequest.getProtocolDC().setBuffer(
input, parsingState.start,
parsingState.checkpoint);
} else {
httpRequest.getProtocolDC().setString("");
}
parsingState.subState = 0;
parsingState.start = -1;
parsingState.checkpoint = -1;
onInitialLineParsed(httpRequest, ctx);
return true;
}
default: throw new IllegalStateException();
}
}
}
private static boolean (ServerHttpRequestImpl httpRequest,
HeaderParsingState state, Buffer input) {
final int limit = Math.min(input.limit(), state.packetLimit);
int offset = state.offset;
boolean found = false;
while(offset < limit) {
final byte b = input.get(offset);
if (b == Constants.SP || b == Constants.HT) {
found = true;
break;
} else if ((b == Constants.CR)
|| (b == Constants.LF)) {
found = true;
break;
} else if ((b == Constants.QUESTION)
&& (state.checkpoint == -1)) {
state.checkpoint = offset;
}
offset++;
}
if (found) {
int requestURIEnd = offset;
if (state.checkpoint != -1) {
requestURIEnd = state.checkpoint;
httpRequest.getQueryStringDC().setBuffer(input,
state.checkpoint + 1, offset);
}
httpRequest.getRequestURIRef().init(input, state.start, requestURIEnd);
state.start = -1;
state.checkpoint = -1;
state.subState++;
}
state.offset = offset;
return found;
}
@Override
protected boolean (final HttpHeader httpHeader,
final Buffer buffer,
final FilterChainContext ctx) {
final ServerHttpRequestImpl request = (ServerHttpRequestImpl) httpHeader;
prepareRequest(request, buffer.hasRemaining());
return request.getProcessingState().error;
}
private void prepareRequest(final ServerHttpRequestImpl request,
final boolean hasReadyContent) {
final ProcessingState state = request.getProcessingState();
final HttpResponsePacket response = request.getResponse();
Protocol protocol;
try {
protocol = request.getProtocol();
} catch (IllegalStateException e) {
state.error = true;
HttpStatus.HTTP_VERSION_NOT_SUPPORTED_505.setValues(response);
protocol = Protocol.HTTP_1_1;
request.setProtocol(protocol);
return;
}
request.getResponse().setChunkingAllowed(isChunkingEnabled());
if (request.getHeaderParsingState().contentLengthsDiffer) {
request.getProcessingState().error = true;
return;
}
final MimeHeaders headers = request.getHeaders();
DataChunk hostDC = null;
final DataChunk uriBC = request.getRequestURIRef().getRequestURIBC();
if (uriBC.startsWithIgnoreCase("http", 0)) {
int pos = uriBC.indexOf("://", 4);
int uriBCStart = uriBC.getStart();
int slashPos;
if (pos != -1) {
slashPos = uriBC.indexOf('/', pos + 3);
if (slashPos == -1) {
slashPos = uriBC.getLength();
uriBC.setStart(uriBCStart + pos + 1);
uriBC.setEnd(uriBCStart + pos + 2);
} else {
uriBC.setStart(uriBCStart + slashPos);
uriBC.setEnd(uriBC.getEnd());
}
hostDC = headers.setValue(Header.Host);
hostDC.set(uriBC, uriBCStart + pos + 3, uriBCStart + slashPos);
}
}
if (hostDC == null) {
hostDC = headers.getValue(Header.Host);
}
final boolean isHttp11 = protocol == Protocol.HTTP_1_1;
if (isHttp11 && (hostDC == null || hostDC.isNull())) {
state.error = true;
return;
}
request.unparsedHostC = hostDC;
if (request.isIgnoreContentModifiers()) {
return;
}
final Method method = request.getMethod();
final PayloadExpectation payloadExpectation = method.getPayloadExpectation();
if (payloadExpectation != PayloadExpectation.NOT_ALLOWED) {
final boolean hasPayload =
request.getContentLength() > 0 || request.isChunked();
if (hasPayload && payloadExpectation == PayloadExpectation.UNDEFINED &&
!allowPayloadForUndefinedHttpMethods) {
state.error = true;
HttpStatus.BAD_REQUEST_400.setValues(response);
return;
}
request.setExpectContent(hasPayload);
} else {
request.setExpectContent(method == Method.CONNECT ||
method == Method.PRI);
}
if (method == Method.CONNECT) {
state.keepAlive = false;
} else {
final DataChunk connectionValueDC = headers.getValue(Header.Connection);
final boolean isConnectionClose = (connectionValueDC != null &&
connectionValueDC.equalsIgnoreCaseLowerCase(CLOSE_BYTES));
if (!isConnectionClose) {
state.keepAlive = allowKeepAlive && (isHttp11 ||
(connectionValueDC != null &&
connectionValueDC.equalsIgnoreCaseLowerCase(KEEPALIVE_BYTES)));
}
}
if (request.requiresAcknowledgement()) {
request.requiresAcknowledgement(isHttp11 && !hasReadyContent);
}
}
@Override
protected final boolean (final HttpHeader httpHeader,
final FilterChainContext ctx) {
final ServerHttpRequestImpl request = (ServerHttpRequestImpl) httpHeader;
final boolean error = request.getProcessingState().error;
if (!error) {
httpRequestInProcessAttr.remove(ctx.getConnection());
}
return error;
}
@Override
protected void (final HttpHeader httpHeader,
final FilterChainContext ctx) {
}
@Override
protected void (final HttpHeader httpHeader,
final MimeHeaders headers,
final FilterChainContext ctx) {
}
@Override
protected void onHttpContentParsed(HttpContent content, FilterChainContext ctx) {
}
@Override
protected void (final HttpHeader httpHeader,
final FilterChainContext ctx,
final Throwable t) throws IOException {
final ServerHttpRequestImpl request = (ServerHttpRequestImpl) httpHeader;
final HttpResponsePacket response = request.getResponse();
sendBadRequestResponse(ctx, response);
}
@Override
protected void (final HttpHeader httpHeader,
final FilterChainContext ctx,
final Throwable t) throws IOException {
final ServerHttpRequestImpl request = (ServerHttpRequestImpl) httpHeader;
final HttpResponsePacket response = request.getResponse();
if (!response.isCommitted()) {
sendBadRequestResponse(ctx, response);
}
httpHeader.setContentBroken(true);
}
@Override
protected Buffer encodeHttpPacket(final FilterChainContext ctx,
final HttpPacket input) {
final HttpHeader header;
HttpContent content;
final boolean isHeaderPacket = input.isHeader();
if (isHeaderPacket) {
header = (HttpHeader) input;
content = null;
} else {
content = (HttpContent) input;
header = content.getHttpHeader();
}
boolean wasContentAlreadyEncoded = false;
final HttpResponsePacket response = (HttpResponsePacket) header;
if (!response.isCommitted()) {
final HttpContent encodedHttpContent = prepareResponse(
ctx, response.getRequest(), response, content);
if (encodedHttpContent != null) {
content = encodedHttpContent;
wasContentAlreadyEncoded = true;
}
}
final Buffer encoded = super.encodeHttpPacket(ctx, header, content,
wasContentAlreadyEncoded);
if (!isHeaderPacket) {
input.recycle();
}
return encoded;
}
private HttpContent prepareResponse(final FilterChainContext ctx,
final HttpRequestPacket request,
final HttpResponsePacket response,
final HttpContent httpContent) {
if (request.isIgnoreContentModifiers() ||
response.isIgnoreContentModifiers()) {
return httpContent;
}
final Protocol requestProtocol = request.getProtocol();
if (requestProtocol == Protocol.HTTP_0_9) {
return null;
}
boolean entityBody = true;
final int statusCode = response.getStatus();
final boolean is204 = HttpStatus.NO_CONTENT_204.statusMatches(statusCode);
if (is204 || HttpStatus.RESET_CONTENT_205.statusMatches(statusCode)
|| HttpStatus.NOT_MODIFIED_304.statusMatches(statusCode)) {
entityBody = false;
response.setExpectContent(false);
if (is204) {
response.setTransferEncoding(null);
response.getHeaders().removeHeader(Header.TransferEncoding);
}
}
final boolean isHttp11OrHigher = (requestProtocol.compareTo(Protocol.HTTP_1_1) >= 0);
HttpContent encodedHttpContent = null;
final Method method = request.getMethod();
if (!Method.CONNECT.equals(method)) {
if (entityBody) {
setContentEncodingsOnSerializing(response);
if (response.getContentLength() == -1L && !response.isChunked()) {
if (httpContent != null && httpContent.isLast()) {
if (!response.getContentEncodings(true).isEmpty()) {
encodedHttpContent = encodeContent(ctx.getConnection(), httpContent);
}
response.setContentLength(httpContent.getContent().remaining());
} else if (chunkingEnabled && isHttp11OrHigher) {
response.setChunked(true);
}
}
}
if (Method.HEAD.equals(method)) {
response.setExpectContent(false);
setContentEncodingsOnSerializing(response);
setTransferEncodingOnSerializing(ctx,
response,
httpContent);
}
} else {
response.setContentEncodingsSelected(true);
response.setContentLength(-1);
response.setChunked(false);
}
final MimeHeaders headers = response.getHeaders();
if (!entityBody) {
response.setContentLength(-1);
} else {
String contentLanguage = response.getContentLanguage();
if (contentLanguage != null) {
headers.setValue(Header.ContentLanguage).setString(contentLanguage);
}
final ContentType contentType = response.getContentTypeHolder();
if (contentType.isMimeTypeSet()) {
final DataChunk contentTypeValue = headers.setValue(Header.ContentType);
if (contentTypeValue.isNull()) {
contentType.serializeToDataChunk(contentTypeValue);
}
} else if (defaultResponseContentType != null) {
final DataChunk contenTypeValue = headers.setValue(Header.ContentType);
if (contenTypeValue.isNull()) {
final String ce = response.getCharacterEncoding();
if (ce == null) {
contenTypeValue.setBytes(defaultResponseContentTypeBytes);
} else {
final byte[] array = ContentType.compose(
defaultResponseContentTypeBytesNoCharset, ce);
contenTypeValue.setBytes(array);
}
}
}
}
if (!response.containsHeader(Header.Date)) {
response.getHeaders().addValue(Header.Date)
.setBytes(FastHttpDateFormat.getCurrentDateBytes());
}
final ProcessingState state = response.getProcessingState();
final boolean isHttp11 = (requestProtocol == Protocol.HTTP_1_1);
if (state.keepAlive) {
if (entityBody && !isHttp11 && response.getContentLength() == -1) {
state.keepAlive = false;
} else if (entityBody && !response.isChunked() && response.getContentLength() == -1) {
state.keepAlive = false;
} else if (!checkKeepAliveRequestsCount(state.getHttpContext())) {
state.keepAlive = false;
} else {
final DataChunk dc = headers.getValue(Header.Connection);
if (dc != null && !dc.isNull() && dc.equalsIgnoreCase(CLOSE_BYTES)) {
state.keepAlive = false;
}
}
state.keepAlive = (state.keepAlive
&& !statusDropsConnection(response.getStatus()));
}
if (!state.keepAlive) {
headers.setValue(Header.Connection).setBytes(CLOSE_BYTES);
} else if (!isHttp11 && !state.error) {
headers.setValue(Header.Connection).setBytes(KEEPALIVE_BYTES);
}
return encodedHttpContent;
}
@Override
Buffer encodeInitialLine(HttpPacket httpPacket, Buffer output, MemoryManager memoryManager) {
final HttpResponsePacket httpResponse = (HttpResponsePacket) httpPacket;
output = put(memoryManager, output, httpResponse.getProtocol().getProtocolBytes());
output = put(memoryManager, output, Constants.SP);
output = put(memoryManager, output, httpResponse.getHttpStatus().getStatusBytes());
output = put(memoryManager, output, Constants.SP);
if (httpResponse.isCustomReasonPhraseSet()) {
final DataChunk customReasonPhrase =
httpResponse.isHtmlEncodingCustomReasonPhrase() ?
HttpUtils.filter(httpResponse.getReasonPhraseDC()) :
HttpUtils.filterNonPrintableCharacters(httpResponse.getReasonPhraseDC());
output = put(memoryManager, output,
httpResponse.getTempHeaderEncodingBuffer(),
customReasonPhrase);
} else {
output = put(memoryManager, output,
httpResponse.getHttpStatus().getReasonPhraseBytes());
}
return output;
}
@Override
protected void (HttpHeader header, FilterChainContext ctx) {
}
@Override
protected void (HttpHeader httpHeader, FilterChainContext ctx) {
}
@Override
protected void onHttpContentEncoded(HttpContent content, FilterChainContext ctx) {
}
@Override
public NextAction handleEvent(final FilterChainContext ctx,
final FilterChainEvent event) throws IOException {
if (event.type() == HttpEvents.ResponseCompleteEvent.TYPE) {
if (ctx.getConnection().isOpen()) {
final HttpContext context = HttpContext.get(ctx);
final HttpRequestPacket httpRequest = context.getRequest();
if (allowKeepAlive) {
if (keepAliveQueue != null) {
final KeepAliveContext keepAliveContext =
keepAliveContextAttr.get(context);
if (keepAliveContext != null) {
keepAliveQueue.add(keepAliveContext,
keepAlive.getIdleTimeoutInSeconds(),
TimeUnit.SECONDS);
}
}
final boolean isStayAlive =
httpRequest.getProcessingState().isKeepAlive();
processResponseComplete(ctx, httpRequest, isStayAlive);
} else {
processResponseComplete(ctx, httpRequest, false);
}
}
return ctx.getStopAction();
}
return ctx.getInvokeAction();
}
@Override
public NextAction handleClose(final FilterChainContext ctx) throws IOException {
final ServerHttpRequestImpl httpRequest =
httpRequestInProcessAttr.get(ctx.getConnection());
if (httpRequest != null && !httpRequest.isContentBroken()) {
if (httpRequest.isExpectContent() &&
httpRequest.getTransferEncoding() == null) {
httpRequest.setExpectContent(false);
onHttpPacketParsed(httpRequest, ctx);
}
}
return ctx.getInvokeAction();
}
private void processResponseComplete(final FilterChainContext ctx,
final HttpRequestPacket httpRequest, final boolean isStayAlive)
throws IOException {
if (httpRequest.isUpgrade()) {
httpRequest.getProcessingState().getHttpContext().close();
return;
}
if (httpRequest.isExpectContent()) {
if (!httpRequest.isContentBroken() &&
checkContentLengthRemainder(httpRequest)) {
httpRequest.setSkipRemainder(true);
} else {
httpRequest.setExpectContent(false);
onHttpPacketParsed(httpRequest, ctx);
httpRequest.getProcessingState().getHttpContext().close();
}
} else if (!isStayAlive) {
httpRequest.getProcessingState().getHttpContext().close();
}
}
protected HttpContent customizeErrorResponse(
final HttpResponsePacket response) {
response.setContentLength(0);
return HttpContent.builder(response).last(true).build();
}
private boolean checkKeepAliveRequestsCount(final HttpContext httpContext) {
if (!allowKeepAlive) {
return false;
}
final KeepAliveContext keepAliveContext = keepAliveContextAttr.get(httpContext);
final int requestsProcessed = keepAliveContext.requestsProcessed++;
final int maxRequestCount = keepAlive.getMaxRequestsCount();
final boolean isKeepAlive = (maxRequestCount == -1 ||
keepAliveContext.requestsProcessed <= maxRequestCount);
if (requestsProcessed == 0) {
if (isKeepAlive) {
KeepAlive.notifyProbesConnectionAccepted(keepAlive,
keepAliveContext.connection);
} else {
KeepAlive.notifyProbesRefused(keepAlive, keepAliveContext.connection);
}
}
return isKeepAlive;
}
private void sendBadRequestResponse(final FilterChainContext ctx,
final HttpResponsePacket response) {
if (response.getHttpStatus().getStatusCode() < 400) {
HttpStatus.BAD_REQUEST_400.setValues(response);
}
commitAndCloseAsError(ctx, response);
}
private void commitAndCloseAsError(FilterChainContext ctx, HttpResponsePacket response) {
final HttpContent errorHttpResponse = customizeErrorResponse(response);
final Buffer resBuf = encodeHttpPacket(ctx, errorHttpResponse);
ctx.write(resBuf);
response.getProcessingState().getHttpContext().close();
}
private boolean checkContentLengthRemainder(final HttpRequestPacket httpRequest) {
return maxPayloadRemainderToSkip < 0 ||
httpRequest.getContentLength() <= 0 ||
((HttpPacketParsing) httpRequest).getContentParsingState().chunkRemainder <= maxPayloadRemainderToSkip;
}
private static class KeepAliveContext {
private final Connection connection;
public KeepAliveContext(Connection connection) {
this.connection = connection;
}
private volatile long keepAliveTimeoutMillis = DelayedExecutor.UNSET_TIMEOUT;
private int requestsProcessed;
}
private static class KeepAliveWorker implements DelayedExecutor.Worker<KeepAliveContext> {
private final KeepAlive keepAlive;
public KeepAliveWorker(final KeepAlive keepAlive) {
this.keepAlive = keepAlive;
}
@Override
public boolean doWork(final KeepAliveContext context) {
KeepAlive.notifyProbesTimeout(keepAlive, context.connection);
context.connection.closeSilently();
return true;
}
}
private static class KeepAliveResolver implements
DelayedExecutor.Resolver<KeepAliveContext> {
@Override
public boolean removeTimeout(KeepAliveContext context) {
if (context.keepAliveTimeoutMillis != DelayedExecutor.UNSET_TIMEOUT) {
context.keepAliveTimeoutMillis = DelayedExecutor.UNSET_TIMEOUT;
return true;
}
return false;
}
@Override
public long getTimeoutMillis(KeepAliveContext element) {
return element.keepAliveTimeoutMillis;
}
@Override
public void setTimeoutMillis(KeepAliveContext element, long timeoutMillis) {
element.keepAliveTimeoutMillis = timeoutMillis;
}
}
private static final class ServerHttpRequestImpl extends HttpRequestPacket
implements HttpPacketParsing {
private static final ThreadCache.CachedTypeIndex<ServerHttpRequestImpl> CACHE_IDX =
ThreadCache.obtainIndex(ServerHttpRequestImpl.class, 16);
public static ServerHttpRequestImpl create() {
final ServerHttpRequestImpl httpRequestImpl =
ThreadCache.takeFromCache(CACHE_IDX);
if (httpRequestImpl != null) {
return httpRequestImpl;
}
return new ServerHttpRequestImpl();
}
private boolean contentTypeParsed;
private boolean ;
private final HttpCodecFilter.HeaderParsingState ;
private final HttpCodecFilter.ContentParsingState contentParsingState;
private final ProcessingState processingState;
private final HttpResponsePacket finalHttpResponse;
private ServerHttpRequestImpl() {
this.headerParsingState = new HttpCodecFilter.HeaderParsingState();
this.contentParsingState = new HttpCodecFilter.ContentParsingState();
this.processingState = new ProcessingState();
finalHttpResponse = new HttpResponsePacketImpl();
isExpectContent = true;
}
public void initialize(final Connection connection,
final HttpCodecFilter filter,
final int initialOffset,
final int maxHeaderSize,
final int maxNumberOfHeaders) {
headerParsingState.initialize(filter, initialOffset, maxHeaderSize);
contentParsingState.trailerHeaders.setMaxNumHeaders(maxNumberOfHeaders);
headers.setMaxNumHeaders(maxNumberOfHeaders);
finalHttpResponse.setProtocol(Protocol.HTTP_1_1);
setResponse(finalHttpResponse);
setConnection(connection);
}
@Override
public String getCharacterEncoding() {
if (!contentTypeParsed) {
parseContentTypeHeader();
}
return super.getCharacterEncoding();
}
@Override
public void setCharacterEncoding(final String charset) {
if (!contentTypeParsed) {
parseContentTypeHeader();
}
super.setCharacterEncoding(charset);
}
@Override
public String getContentType() {
if (!contentTypeParsed) {
parseContentTypeHeader();
}
return super.getContentType();
}
private void () {
contentTypeParsed = true;
if (!contentType.isSet()) {
final DataChunk dc = headers.getValue(Header.ContentType);
if (dc != null && !dc.isNull()) {
setContentType(dc.toString());
}
}
}
@Override
public ProcessingState getProcessingState() {
return processingState;
}
@Override
public HttpCodecFilter.HeaderParsingState () {
return headerParsingState;
}
@Override
public ContentParsingState getContentParsingState() {
return contentParsingState;
}
@Override
public boolean () {
return isHeaderParsed;
}
@Override
public void (final boolean isHeaderParsed) {
if (isHeaderParsed && isExpectContent() && !isChunked) {
contentParsingState.chunkRemainder = getContentLength();
}
this.isHeaderParsed = isHeaderParsed;
}
@Override
protected HttpPacketParsing getParsingState() {
return this;
}
@Override
protected void reset() {
contentTypeParsed = false;
isHeaderParsed = false;
headerParsingState.recycle();
contentParsingState.recycle();
processingState.recycle();
super.reset();
}
@Override
public void recycle() {
if (isExpectContent()) {
return;
}
reset();
ThreadCache.putToCache(CACHE_IDX, this);
}
}
}