/*
 * Copyright (c) 2010, 2017 Oracle and/or its affiliates. All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v. 2.0, which is available at
 * http://www.eclipse.org/legal/epl-2.0.
 *
 * This Source Code may also be made available under the following Secondary
 * Licenses when the conditions for such availability set forth in the
 * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
 * version 2 with the GNU Classpath Exception, which is available at
 * https://www.gnu.org/software/classpath/license.html.
 *
 * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
 */

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.*;

Client side HttpCodecFilter implementation, which is responsible for decoding HttpResponsePacket and encoding HttpRequestPacket messages. This Filter is usually used, when we build an asynchronous HTTP client connection.
Author:Alexey Stashok
See Also:
/** * Client side {@link HttpCodecFilter} implementation, which is responsible for * decoding {@link HttpResponsePacket} and encoding {@link HttpRequestPacket} messages. * * This <tt>Filter</tt> is usually used, when we build an asynchronous HTTP client * connection. * * @see HttpCodecFilter * @see HttpServerFilter * * @author Alexey Stashok */
public class HttpClientFilter extends HttpCodecFilter { private final Attribute<Queue<HttpRequestPacket>> httpRequestQueueAttr; private final Attribute<HttpResponsePacket> httpResponseInProcessAttr;
Constructor, which creates HttpClientFilter instance
/** * Constructor, which creates <tt>HttpClientFilter</tt> instance */
public HttpClientFilter() { this(DEFAULT_MAX_HTTP_PACKET_HEADER_SIZE); }
Constructor, which creates HttpClientFilter instance, with the specific secure and max header size parameter.
Params:
  • maxHeadersSize – the maximum size of the HTTP message header.
/** * Constructor, which creates <tt>HttpClientFilter</tt> instance, * with the specific secure and max header size parameter. * * @param maxHeadersSize the maximum size of the HTTP message header. */
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); }
The method is called, once we have received a Buffer, which has to be transformed into HTTP response packet part. Filter gets Buffer, which represents a part or complete HTTP response message. As the result of "read" transformation - we will get HttpContent message, which will represent HTTP response packet content (might be zero length content) and reference to a HttpHeader, which contains HTTP response message header.
Params:
  • ctx – Request processing context
Throws:
Returns:NextAction
/** * The method is called, once we have received a {@link Buffer}, * which has to be transformed into HTTP response packet part. * * Filter gets {@link Buffer}, which represents a part or complete HTTP * response message. As the result of "read" transformation - we will get * {@link HttpContent} message, which will represent HTTP response packet * content (might be zero length content) and reference * to a {@link HttpHeader}, which contains HTTP response message header. * * @param ctx Request processing context * * @return {@link NextAction} * @throws IOException */
@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 { // normally it shouldn't happen, but we use this path in tests to // check HttpResponsePacket parsing 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)); // check if the client HTTP Filter knows how to parse the // incoming HTTP response of the specific HTTP protocol version. if (httpRequest != null) { try { final Protocol protocol = httpRequest.getProtocol(); if (Protocol.HTTP_2_0.equals(protocol)) { // for the HTTP/2.0 (PRI request) we don't expect text-based HTTP response. // create dummy HTTP response httpResponse.setProtocol(httpRequest.getProtocol()); httpResponse.setStatus(HttpStatus.OK_200); httpResponse.setExpectContent(true); httpResponse.setHeaderParsed(true); } } catch (IllegalStateException ise) { // unknown protocol } } return httpResponse; } @Override protected boolean onHttpPacketParsed(HttpHeader httpHeader, FilterChainContext ctx) { final Connection connection = ctx.getConnection(); clearResponse(connection); return false; } @Override protected boolean onHttpHeaderParsed(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) { // if request is null - we can't really check and set the keep-alive state response.getProcessingState().setKeepAlive(checkKeepAlive(response)); } return false; } @Override protected void onHttpHeaderError(final HttpHeader httpHeader, final FilterChainContext ctx, final Throwable t) throws IOException { throw new IllegalStateException(t); } @Override protected void onHttpContentError(final HttpHeader httpHeader, final FilterChainContext ctx, final Throwable t) throws IOException { httpHeader.setContentBroken(true); throw new IllegalStateException(t); } @Override protected void onInitialLineParsed(final HttpHeader httpHeader, final FilterChainContext ctx) { // no-op } @Override protected void onInitialLineEncoded(HttpHeader header, FilterChainContext ctx) { // no-op } @Override protected void onHttpHeadersParsed(final HttpHeader httpHeader, final MimeHeaders headers, final FilterChainContext ctx) { // no-op } @Override protected void onHttpHeadersEncoded(HttpHeader httpHeader, FilterChainContext ctx) { // no-op } @Override protected void onHttpContentParsed(HttpContent content, FilterChainContext ctx) { // no-op } @Override protected void onHttpContentEncoded(HttpContent content, FilterChainContext ctx) { // no-op } 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 decodeInitialLineFromBytes(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; //noinspection LoopStatementThatDoesntLoop while(true) { int subState = parsingState.subState; switch(subState) { case 0: { // HTTP protocol 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: { // skip spaces after the HTTP protocol 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 : { // parse the status code 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: { // skip spaces after the status code 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: { // HTTP response reason-phrase 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) { // reset the parsing state in preparation to parse // another initial line which represents the final // response from the server after it has sent a // 100-Continue. 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 decodeInitialLineFromBuffer(final FilterChainContext ctx, final HttpPacketParsing httpPacket, final HeaderParsingState parsingState, final Buffer input) { final HttpResponsePacket httpResponse = (HttpResponsePacket) httpPacket; final int packetLimit = parsingState.packetLimit; //noinspection LoopStatementThatDoesntLoop while(true) { int subState = parsingState.subState; switch(subState) { case 0: { // HTTP protocol 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: { // skip spaces after the HTTP protocol 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 : { // parse the status code 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: { // skip spaces after the status code 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: { // HTTP response reason-phrase 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) { // reset the parsing state in preparation to parse // another initial line which represents the final // response from the server after it has sent a // 100-Continue. 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); // double-check the transfer encoding here if (keepAlive) { // Check the Connection header final DataChunk cVal = response.getHeaders().getValue(Header.Connection); if (response.getProtocol().compareTo(Protocol.HTTP_1_1) < 0) { // HTTP 1.0 response // "Connection: keep-alive" should be specified explicitly keepAlive = cVal != null && cVal.equalsIgnoreCase(KEEPALIVE_BYTES); } else { // HTTP 1.1+ // keep-alive by default, if there's no "Connection: close" 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(); }
content type parsed flag.
/** * content type parsed flag. */
private boolean contentTypeParsed; private boolean isHeaderParsed; private final HttpCodecFilter.HeaderParsingState 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 parseContentTypeHeader() { 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 getHeaderParsingState() { return headerParsingState; } @Override public ContentParsingState getContentParsingState() { return contentParsingState; } @Override public ProcessingState getProcessingState() { return getRequest().getProcessingState(); } @Override public boolean isHeaderParsed() { return isHeaderParsed; } @Override public void setHeaderParsed(boolean isHeaderParsed) { this.isHeaderParsed = isHeaderParsed; }
{@inheritDoc}
/** * {@inheritDoc} */
@Override protected void reset() { contentTypeParsed = false; isHeaderParsed = false; headerParsingState.recycle(); contentParsingState.recycle(); super.reset(); }
{@inheritDoc}
/** * {@inheritDoc} */
@Override public void recycle() { if (getRequest().isExpectContent()) { return; } reset(); ThreadCache.putToCache(CACHE_IDX, this); } } }