package org.glassfish.grizzly.http;
import static org.glassfish.grizzly.http.util.HttpCodecUtils.findEOL;
import static org.glassfish.grizzly.http.util.HttpCodecUtils.findSpace;
import static org.glassfish.grizzly.http.util.HttpCodecUtils.put;
import static org.glassfish.grizzly.http.util.HttpCodecUtils.skipSpaces;
import static org.glassfish.grizzly.http.util.HttpCodecUtils.toCheckedByteArray;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
import org.glassfish.grizzly.Buffer;
import org.glassfish.grizzly.Connection;
import org.glassfish.grizzly.Grizzly;
import org.glassfish.grizzly.ThreadCache;
import org.glassfish.grizzly.attributes.Attribute;
import org.glassfish.grizzly.filterchain.FilterChainContext;
import org.glassfish.grizzly.filterchain.FilterChainEvent;
import org.glassfish.grizzly.filterchain.NextAction;
import org.glassfish.grizzly.http.Method.PayloadExpectation;
import org.glassfish.grizzly.http.util.Constants;
import org.glassfish.grizzly.http.util.ContentType;
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.HttpUtils;
import org.glassfish.grizzly.http.util.MimeHeaders;
import org.glassfish.grizzly.memory.MemoryManager;
import org.glassfish.grizzly.utils.DelayedExecutor;
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);
}
}
}