package org.glassfish.grizzly.http;
import java.io.IOException;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
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.MimeHeaders;
import org.glassfish.grizzly.memory.MemoryManager;
import org.glassfish.grizzly.ThreadCache;
import org.glassfish.grizzly.filterchain.FilterChainEvent;
import org.glassfish.grizzly.http.util.Ascii;
import org.glassfish.grizzly.http.util.Constants;
import org.glassfish.grizzly.http.util.DataChunk;
import org.glassfish.grizzly.http.util.Header;
import org.glassfish.grizzly.http.util.HttpStatus;
import static org.glassfish.grizzly.http.util.HttpCodecUtils.*;
public class HttpClientFilter extends HttpCodecFilter {
private final Attribute<Queue<HttpRequestPacket>> httpRequestQueueAttr;
private final Attribute<HttpResponsePacket> httpResponseInProcessAttr;
public HttpClientFilter() {
this(DEFAULT_MAX_HTTP_PACKET_HEADER_SIZE);
}
public HttpClientFilter(int maxHeadersSize) {
super(true, maxHeadersSize);
this.httpResponseInProcessAttr =
Grizzly.DEFAULT_ATTRIBUTE_BUILDER.createAttribute(
"HttpClientFilter.httpResponse");
this.httpRequestQueueAttr =
Grizzly.DEFAULT_ATTRIBUTE_BUILDER.createAttribute(
"HttpClientFilter.httpRequest");
contentEncodings.add(new GZipContentEncoding());
contentEncodings.add(new LZMAContentEncoding());
}
@Override
public NextAction handleWrite(FilterChainContext ctx) throws IOException {
final Connection c = ctx.getConnection();
final Object message = ctx.getMessage();
if (HttpPacket.isHttp(message)) {
assert message instanceof HttpPacket;
final HttpHeader header = ((HttpPacket) message).getHttpHeader();
if (!header.isCommitted() && header.isRequest()) {
assert header instanceof HttpRequestPacket;
getRequestQueue(c).offer((HttpRequestPacket) header);
}
}
return super.handleWrite(ctx);
}
@Override
public NextAction handleRead(FilterChainContext ctx) throws IOException {
final Connection connection = ctx.getConnection();
HttpResponsePacket httpResponse = httpResponseInProcessAttr.get(connection);
if (httpResponse == null) {
httpResponse = createHttpResponse(ctx);
httpResponseInProcessAttr.set(connection, httpResponse);
}
final HttpRequestPacket request = httpResponse.getRequest();
HttpContext httpCtx;
if (request != null) {
httpCtx = request.getProcessingState().getHttpContext();
if (httpCtx == null) {
httpCtx = HttpContext.newInstance(connection,
connection, connection, request);
request.getProcessingState().setHttpContext(httpCtx);
}
} else {
httpCtx = HttpContext.newInstance(connection,
connection, connection, null);
}
httpCtx.attach(ctx);
return handleRead(ctx, httpResponse);
}
@Override
public NextAction handleEvent(final FilterChainContext ctx,
final FilterChainEvent event) throws IOException {
if (event.type() == HttpEvents.ChangePacketInProgressEvent.TYPE) {
final HttpResponsePacket responsePacket = (HttpResponsePacket)
((HttpEvents.ChangePacketInProgressEvent) event).getPacket();
httpResponseInProcessAttr.set(
responsePacket.getProcessingState().getHttpContext(),
responsePacket);
return ctx.getStopAction();
} else {
return super.handleEvent(ctx, event);
}
}
private ClientHttpResponseImpl createHttpResponse(FilterChainContext ctx) {
final Buffer input = ctx.getMessage();
final Connection connection = ctx.getConnection();
ClientHttpResponseImpl httpResponse = ClientHttpResponseImpl.create();
final HttpRequestPacket httpRequest =
getRequestQueue(connection).poll();
httpResponse.setRequest(httpRequest);
httpResponse.initialize(this, input.position(),
maxHeadersSize, MimeHeaders.MAX_NUM_HEADERS_UNBOUNDED);
httpResponse.setSecure(isSecure(connection));
if (httpRequest != null) {
try {
final Protocol protocol = httpRequest.getProtocol();
if (Protocol.HTTP_2_0.equals(protocol)) {
httpResponse.setProtocol(httpRequest.getProtocol());
httpResponse.setStatus(HttpStatus.OK_200);
httpResponse.setExpectContent(true);
httpResponse.setHeaderParsed(true);
}
} catch (IllegalStateException ise) {
}
}
return httpResponse;
}
@Override
protected boolean (HttpHeader httpHeader, FilterChainContext ctx) {
final Connection connection = ctx.getConnection();
clearResponse(connection);
return false;
}
@Override
protected boolean (final HttpHeader httpHeader, final Buffer buffer,
final FilterChainContext ctx) {
final ClientHttpResponseImpl response = (ClientHttpResponseImpl) httpHeader;
final HttpRequestPacket request = response.getRequest();
final int statusCode = response.getStatus();
final boolean noContent =
((statusCode == 204) || (statusCode == 205) || (statusCode == 304)
|| ((request != null) && request.isHeadRequest()));
response.setExpectContent(!noContent);
if (request != null) {
response.getProcessingState().setKeepAlive(checkKeepAlive(response));
}
return false;
}
@Override
protected void (final HttpHeader httpHeader,
final FilterChainContext ctx,
final Throwable t) throws IOException {
throw new IllegalStateException(t);
}
@Override
protected void (final HttpHeader httpHeader,
final FilterChainContext ctx,
final Throwable t) throws IOException {
httpHeader.setContentBroken(true);
throw new IllegalStateException(t);
}
@Override
protected void (final HttpHeader httpHeader,
final FilterChainContext ctx) {
}
@Override
protected void (HttpHeader header, FilterChainContext ctx) {
}
@Override
protected void (final HttpHeader httpHeader,
final MimeHeaders headers,
final FilterChainContext ctx) {
}
@Override
protected void (HttpHeader httpHeader, FilterChainContext ctx) {
}
@Override
protected void onHttpContentParsed(HttpContent content, FilterChainContext ctx) {
}
@Override
protected void onHttpContentEncoded(HttpContent content, FilterChainContext ctx) {
}
protected final void clearResponse(final Connection connection) {
httpResponseInProcessAttr.remove(connection);
}
@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();
}
final HttpRequestPacket request = (HttpRequestPacket) header;
if (!request.isCommitted()) {
prepareRequest(request);
}
return super.encodeHttpPacket(ctx, header, content, false);
}
@Override
final boolean (final FilterChainContext ctx,
final HttpPacketParsing httpPacket,
final HeaderParsingState parsingState,
final byte[] input,
final int end) {
final HttpResponsePacket httpResponse = (HttpResponsePacket) httpPacket;
final int arrayOffs = parsingState.arrayOffset;
final int packetLimit = arrayOffs + parsingState.packetLimit;
while(true) {
int subState = parsingState.subState;
switch(subState) {
case 0: {
final int spaceIdx =
findSpace(input, arrayOffs + parsingState.offset, end, packetLimit);
if (spaceIdx == -1) {
parsingState.offset = end - arrayOffs;
return false;
}
httpResponse.getProtocolDC().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, packetLimit) - arrayOffs;
if (nonSpaceIdx < 0) {
parsingState.offset = end - arrayOffs;
return false;
}
parsingState.start = nonSpaceIdx;
parsingState.offset = nonSpaceIdx + 1;
parsingState.subState++;
}
case 2 : {
if (parsingState.offset + 3 > end - arrayOffs) {
return false;
}
httpResponse.setStatus(Ascii.parseInt(input,
arrayOffs + parsingState.start,
3));
parsingState.start = -1;
parsingState.offset += 3;
parsingState.subState++;
}
case 3: {
final int nonSpaceIdx =
skipSpaces(input, arrayOffs + parsingState.offset,
end, packetLimit) - 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;
}
httpResponse.getReasonPhraseRawDC().setBytes(
input, arrayOffs + parsingState.start,
arrayOffs + parsingState.checkpoint);
parsingState.subState = 0;
parsingState.start = -1;
parsingState.checkpoint = -1;
onInitialLineParsed(httpResponse, ctx);
if (httpResponse.getStatus() == 100) {
parsingState.offset += 2;
parsingState.start = parsingState.offset;
if (parsingState.start < end)
{
parsingState.subState = 0;
continue;
}
return false;
}
return true;
}
default: throw new IllegalStateException();
}
}
}
@Override
final boolean (final FilterChainContext ctx,
final HttpPacketParsing httpPacket,
final HeaderParsingState parsingState,
final Buffer input) {
final HttpResponsePacket httpResponse = (HttpResponsePacket) httpPacket;
final int packetLimit = parsingState.packetLimit;
while(true) {
int subState = parsingState.subState;
switch(subState) {
case 0: {
final int spaceIdx =
findSpace(input, parsingState.offset, packetLimit);
if (spaceIdx == -1) {
parsingState.offset = input.limit();
return false;
}
httpResponse.getProtocolDC().setBuffer(input,
parsingState.start, spaceIdx);
parsingState.start = -1;
parsingState.offset = spaceIdx;
parsingState.subState++;
}
case 1: {
final int nonSpaceIdx =
skipSpaces(input, parsingState.offset, packetLimit);
if (nonSpaceIdx == -1) {
parsingState.offset = input.limit();
return false;
}
parsingState.start = nonSpaceIdx;
parsingState.offset = nonSpaceIdx + 1;
parsingState.subState++;
}
case 2 : {
if (parsingState.offset + 3 > input.limit()) {
return false;
}
httpResponse.setStatus(Ascii.parseInt(input,
parsingState.start,
3));
parsingState.start = -1;
parsingState.offset += 3;
parsingState.subState++;
}
case 3: {
final int nonSpaceIdx =
skipSpaces(input, parsingState.offset, packetLimit);
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;
}
httpResponse.getReasonPhraseRawDC().setBuffer(
input, parsingState.start,
parsingState.checkpoint);
parsingState.subState = 0;
parsingState.start = -1;
parsingState.checkpoint = -1;
onInitialLineParsed(httpResponse, ctx);
if (httpResponse.getStatus() == 100) {
parsingState.offset += 2;
parsingState.start = 0;
input.position(parsingState.offset);
input.shrink();
parsingState.offset = 0;
return false;
}
return true;
}
default: throw new IllegalStateException();
}
}
}
@Override
Buffer encodeInitialLine(HttpPacket httpPacket, Buffer output, MemoryManager memoryManager) {
final HttpRequestPacket httpRequest = (HttpRequestPacket) httpPacket;
final byte[] tempEncodingBuffer = httpRequest.getTempHeaderEncodingBuffer();
output = put(memoryManager, output, tempEncodingBuffer, httpRequest.getMethodDC());
output = put(memoryManager, output, Constants.SP);
output = put(memoryManager, output, tempEncodingBuffer, httpRequest.getRequestURIRef().getRequestURIBC());
if (!httpRequest.getQueryStringDC().isNull()) {
output = put(memoryManager, output, (byte) '?');
output = put(memoryManager, output, tempEncodingBuffer, httpRequest.getQueryStringDC());
}
output = put(memoryManager, output, Constants.SP);
output = put(memoryManager, output, tempEncodingBuffer, httpRequest.getProtocolString());
return output;
}
private Queue<HttpRequestPacket> getRequestQueue(final Connection c) {
Queue<HttpRequestPacket> q = httpRequestQueueAttr.get(c);
if (q == null) {
q = new ConcurrentLinkedQueue<HttpRequestPacket>();
httpRequestQueueAttr.set(c, q);
}
return q;
}
private static void prepareRequest(final HttpRequestPacket request) {
String contentType = request.getContentType();
if (contentType != null) {
request.getHeaders().setValue(Header.ContentType).setString(contentType);
}
}
private static boolean checkKeepAlive(final HttpResponsePacket response) {
final int statusCode = response.getStatus();
final boolean isExpectContent = response.isExpectContent();
boolean keepAlive = !statusDropsConnection(statusCode) ||
(!isExpectContent || !response.isChunked() || response.getContentLength() == -1);
if (keepAlive) {
final DataChunk cVal =
response.getHeaders().getValue(Header.Connection);
if (response.getProtocol().compareTo(Protocol.HTTP_1_1) < 0) {
keepAlive = cVal != null && cVal.equalsIgnoreCase(KEEPALIVE_BYTES);
} else {
keepAlive = cVal == null || !cVal.equalsIgnoreCase(CLOSE_BYTES);
}
}
return keepAlive;
}
private static final class ClientHttpResponseImpl
extends HttpResponsePacket implements HttpPacketParsing {
private static final ThreadCache.CachedTypeIndex<ClientHttpResponseImpl> CACHE_IDX =
ThreadCache.obtainIndex(ClientHttpResponseImpl.class, 16);
public static ClientHttpResponseImpl create() {
final ClientHttpResponseImpl httpResponseImpl =
ThreadCache.takeFromCache(CACHE_IDX);
if (httpResponseImpl != null) {
return httpResponseImpl;
}
return new ClientHttpResponseImpl();
}
private boolean contentTypeParsed;
private boolean ;
private final HttpCodecFilter.HeaderParsingState ;
private final HttpCodecFilter.ContentParsingState contentParsingState;
private ClientHttpResponseImpl() {
this.headerParsingState = new HttpCodecFilter.HeaderParsingState();
this.contentParsingState = new HttpCodecFilter.ContentParsingState();
}
public void initialize(final HttpCodecFilter filter,
final int initialOffset,
final int maxHeaderSize,
final int maxNumberOfHeaders) {
headerParsingState.initialize(filter, initialOffset, maxHeaderSize);
headers.setMaxNumHeaders(maxNumberOfHeaders);
contentParsingState.trailerHeaders.setMaxNumHeaders(maxNumberOfHeaders);
}
@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
protected HttpPacketParsing getParsingState() {
return this;
}
@Override
public HttpCodecFilter.HeaderParsingState () {
return headerParsingState;
}
@Override
public ContentParsingState getContentParsingState() {
return contentParsingState;
}
@Override
public ProcessingState getProcessingState() {
return getRequest().getProcessingState();
}
@Override
public boolean () {
return isHeaderParsed;
}
@Override
public void (boolean isHeaderParsed) {
this.isHeaderParsed = isHeaderParsed;
}
@Override
protected void reset() {
contentTypeParsed = false;
isHeaderParsed = false;
headerParsingState.recycle();
contentParsingState.recycle();
super.reset();
}
@Override
public void recycle() {
if (getRequest().isExpectContent()) {
return;
}
reset();
ThreadCache.putToCache(CACHE_IDX, this);
}
}
}