All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.ning.http.client.providers.grizzly.GrizzlyAsyncHttpProvider Maven / Gradle / Ivy

Go to download

Async Http Client library purpose is to allow Java applications to easily execute HTTP requests and asynchronously process the HTTP responses.

The newest version!
/*
 * Copyright (c) 2012-2014 Sonatype, Inc. All rights reserved.
 *
 * This program is licensed to you under the Apache License Version 2.0,
 * and you may not use this file except in compliance with the Apache License Version 2.0.
 * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0.
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the Apache License Version 2.0 is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under.
 */

package com.ning.http.client.providers.grizzly;

import static com.ning.http.client.providers.grizzly.GrizzlyAsyncHttpProviderConfig.Property.BUFFER_WEBSOCKET_FRAGMENTS;
import static com.ning.http.client.providers.grizzly.GrizzlyAsyncHttpProviderConfig.Property.MAX_HTTP_PACKET_HEADER_SIZE;
import static com.ning.http.client.providers.grizzly.GrizzlyAsyncHttpProviderConfig.Property.TRANSPORT_CUSTOMIZER;
import static com.ning.http.util.AsyncHttpProviderUtils.getNonEmptyPath;
import static com.ning.http.util.MiscUtils.isNonEmpty;

import org.glassfish.grizzly.Buffer;
import org.glassfish.grizzly.CloseListener;
import org.glassfish.grizzly.CloseType;
import org.glassfish.grizzly.Closeable;
import org.glassfish.grizzly.CompletionHandler;
import org.glassfish.grizzly.Connection;
import org.glassfish.grizzly.EmptyCompletionHandler;
import org.glassfish.grizzly.FileTransfer;
import org.glassfish.grizzly.Grizzly;
import org.glassfish.grizzly.WriteResult;
import org.glassfish.grizzly.asyncqueue.AsyncQueueWriter;
import org.glassfish.grizzly.attributes.Attribute;
import org.glassfish.grizzly.filterchain.BaseFilter;
import org.glassfish.grizzly.filterchain.FilterChain;
import org.glassfish.grizzly.filterchain.FilterChainBuilder;
import org.glassfish.grizzly.filterchain.FilterChainContext;
import org.glassfish.grizzly.filterchain.FilterChainEvent;
import org.glassfish.grizzly.filterchain.NextAction;
import org.glassfish.grizzly.filterchain.TransportFilter;
import org.glassfish.grizzly.http.ContentEncoding;
import org.glassfish.grizzly.http.EncodingFilter;
import org.glassfish.grizzly.http.GZipContentEncoding;
import org.glassfish.grizzly.http.HttpClientFilter;
import org.glassfish.grizzly.http.HttpContent;
import org.glassfish.grizzly.http.HttpHeader;
import org.glassfish.grizzly.http.HttpRequestPacket;
import org.glassfish.grizzly.http.HttpResponsePacket;
import org.glassfish.grizzly.http.Method;
import org.glassfish.grizzly.http.Protocol;
import org.glassfish.grizzly.http.util.CookieSerializerUtils;
import org.glassfish.grizzly.http.util.DataChunk;
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.impl.FutureImpl;
import org.glassfish.grizzly.impl.SafeFutureImpl;
import org.glassfish.grizzly.memory.Buffers;
import org.glassfish.grizzly.memory.MemoryManager;
import org.glassfish.grizzly.nio.RoundRobinConnectionDistributor;
import org.glassfish.grizzly.nio.transport.TCPNIOConnectorHandler;
import org.glassfish.grizzly.nio.transport.TCPNIOTransport;
import org.glassfish.grizzly.nio.transport.TCPNIOTransportBuilder;
import org.glassfish.grizzly.ssl.SSLEngineConfigurator;
import org.glassfish.grizzly.ssl.SSLFilter;
import org.glassfish.grizzly.strategies.SameThreadIOStrategy;
import org.glassfish.grizzly.strategies.WorkerThreadIOStrategy;
import org.glassfish.grizzly.threadpool.ThreadPoolConfig;
import org.glassfish.grizzly.utils.Charsets;
import org.glassfish.grizzly.utils.DelayedExecutor;
import org.glassfish.grizzly.utils.Futures;
import org.glassfish.grizzly.utils.IdleTimeoutFilter;
import org.glassfish.grizzly.websockets.ClosingFrame;
import org.glassfish.grizzly.websockets.DataFrame;
import org.glassfish.grizzly.websockets.HandShake;
import org.glassfish.grizzly.websockets.ProtocolHandler;
import org.glassfish.grizzly.websockets.SimpleWebSocket;
import org.glassfish.grizzly.websockets.Version;
import org.glassfish.grizzly.websockets.WebSocketFilter;
import org.glassfish.grizzly.websockets.WebSocketHolder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.ning.http.client.AsyncHandler;
import com.ning.http.client.AsyncHandlerExtensions;
import com.ning.http.client.AsyncHttpClient;
import com.ning.http.client.AsyncHttpClientConfig;
import com.ning.http.client.AsyncHttpProvider;
import com.ning.http.client.AsyncHttpProviderConfig;
import com.ning.http.client.Body;
import com.ning.http.client.BodyGenerator;
import com.ning.http.client.FluentCaseInsensitiveStringsMap;
import com.ning.http.client.ListenableFuture;
import com.ning.http.client.MaxRedirectException;
import com.ning.http.client.Param;
import com.ning.http.client.ProxyServer;
import com.ning.http.client.Realm;
import com.ning.http.client.Request;
import com.ning.http.client.RequestBuilder;
import com.ning.http.client.UpgradeHandler;
import com.ning.http.client.cookie.Cookie;
import com.ning.http.client.cookie.CookieDecoder;
import com.ning.http.client.filter.FilterContext;
import com.ning.http.client.filter.ResponseFilter;
import com.ning.http.client.listener.TransferCompletionHandler;
import com.ning.http.client.multipart.MultipartBody;
import com.ning.http.client.multipart.MultipartUtils;
import com.ning.http.client.multipart.Part;
import com.ning.http.client.ntlm.NTLMEngine;
import com.ning.http.client.uri.UriComponents;
import com.ning.http.client.websocket.WebSocket;
import com.ning.http.client.websocket.WebSocketByteListener;
import com.ning.http.client.websocket.WebSocketCloseCodeReasonListener;
import com.ning.http.client.websocket.WebSocketListener;
import com.ning.http.client.websocket.WebSocketPingListener;
import com.ning.http.client.websocket.WebSocketPongListener;
import com.ning.http.client.websocket.WebSocketTextListener;
import com.ning.http.client.websocket.WebSocketUpgradeHandler;
import com.ning.http.util.AsyncHttpProviderUtils;
import com.ning.http.util.AuthenticatorUtils;
import com.ning.http.util.ProxyUtils;
import com.ning.http.util.SslUtils;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.InetSocketAddress;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLEncoder;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;

/**
 * A Grizzly 2.0-based implementation of {@link AsyncHttpProvider}.
 *
 * @author The Grizzly Team
 * @since 1.7.0
 */
public class GrizzlyAsyncHttpProvider implements AsyncHttpProvider {

    private final static Logger LOGGER = LoggerFactory.getLogger(GrizzlyAsyncHttpProvider.class);
    private static final boolean SEND_FILE_SUPPORT;
    static {
        SEND_FILE_SUPPORT = /*configSendFileSupport()*/ false;
    }
    private static final Attribute REQUEST_STATE_ATTR =
            Grizzly.DEFAULT_ATTRIBUTE_BUILDER.createAttribute(HttpTransactionContext.class.getName());
    private final static NTLMEngine ntlmEngine = new NTLMEngine();

    public static final IOException REMOTELY_CLOSED_EXCEPTION = new IOException("Remotely Closed");

    static {
        REMOTELY_CLOSED_EXCEPTION.setStackTrace(new StackTraceElement[] {});
    }
    
    private final BodyHandlerFactory bodyHandlerFactory = new BodyHandlerFactory();

    private final TCPNIOTransport clientTransport;
    private final AsyncHttpClientConfig clientConfig;
    private final GrizzlyAsyncHttpProviderConfig providerConfig;
    private final ConnectionManager connectionManager;

    DelayedExecutor.Resolver resolver;
    private DelayedExecutor timeoutExecutor;




    // ------------------------------------------------------------ Constructors


    public GrizzlyAsyncHttpProvider(final AsyncHttpClientConfig clientConfig) {

        this.clientConfig = clientConfig;
        this.providerConfig =
                clientConfig.getAsyncHttpProviderConfig() instanceof GrizzlyAsyncHttpProviderConfig ?
                (GrizzlyAsyncHttpProviderConfig) clientConfig.getAsyncHttpProviderConfig()
                : new GrizzlyAsyncHttpProviderConfig();
        final TCPNIOTransportBuilder builder = TCPNIOTransportBuilder.newInstance();
        clientTransport = builder.build();
        initializeTransport(clientConfig);
        connectionManager = new ConnectionManager(this, clientTransport, providerConfig);
        try {
            clientTransport.start();
        } catch (IOException ioe) {
            throw new RuntimeException(ioe);
        }

    }


    // ------------------------------------------ Methods from AsyncHttpProvider


    @SuppressWarnings({"unchecked"})
    @Override
    public  ListenableFuture execute(final Request request,
                                           final AsyncHandler handler) throws IOException {

        if (clientTransport.isStopped()) {
            throw new IOException("AsyncHttpClient has been closed.");
        }
        final ProxyServer proxy = ProxyUtils.getProxyServer(clientConfig, request);
        final GrizzlyResponseFuture future = new GrizzlyResponseFuture(this, request, handler, proxy);
        future.setDelegate(SafeFutureImpl.create());
        final CompletionHandler  connectHandler = new CompletionHandler() {
            @Override
            public void cancelled() {
                future.cancel(true);
            }

            @Override
            public void failed(final Throwable throwable) {
                future.abort(throwable);
            }

            @Override
            public void completed(final Connection c) {
                try {
                    execute(c, request, handler, future, true);
                } catch (Exception e) {
                    if (e instanceof RuntimeException) {
                        failed(e);
                    } else if (e instanceof IOException) {
                        failed(e);
                    }
                    if (LOGGER.isWarnEnabled()) {
                        LOGGER.warn(e.toString(), e);
                    }
                }
            }

            @Override
            public void updated(final Connection c) {
                // no-op
            }
        };

        try {
            connectionManager.doAsyncTrackedConnection(request, future, connectHandler);
        } catch (Exception e) {
            if (e instanceof RuntimeException) {
                throw (RuntimeException) e;
            } else if (e instanceof IOException) {
                throw (IOException) e;
            }
            if (LOGGER.isWarnEnabled()) {
                LOGGER.warn(e.toString(), e);
            }
        }

        return future;
    }

    @Override
    public void close() {

        try {
            connectionManager.destroy();
            clientTransport.shutdownNow();
            final ExecutorService service = clientConfig.executorService();
            if (service != null) {
                service.shutdown();
            }
            if (timeoutExecutor != null) {
                timeoutExecutor.stop();
                timeoutExecutor.getThreadPool().shutdownNow();
            }
        } catch (IOException ignored) { }

    }

    // ------------------------------------------------------- Protected Methods


    @SuppressWarnings({"unchecked"})
    protected  ListenableFuture execute(final Connection c,
                                              final Request request,
                                              final AsyncHandler handler,
                                              final GrizzlyResponseFuture future,
                                              final boolean forceTxContextExist)
    throws IOException {

        try {
            if (forceTxContextExist && HttpTransactionContext.get(c) == null) {
                HttpTransactionContext.set(c,
                        new HttpTransactionContext(this, future, request, handler));
            }
            c.write(request, createWriteCompletionHandler(future));
        } catch (Exception e) {
            if (e instanceof RuntimeException) {
                throw (RuntimeException) e;
            } else if (e instanceof IOException) {
                throw (IOException) e;
            }
            if (LOGGER.isWarnEnabled()) {
                LOGGER.warn(e.toString(), e);
            }
        }

        return future;
    }


    protected void initializeTransport(final AsyncHttpClientConfig clientConfig) {

        final FilterChainBuilder fcb = FilterChainBuilder.stateless();
        fcb.add(new TransportFilter());

        final int timeout = clientConfig.getRequestTimeout();
        if (timeout > 0) {
            int delay = 500;
            if (timeout < delay) {
                delay = timeout - 10;
            }
            timeoutExecutor = IdleTimeoutFilter.createDefaultIdleDelayedExecutor(delay, TimeUnit.MILLISECONDS);
            timeoutExecutor.start();
            final IdleTimeoutFilter.TimeoutResolver timeoutResolver =
                    new IdleTimeoutFilter.TimeoutResolver() {
                        @Override
                        public long getTimeout(FilterChainContext ctx) {
                            final HttpTransactionContext context =
                                    HttpTransactionContext.get(ctx.getConnection());
                            if (context != null) {
                                if (context.isWSRequest) {
                                    return clientConfig.getWebSocketTimeout();
                                }
                                final long timeout = context.request.getRequestTimeout();
                                if (timeout > 0) {
                                    return timeout;
                                }
                            }
                            return timeout;
                        }
                    };
            final IdleTimeoutFilter timeoutFilter = new IdleTimeoutFilter(timeoutExecutor,
                    timeoutResolver,
                    new IdleTimeoutFilter.TimeoutHandler() {
                        public void onTimeout(Connection connection) {
                            timeout(connection);
                        }
                    });
            fcb.add(timeoutFilter);
            resolver = timeoutFilter.getResolver();
        }

        SSLContext context = clientConfig.getSSLContext();
        boolean defaultSecState = (context != null);
        if (context == null) {
            try {
                context = SslUtils.getInstance().getSSLContext(clientConfig.isAcceptAnyCertificate());
            } catch (Exception e) {
                throw new IllegalStateException(e);
            }
        }
        final SSLEngineConfigurator configurator =
                new SSLEngineConfigurator(context,
                        true,
                        false,
                        false);
        final SwitchingSSLFilter sslFilter =
                new SwitchingSSLFilter(configurator, defaultSecState);
        if (clientConfig.getHostnameVerifier() != null) {
            sslFilter.addHandshakeListener(new HostnameVerifierListener());
        }
        fcb.add(sslFilter);
        
        final AsyncHttpClientEventFilter eventFilter = new
                AsyncHttpClientEventFilter(this, (Integer) providerConfig.getProperty(MAX_HTTP_PACKET_HEADER_SIZE));
        final AsyncHttpClientFilter clientFilter =
                new AsyncHttpClientFilter(clientConfig);
        ContentEncoding[] encodings = eventFilter.getContentEncodings();
        if (encodings.length > 0) {
            for (ContentEncoding encoding : encodings) {
                eventFilter.removeContentEncoding(encoding);
            }
        }
        if (clientConfig.isCompressionEnabled()) {
            eventFilter.addContentEncoding(
                    new GZipContentEncoding(512,
                                            512,
                                            new ClientEncodingFilter()));
        }
        fcb.add(eventFilter);
        fcb.add(clientFilter);
        clientTransport.getAsyncQueueIO().getWriter()
                       .setMaxPendingBytesPerConnection(AsyncQueueWriter.AUTO_SIZE);
        
        clientTransport.setNIOChannelDistributor(
                new RoundRobinConnectionDistributor(clientTransport, false, false));
        
        final int kernelThreadsCount =
                clientConfig.getIoThreadMultiplier() *
                Runtime.getRuntime().availableProcessors();
        
        clientTransport.setSelectorRunnersCount(kernelThreadsCount);
        clientTransport.setKernelThreadPoolConfig(
                ThreadPoolConfig.defaultConfig()
                .setCorePoolSize(kernelThreadsCount)
                .setMaxPoolSize(kernelThreadsCount)
                .setPoolName("grizzly-ahc-kernel")
//                .setPoolName(discoverTestName("grizzly-ahc-kernel")) // uncomment for tests to track down the leaked threads
        );

        
        final TransportCustomizer customizer = (TransportCustomizer)
                providerConfig.getProperty(TRANSPORT_CUSTOMIZER);
        if (customizer != null) {
            customizer.customize(clientTransport, fcb);
        } else {
            doDefaultTransportConfig();
        }
        fcb.add(new WebSocketFilter());
        
        clientTransport.setProcessor(fcb.build());

    }


    // ------------------------------------------------- Package Private Methods


    void touchConnection(final Connection c, final Request request) {

        final long perRequestTimeout = request.getRequestTimeout();
        if (perRequestTimeout > 0) {
            final long newTimeout = System.currentTimeMillis() + perRequestTimeout;
            if (resolver != null) {
                resolver.setTimeoutMillis(c, newTimeout);
            }
        } else {
            final long timeout = clientConfig.getRequestTimeout();
            if (timeout > 0) {
                if (resolver != null) {
                    resolver.setTimeoutMillis(c, System.currentTimeMillis() + timeout);
                }
            }
        }

    }


    // --------------------------------------------------------- Private Methods


    private static boolean configSendFileSupport() {

        return !((System.getProperty("os.name").equalsIgnoreCase("linux")
                && !linuxSendFileSupported())
                || System.getProperty("os.name").equalsIgnoreCase("HP-UX"));
    }


    private static boolean linuxSendFileSupported() {
        final String version = System.getProperty("java.version");
        if (version.startsWith("1.6")) {
            int idx = version.indexOf('_');
            if (idx == -1) {
                return false;
            }
            final int patchRev = Integer.parseInt(version.substring(idx + 1));
            return (patchRev >= 18);
        } else {
            return version.startsWith("1.7") || version.startsWith("1.8");
        }
    }
    
    private void doDefaultTransportConfig() {
        final ExecutorService service = clientConfig.executorService();
        if (service != null) {
            clientTransport.setIOStrategy(WorkerThreadIOStrategy.getInstance());
            clientTransport.setWorkerThreadPool(service);
        } else {
            clientTransport.setIOStrategy(SameThreadIOStrategy.getInstance());
        }
    }


    private  CompletionHandler createWriteCompletionHandler(final GrizzlyResponseFuture future) {
        return new CompletionHandler() {

            public void cancelled() {
                future.cancel(true);
            }

            public void failed(Throwable throwable) {
                future.abort(throwable);
            }

            public void completed(WriteResult result) {
            }

            public void updated(WriteResult result) {
                // no-op
            }

        };
    }


    void timeout(final Connection c) {

        final HttpTransactionContext context = HttpTransactionContext.remove(c);
        context.abort(new TimeoutException("Timeout exceeded"));

    }

    static int getPort(final UriComponents uri, final int p) {
        int port = p;
        if (port == -1) {
            final String protocol = uri.getScheme().toLowerCase(Locale.ENGLISH);
            if ("http".equals(protocol) || "ws".equals(protocol)) {
                port = 80;
            } else if ("https".equals(protocol) || "wss".equals(protocol)) {
                port = 443;
            } else {
                throw new IllegalArgumentException("Unknown protocol: " + protocol);
            }
        }
        return port;
    }


    @SuppressWarnings({"unchecked"})
    boolean sendRequest(final FilterChainContext ctx,
                     final Request request,
                     final HttpRequestPacket requestPacket,
                     final BodyHandler bodyHandler)
    throws IOException {

        boolean isWriteComplete = true;
        
        if (bodyHandler != null) { // Check if the HTTP request has body
            final HttpTransactionContext context = HttpTransactionContext.get(ctx.getConnection());
            
            context.bodyHandler = bodyHandler;
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("REQUEST: " + requestPacket.toString());
            }
            isWriteComplete = bodyHandler.doHandle(ctx, request, requestPacket);
        } else {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("REQUEST: " + requestPacket.toString());
            }
            ctx.write(requestPacket, ctx.getTransportContext().getCompletionHandler());
        }

        
        return isWriteComplete;
    }

    // ----------------------------------------------------------- Inner Classes


    private interface StatusHandler {

        public enum InvocationStatus {
            CONTINUE,
            STOP
        }

        boolean handleStatus(final HttpResponsePacket httpResponse,
                             final HttpTransactionContext httpTransactionContext,
                             final FilterChainContext ctx);

        boolean handlesStatus(final int statusCode);

    } // END StatusHandler


    static final class HttpTransactionContext {

        final AtomicInteger redirectCount = new AtomicInteger(0);

        final int maxRedirectCount;
        final boolean redirectsAllowed;
        final GrizzlyAsyncHttpProvider provider;

        Request request;
        UriComponents requestUri;
        AsyncHandler handler;
        BodyHandler bodyHandler;
        StatusHandler statusHandler;
        StatusHandler.InvocationStatus invocationStatus =
                StatusHandler.InvocationStatus.CONTINUE;
        GrizzlyResponseStatus responseStatus;
        GrizzlyResponseFuture future;
        String lastRedirectURI;
        AtomicLong totalBodyWritten = new AtomicLong();
        AsyncHandler.STATE currentState;
        
        UriComponents wsRequestURI;
        boolean isWSRequest;
        HandShake handshake;
        ProtocolHandler protocolHandler;
        WebSocket webSocket;
        boolean establishingTunnel;
        
        private final CloseListener listener = new CloseListener() {
            @Override
            public void onClosed(Closeable closeable, CloseType type) throws IOException {
                if (responseStatus != null && // responseStatus==null if request wasn't even sent
                        isGracefullyFinishResponseOnClose()) {
                    // Connection was closed.
                    // This event is fired only for responses, which don't have
                    // associated transfer-encoding or content-length.
                    // We have to complete such a request-response processing gracefully.
                    final Connection c = responseStatus.getResponse()
                            .getRequest().getConnection();
                    final FilterChain fc = (FilterChain) c.getProcessor();

                    fc.fireEventUpstream(c,
                            new GracefulCloseEvent(HttpTransactionContext.this), null);
                } else if (CloseType.REMOTELY.equals(type)) {
                    abort(REMOTELY_CLOSED_EXCEPTION);
                }
            }
        };

        // -------------------------------------------------------- Static methods
        
        static void set(final Connection c, final HttpTransactionContext httpTxContext) {
            c.addCloseListener(httpTxContext.listener);
            REQUEST_STATE_ATTR.set(c, httpTxContext);
        }

        static HttpTransactionContext remove(final Connection c) {
            final HttpTransactionContext httpTxContext = REQUEST_STATE_ATTR.remove(c);
            c.removeCloseListener(httpTxContext.listener);
            return httpTxContext;
        }

        static HttpTransactionContext get(final Connection c) {
            return REQUEST_STATE_ATTR.get(c);
        }
        
        // -------------------------------------------------------- Constructors


        HttpTransactionContext(final GrizzlyAsyncHttpProvider provider,
                               final GrizzlyResponseFuture future,
                               final Request request,
                               final AsyncHandler handler) {

            this.provider = provider;
            this.future = future;
            this.request = request;
            this.handler = handler;
            redirectsAllowed = provider.clientConfig.isFollowRedirect();
            maxRedirectCount = provider.clientConfig.getMaxRedirects();
            this.requestUri = request.getURI();

        }


        // ----------------------------------------------------- Private Methods


        HttpTransactionContext copy() {
            final HttpTransactionContext newContext =
                    new HttpTransactionContext(provider,
                                               future,
                                               request,
                                               handler);
            newContext.invocationStatus = invocationStatus;
            newContext.bodyHandler = bodyHandler;
            newContext.currentState = currentState;
            newContext.statusHandler = statusHandler;
            newContext.lastRedirectURI = lastRedirectURI;
            newContext.redirectCount.set(redirectCount.get());
            return newContext;

        }

        boolean isGracefullyFinishResponseOnClose() {
            final HttpResponsePacket response = responseStatus.getResponse();
            return !response.getProcessingState().isKeepAlive() &&
                    !response.isChunked() && response.getContentLength() == -1;
        }

        void abort(final Throwable t) {
            if (future != null) {
                future.abort(t);
            }
        }

        void done() {
            if (future != null) {
                future.done();
            }
        }

        @SuppressWarnings({"unchecked"})
        void result(Object result) {
            if (future != null) {
                future.delegate.result(result);
                future.done();
            }
        }

        boolean isTunnelEstablished(final Connection c) {
            return c.getAttributes().getAttribute("tunnel-established") != null;
        }


        void tunnelEstablished(final Connection c) {
            c.getAttributes().setAttribute("tunnel-established", Boolean.TRUE);
        }


    } // END HttpTransactionContext


    // ---------------------------------------------------------- Nested Classes

    private static final class ContinueEvent implements FilterChainEvent {

        private final HttpTransactionContext context;


        // -------------------------------------------------------- Constructors


        ContinueEvent(final HttpTransactionContext context) {

            this.context = context;

        }


        // --------------------------------------- Methods from FilterChainEvent


        @Override
        public Object type() {
            return ContinueEvent.class;
        }

    } // END ContinueEvent

    /**
     * {@link FilterChainEvent} to gracefully complete the request-response processing
     * when {@link Connection} is getting closed by the remote host.
     *
     * @since 1.8.7
     * @author The Grizzly Team
     */
    public static class GracefulCloseEvent implements FilterChainEvent {
        private final HttpTransactionContext httpTxContext;

        public GracefulCloseEvent(HttpTransactionContext httpTxContext) {
            this.httpTxContext = httpTxContext;
        }

        public HttpTransactionContext getHttpTxContext() {
            return httpTxContext;
        }

        @Override
        public Object type() {
            return GracefulCloseEvent.class;
        }
    }  // END GracefulCloseEvent
        
    private final class AsyncHttpClientFilter extends BaseFilter {


        private final AsyncHttpClientConfig config;


        // -------------------------------------------------------- Constructors


        AsyncHttpClientFilter(final AsyncHttpClientConfig config) {

            this.config = config;

        }


        // --------------------------------------------- Methods from BaseFilter


        @Override
        public NextAction handleWrite(final FilterChainContext ctx)
        throws IOException {

            Object message = ctx.getMessage();
            if (message instanceof Request) {
                ctx.setMessage(null);
                if (!sendAsGrizzlyRequest((Request) message, ctx)) {
                    return ctx.getSuspendAction();
                }
            } else if (message instanceof Buffer) {
                return ctx.getInvokeAction();
            }

            return ctx.getStopAction();
        }

        @Override
        public NextAction handleEvent(final FilterChainContext ctx,
                                      final FilterChainEvent event)
        throws IOException {

            final Object type = event.type();
            if (type == ContinueEvent.class) {
                final ContinueEvent continueEvent = (ContinueEvent) event;
                ((ExpectHandler) continueEvent.context.bodyHandler).finish(ctx);
            }

            return ctx.getStopAction();

        }

        // ----------------------------------------------------- Private Methods


        private boolean sendAsGrizzlyRequest(final Request request,
                                          final FilterChainContext ctx)
        throws IOException {
            
            final Connection connection = ctx.getConnection();
            
            final HttpTransactionContext httpCtx = HttpTransactionContext.get(connection);
            if (isUpgradeRequest(httpCtx.handler) && isWSRequest(httpCtx.requestUri)) {
                httpCtx.isWSRequest = true;
                convertToUpgradeRequest(httpCtx);
            }
            final Request req = httpCtx.request;
            final UriComponents uri = req.getURI();
            final Method method = Method.valueOf(request.getMethod());
            final HttpRequestPacket.Builder builder = HttpRequestPacket.builder();
            boolean secure = "https".equals(uri.getScheme());
            builder.method(method);
            builder.protocol(Protocol.HTTP_1_1);
            
            if (!request.getHeaders().containsKey(Header.Host.toString())) {
                String host = request.getVirtualHost();
                if (host != null) {
                    builder.header(Header.Host, host);
                } else {
                    if (uri.getPort() == -1) {
                        builder.header(Header.Host, uri.getHost());
                    } else {
                        builder.header(Header.Host, uri.getHost() + ':' + uri.getPort());
                    }
                }
            }
            final ProxyServer proxy = ProxyUtils.getProxyServer(config, request);
            final boolean useProxy = proxy != null;
            if (useProxy) {
                if ((secure || httpCtx.isWSRequest) && !httpCtx.isTunnelEstablished(connection)) {
                    secure = false;
                    httpCtx.establishingTunnel = true;
                    builder.method(Method.CONNECT);
                    builder.uri(AsyncHttpProviderUtils.getAuthority(uri));
                } else if ((secure || httpCtx.isWSRequest) && config.isUseRelativeURIsWithConnectProxies()){
                    builder.uri(getNonEmptyPath(uri));
                } else {
                    builder.uri(uri.toUrl());
                }
            } else {
                builder.uri(getNonEmptyPath(uri));
            }
            
            final BodyHandler bodyHandler = isPayloadAllowed(method) ?
                    bodyHandlerFactory.getBodyHandler(request) :
                    null;
            
            if (bodyHandler != null) {
                final long contentLength = request.getContentLength();
                if (contentLength >= 0) {
                    builder.contentLength(contentLength);
                    builder.chunked(false);
                } else {
                    builder.chunked(true);
                }
            }
            
            HttpRequestPacket requestPacket;
            if (httpCtx.isWSRequest && !httpCtx.establishingTunnel) {
                try {
                    final URI wsURI = httpCtx.wsRequestURI.toURI();
                    secure = "wss".equalsIgnoreCase(wsURI.getScheme());
                    httpCtx.protocolHandler = Version.RFC6455.createHandler(true);
                    httpCtx.handshake = httpCtx.protocolHandler.createHandShake(wsURI);
                    requestPacket = (HttpRequestPacket)
                            httpCtx.handshake.composeHeaders().getHttpHeader();
                } catch (URISyntaxException e) {
                    throw new IllegalArgumentException("Invalid WS URI: " + httpCtx.wsRequestURI);
                }
            } else {
                requestPacket = builder.build();
            }
            requestPacket.setSecure(secure);

            ctx.notifyDownstream(new SwitchingSSLFilter.SSLSwitchingEvent(secure, connection));

            if (!useProxy && !httpCtx.isWSRequest) {
                requestPacket.setQueryString(uri.getQuery());
                //addQueryString(request, requestPacket);
            }
            addHeaders(request, requestPacket);
            addCookies(request, requestPacket);
            addAuthorizationHeader(request, requestPacket);

            if (useProxy) {
                if (!requestPacket.getHeaders().contains(Header.ProxyConnection)) {
                    requestPacket.setHeader(Header.ProxyConnection, "keep-alive");
                }

                if (proxy.getPrincipal() != null) {
                    requestPacket.setHeader(Header.ProxyAuthorization,
                            AuthenticatorUtils.computeBasicAuthentication(proxy));
                }
            }
            final AsyncHandler h = httpCtx.handler;
            if (h instanceof TransferCompletionHandler) {
                final FluentCaseInsensitiveStringsMap map =
                        new FluentCaseInsensitiveStringsMap(request.getHeaders());
                TransferCompletionHandler.class.cast(h).headers(map);
            }
            
            requestPacket.setConnection(connection);
            return sendRequest(ctx, request, requestPacket,
                    wrapWithExpectHandlerIfNeeded(bodyHandler, requestPacket));

        }

        /**
         * check if we need to wrap the BodyHandler with ExpectHandler
         */
        private BodyHandler wrapWithExpectHandlerIfNeeded(
                final BodyHandler bodyHandler,
                final HttpRequestPacket requestPacket) {
            
            if (bodyHandler == null) {
                return null;
            }
            
            // check if we need to wrap the BodyHandler with ExpectHandler
            final MimeHeaders headers = requestPacket.getHeaders();
            final int expectHeaderIdx = headers.indexOf(Header.Expect, 0);

            return expectHeaderIdx != -1
                    && headers.getValue(expectHeaderIdx).equalsIgnoreCase("100-Continue")
                    ? new ExpectHandler(bodyHandler)
                    : bodyHandler;
        }

        private boolean isPayloadAllowed(final Method method) {
            return method.getPayloadExpectation() != Method.PayloadExpectation.NOT_ALLOWED;
        }
        
        private void addAuthorizationHeader(final Request request, final HttpRequestPacket requestPacket) {
            Realm realm = request.getRealm();
            if (realm == null) {
                realm = config.getRealm();
            }
            if (realm != null && realm.getUsePreemptiveAuth()) {
                final String authHeaderValue = generateAuthHeader(realm);
                if (authHeaderValue != null) {
                    requestPacket.addHeader(Header.Authorization, authHeaderValue);
                }
            }
        }

        private String generateAuthHeader(final Realm realm) {
            try {
                switch (realm.getAuthScheme()) {
                case BASIC:
                    return AuthenticatorUtils.computeBasicAuthentication(realm);
                case DIGEST:
                    return AuthenticatorUtils.computeDigestAuthentication(realm);
                case NTLM:
                    return ntlmEngine.generateType1Msg("NTLM " + realm.getNtlmDomain(), realm.getNtlmHost());
                default:
                    return null;
                }
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }


        private boolean isUpgradeRequest(final AsyncHandler handler) {
            return (handler instanceof UpgradeHandler);
        }


        private boolean isWSRequest(final UriComponents requestUri) {
            return requestUri.getScheme().startsWith("ws");
        }

        
        private void convertToUpgradeRequest(final HttpTransactionContext ctx) {
            final UriComponents requestUri = ctx.requestUri;

            ctx.wsRequestURI = requestUri;
            ctx.requestUri = requestUri.withNewScheme(
                    "ws".equals(requestUri.getScheme())
                            ? "http"
                            : "https");
        }

        private void addHeaders(final Request request,
                                final HttpRequestPacket requestPacket) {

            final FluentCaseInsensitiveStringsMap map = request.getHeaders();
            if (isNonEmpty(map)) {
                for (final Map.Entry> entry : map.entrySet()) {
                    final String headerName = entry.getKey();
                    final List headerValues = entry.getValue();
                    if (isNonEmpty(headerValues)) {
                        for (final String headerValue : headerValues) {
                            requestPacket.addHeader(headerName, headerValue);
                        }
                    }
                }
            }

            final MimeHeaders headers = requestPacket.getHeaders();
            if (!headers.contains(Header.Connection)) {
                requestPacket.addHeader(Header.Connection, "keep-alive");
            }

            if (!headers.contains(Header.Accept)) {
                requestPacket.addHeader(Header.Accept, "*/*");
            }

            if (!headers.contains(Header.UserAgent)) {
                requestPacket.addHeader(Header.UserAgent, config.getUserAgent());
            }


        }


        private void addCookies(final Request request,
                                final HttpRequestPacket requestPacket) {

            final Collection cookies = request.getCookies();
            if (isNonEmpty(cookies)) {
                StringBuilder sb = new StringBuilder(128);
                org.glassfish.grizzly.http.Cookie[] gCookies =
                        new org.glassfish.grizzly.http.Cookie[cookies.size()];
                convertCookies(cookies, gCookies);
                CookieSerializerUtils.serializeClientCookies(sb, gCookies);
                requestPacket.addHeader(Header.Cookie, sb.toString());
            }

        }


        private void convertCookies(final Collection cookies,
                                    final org.glassfish.grizzly.http.Cookie[] gCookies) {
            int idx = 0;
            for (final Cookie cookie : cookies) {
                gCookies[idx++] = new org.glassfish.grizzly.http.Cookie(
                        cookie.getName(), cookie.getValue());
            }

        }
    } // END AsyncHttpClientFiler


    private static final class AsyncHttpClientEventFilter extends HttpClientFilter {

        private final Map HANDLER_MAP =
                new HashMap();


        private final GrizzlyAsyncHttpProvider provider;

        // -------------------------------------------------------- Constructors


        AsyncHttpClientEventFilter(final GrizzlyAsyncHttpProvider provider, int maxHerdersSizeProperty) {
            super(maxHerdersSizeProperty);

            this.provider = provider;
            HANDLER_MAP.put(HttpStatus.UNAUTHORIZED_401.getStatusCode(),
                    AuthorizationHandler.INSTANCE);
            HANDLER_MAP.put(HttpStatus.MOVED_PERMANENTLY_301.getStatusCode(),
                    RedirectHandler.INSTANCE);
            HANDLER_MAP.put(HttpStatus.FOUND_302.getStatusCode(),
                    RedirectHandler.INSTANCE);
            HANDLER_MAP.put(HttpStatus.SEE_OTHER_303.getStatusCode(),
                    RedirectHandler.INSTANCE);
            HANDLER_MAP.put(HttpStatus.TEMPORARY_REDIRECT_307.getStatusCode(),
                    RedirectHandler.INSTANCE);
            HANDLER_MAP.put(HttpStatus.PERMANENT_REDIRECT_308.getStatusCode(),
                    RedirectHandler.INSTANCE);

        }


        // --------------------------------------- Methods from HttpClientFilter


        @Override
        public NextAction handleEvent(final FilterChainContext ctx,
                final FilterChainEvent event) throws IOException {
            if (event.type() == GracefulCloseEvent.class) {
                // Connection was closed.
                // This event is fired only for responses, which don't have
                // associated transfer-encoding or content-length.
                // We have to complete such a request-response processing gracefully.
                final GracefulCloseEvent closeEvent = (GracefulCloseEvent) event;
                final HttpResponsePacket response = closeEvent.getHttpTxContext()
                        .responseStatus.getResponse();
                response.getProcessingState().getHttpContext().attach(ctx);
                onHttpPacketParsed(response, ctx);

                return ctx.getStopAction();
            }

            return ctx.getInvokeAction();
        }
        
        @Override
        public void exceptionOccurred(FilterChainContext ctx, Throwable error) {

            HttpTransactionContext.get(ctx.getConnection()).abort(error);

        }


        @Override
        protected void onHttpContentParsed(HttpContent content,
                                           FilterChainContext ctx) {

            final HttpTransactionContext context =
                    HttpTransactionContext.get(ctx.getConnection());
            final AsyncHandler handler = context.handler;
            if (handler != null && context.currentState != AsyncHandler.STATE.ABORT) {
                try {
                    context.currentState = handler.onBodyPartReceived(
                            new GrizzlyResponseBodyPart(content,
                                    ctx.getConnection()));
                } catch (Exception e) {
                    handler.onThrowable(e);
                }
            }

        }

        @Override
        protected void onHttpHeadersEncoded(HttpHeader httpHeader, FilterChainContext ctx) {
            final HttpTransactionContext context =
                    HttpTransactionContext.get(ctx.getConnection());
            final AsyncHandler handler = context.handler;
            if (handler instanceof TransferCompletionHandler) {
                ((TransferCompletionHandler) handler).onHeaderWriteCompleted();
            }
            if (handler instanceof AsyncHandlerExtensions) {
                ((AsyncHandlerExtensions) handler).onRequestSent();
            }
        }

        @Override
        protected void onHttpContentEncoded(HttpContent content, FilterChainContext ctx) {
            final HttpTransactionContext context =
                    HttpTransactionContext.get(ctx.getConnection());
            final AsyncHandler handler = context.handler;
            if (handler instanceof TransferCompletionHandler) {
                final int written = content.getContent().remaining();
                final long total = context.totalBodyWritten.addAndGet(written);
                ((TransferCompletionHandler) handler).onContentWriteProgress(
                        written,
                        total,
                        content.getHttpHeader().getContentLength());
            }
        }

        @Override
        protected void onInitialLineParsed(HttpHeader httpHeader,
                                           FilterChainContext ctx) {

            super.onInitialLineParsed(httpHeader, ctx);
            if (httpHeader.isSkipRemainder()) {
                return;
            }
            final HttpTransactionContext context =
                    HttpTransactionContext.get(ctx.getConnection());
            final int status = ((HttpResponsePacket) httpHeader).getStatus();
            if (context.establishingTunnel && HttpStatus.OK_200.statusMatches(status)) {
                return;
            }
            if (HttpStatus.CONINTUE_100.statusMatches(status)) {
                ctx.notifyUpstream(new ContinueEvent(context));
                return;
            }


            if (context.statusHandler != null && !context.statusHandler.handlesStatus(status)) {
                context.statusHandler = null;
                context.invocationStatus = StatusHandler.InvocationStatus.CONTINUE;
            } else {
                context.statusHandler = null;
            }
            if (context.invocationStatus == StatusHandler.InvocationStatus.CONTINUE) {
                if (HANDLER_MAP.containsKey(status)) {
                    context.statusHandler = HANDLER_MAP.get(status);
                }
                if (context.statusHandler instanceof RedirectHandler) {
                    if (!isRedirectAllowed(context)) {
                        context.statusHandler = null;
                    }
                }
            }
            if (isRedirectAllowed(context)) {
                if (isRedirect(status)) {
                    if (context.statusHandler == null) {
                        context.statusHandler = RedirectHandler.INSTANCE;
                    }
                    context.redirectCount.incrementAndGet();
                    if (redirectCountExceeded(context)) {
                        httpHeader.setSkipRemainder(true);
                        context.abort(new MaxRedirectException());
                    }
                } else {
                    if (context.redirectCount.get() > 0) {
                        context.redirectCount.set(0);
                    }
                }
            }
            final GrizzlyResponseStatus responseStatus =
                    new GrizzlyResponseStatus((HttpResponsePacket) httpHeader,
                            context.request.getURI(),
                            provider.clientConfig);
            context.responseStatus = responseStatus;
            if (context.statusHandler != null) {
                return;
            }
            if (context.currentState != AsyncHandler.STATE.ABORT) {

                try {
                    final AsyncHandler handler = context.handler;
                    if (handler != null) {
                        context.currentState = handler.onStatusReceived(responseStatus);
                        if (context.isWSRequest && context.currentState == AsyncHandler.STATE.ABORT) {
                            httpHeader.setSkipRemainder(true);
                            try {
                                context.result(handler.onCompleted());
                                context.done();
                            } catch (Throwable e) {
                                context.abort(e);
                            }
                        }
                    }
                } catch (Exception e) {
                    httpHeader.setSkipRemainder(true);
                    context.abort(e);
                }
            }

        }

        @Override
        protected void onHttpHeaderError(final HttpHeader httpHeader,
                                         final FilterChainContext ctx,
                                         final Throwable t) throws IOException {

            httpHeader.setSkipRemainder(true);
            HttpTransactionContext.get(ctx.getConnection()).abort(t);
        }

        @Override
        protected void onHttpContentError(final HttpHeader httpHeader,
                                         final FilterChainContext ctx,
                                         final Throwable t) throws IOException {

            httpHeader.setSkipRemainder(true);
            HttpTransactionContext.get(ctx.getConnection()).abort(t);
        }
        
        @SuppressWarnings({"unchecked"})
        @Override
        protected void onHttpHeadersParsed(HttpHeader httpHeader,
                                           FilterChainContext ctx) {

            super.onHttpHeadersParsed(httpHeader, ctx);
            LOGGER.debug("RESPONSE: {}", httpHeader);
            final HttpTransactionContext context =
                    HttpTransactionContext.get(ctx.getConnection());
            
            if (httpHeader.containsHeader(Header.Connection)) {
                if ("close".equals(httpHeader.getHeader(Header.Connection))) {
                    ConnectionManager.markConnectionAsDoNotCache(ctx.getConnection());
                }
            }
            if (httpHeader.isSkipRemainder() || context.establishingTunnel) {
                return;
            }

            final AsyncHandler handler = context.handler;
            final List filters = context.provider.clientConfig.getResponseFilters();
            final GrizzlyResponseHeaders responseHeaders = new GrizzlyResponseHeaders((HttpResponsePacket) httpHeader);
            if (!filters.isEmpty()) {
                FilterContext fc = new FilterContext.FilterContextBuilder()
                        .asyncHandler(handler).request(context.request)
                        .responseHeaders(responseHeaders)
                        .responseStatus(context.responseStatus).build();
                try {
                    for (final ResponseFilter f : filters) {
                        fc = f.filter(fc);
                    }
                } catch (Exception e) {
                    context.abort(e);
                }
                if (fc.replayRequest()) {
                    httpHeader.setSkipRemainder(true);
                    final Request newRequest = fc.getRequest();
                    final AsyncHandler newHandler = fc.getAsyncHandler();
                    try {
                        final ConnectionManager m =
                                context.provider.connectionManager;
                        final Connection c =
                                m.obtainConnection(newRequest,
                                        context.future);
                        final HttpTransactionContext newContext =
                                context.copy();
                        context.future = null;
                        HttpTransactionContext.set(c, newContext);
                        try {
                            context.provider.execute(c,
                                    newRequest,
                                    newHandler,
                                    context.future,
                                    false);
                        } catch (IOException ioe) {
                            newContext.abort(ioe);
                        }
                    } catch (Exception e) {
                        context.abort(e);
                    }
                    return;
                }
            }
            if (context.statusHandler != null && context.invocationStatus == StatusHandler.InvocationStatus.CONTINUE) {
                final boolean result = context.statusHandler.handleStatus(((HttpResponsePacket) httpHeader),
                        context,
                        ctx);
                if (!result) {
                    httpHeader.setSkipRemainder(true);
                    return;
                }
            }
            if (context.isWSRequest) {
                try {
                    context.protocolHandler.setConnection(ctx.getConnection());
                    final GrizzlyWebSocketAdapter webSocketAdapter = createWebSocketAdapter(context);
                    context.webSocket = webSocketAdapter;
                    SimpleWebSocket ws = webSocketAdapter.gWebSocket;
                    if (context.currentState == AsyncHandler.STATE.UPGRADE) {
                        httpHeader.setChunked(false);
                        ws.onConnect();
                        WebSocketHolder.set(ctx.getConnection(),
                                            context.protocolHandler,
                                            ws);
                        ((WebSocketUpgradeHandler) context.handler).onSuccess(context.webSocket);
                        final int wsTimeout = context.provider.clientConfig.getWebSocketTimeout();
                        IdleTimeoutFilter.setCustomTimeout(ctx.getConnection(),
                                ((wsTimeout <= 0)
                                        ? IdleTimeoutFilter.FOREVER
                                        : wsTimeout),
                                TimeUnit.MILLISECONDS);
                        context.result(handler.onCompleted());
                    } else {
                        httpHeader.setSkipRemainder(true);
                        ((WebSocketUpgradeHandler) context.handler).
                                onClose(context.webSocket,
                                        1002,
                                        "WebSocket protocol error: unexpected HTTP response status during handshake.");
                        context.result(null);
                    }
                } catch (Throwable e) {
                    httpHeader.setSkipRemainder(true);
                    context.abort(e);
                }
            } else {
                if (context.currentState != AsyncHandler.STATE.ABORT) {
                    try {
                        context.currentState = handler.onHeadersReceived(
                                responseHeaders);
                    } catch (Exception e) {
                        httpHeader.setSkipRemainder(true);
                        context.abort(e);
                    }
                }
            }

        }

        @Override
        protected boolean onHttpHeaderParsed(final HttpHeader httpHeader,
                final Buffer buffer, final FilterChainContext ctx) {
            super.onHttpHeaderParsed(httpHeader, buffer, ctx);
            
            final HttpRequestPacket request = ((HttpResponsePacket) httpHeader).getRequest();
            if (Method.CONNECT.equals(request.getMethod())) {
                // finish request/response processing, because Grizzly itself
                // treats CONNECT traffic as part of request-response processing
                // and we don't want it be treated like that
                httpHeader.setExpectContent(false);
            }
            
            return false;
        }


        @SuppressWarnings({"unchecked"})
        @Override
        protected boolean onHttpPacketParsed(HttpHeader httpHeader, FilterChainContext ctx) {

            boolean result;
            if (httpHeader.isSkipRemainder()) {
                clearResponse(ctx.getConnection());
                cleanup(ctx, provider);
                return false;
            }

            result = super.onHttpPacketParsed(httpHeader, ctx);

            final HttpTransactionContext context =
                    HttpTransactionContext.get(ctx.getConnection());
            if (context.establishingTunnel
                    && HttpStatus.OK_200.statusMatches(
                    ((HttpResponsePacket) httpHeader).getStatus())) {
                context.establishingTunnel = false;
                final Connection c = ctx.getConnection();
                context.tunnelEstablished(c);
                try {
                    context.provider.execute(c,
                            context.request,
                            context.handler,
                            context.future,
                            false);
                    return result;
                } catch (IOException e) {
                    context.abort(e);
                    return result;
                }
            } else {
                cleanup(ctx, provider);
                final AsyncHandler handler = context.handler;
                if (handler != null) {
                    try {
                        context.result(handler.onCompleted());
                    } catch (Throwable e) {
                        context.abort(e);
                    }
                } else {
                    context.done();
                }

                return result;
            }
        }


        // ----------------------------------------------------- Private Methods

        private static GrizzlyWebSocketAdapter createWebSocketAdapter(final HttpTransactionContext context) {
            SimpleWebSocket ws = new SimpleWebSocket(context.protocolHandler);
            AsyncHttpProviderConfig config = context.provider.clientConfig.getAsyncHttpProviderConfig();
            boolean bufferFragments = true;
            if (config instanceof GrizzlyAsyncHttpProviderConfig) {
                bufferFragments = (Boolean) ((GrizzlyAsyncHttpProviderConfig) config).getProperty(BUFFER_WEBSOCKET_FRAGMENTS);
            }

            return new GrizzlyWebSocketAdapter(ws, bufferFragments);
        }

        private static boolean isRedirectAllowed(final HttpTransactionContext ctx) {
            return ctx.request.getFollowRedirect() != null? ctx.request.getFollowRedirect().booleanValue() : ctx.redirectsAllowed;
        }

        private static HttpTransactionContext cleanup(final FilterChainContext ctx,
                                                      final GrizzlyAsyncHttpProvider provider) {

            final Connection c = ctx.getConnection();
            final HttpTransactionContext context = HttpTransactionContext.remove(c);
            if (!context.provider.connectionManager.canReturnConnection(c)) {
                context.abort(new IOException("Maximum pooled connections exceeded"));
            } else {
                if (!context.provider.connectionManager.returnConnection(context.request, c)) {
                    ctx.getConnection().close();
                }
            }

            return context;

        }

        private static boolean redirectCountExceeded(final HttpTransactionContext context) {

            return (context.redirectCount.get() > context.maxRedirectCount);

        }


        private static boolean isRedirect(final int status) {

            return HttpStatus.MOVED_PERMANENTLY_301.statusMatches(status)
                    || HttpStatus.FOUND_302.statusMatches(status)
                    || HttpStatus.SEE_OTHER_303.statusMatches(status)
                    || HttpStatus.TEMPORARY_REDIRECT_307.statusMatches(status)
                    || HttpStatus.PERMANENT_REDIRECT_308.statusMatches(status);

        }


        // ------------------------------------------------------- Inner Classes


        private static final class AuthorizationHandler implements StatusHandler {

            private static final AuthorizationHandler INSTANCE =
                    new AuthorizationHandler();

            // -------------------------------------- Methods from StatusHandler


            public boolean handlesStatus(int statusCode) {
                return (HttpStatus.UNAUTHORIZED_401.statusMatches(statusCode));
            }

            @SuppressWarnings({"unchecked"})
            public boolean handleStatus(final HttpResponsePacket responsePacket,
                                     final HttpTransactionContext httpTransactionContext,
                                     final FilterChainContext ctx) {

                final String auth = responsePacket.getHeader(Header.WWWAuthenticate);
                if (auth == null) {
                    throw new IllegalStateException("401 response received, but no WWW-Authenticate header was present");
                }

                Realm realm = httpTransactionContext.request.getRealm();
                if (realm == null) {
                    realm = httpTransactionContext.provider.clientConfig.getRealm();
                }
                if (realm == null) {
                    httpTransactionContext.invocationStatus = InvocationStatus.STOP;
                    if (httpTransactionContext.handler != null) {
                        try {
                            httpTransactionContext.handler.onStatusReceived(httpTransactionContext.responseStatus);
                        } catch (Exception e) {
                            httpTransactionContext.abort(e);
                        }
                    }
                    return true;
                }

                responsePacket.setSkipRemainder(true); // ignore the remainder of the response

                final Request req = httpTransactionContext.request;
                realm = new Realm.RealmBuilder().clone(realm)
                                .setScheme(realm.getAuthScheme())
                                .setUri(httpTransactionContext.request.getURI())
                                .setMethodName(req.getMethod())
                                .setUsePreemptiveAuth(true)
                                .parseWWWAuthenticateHeader(auth)
                                .build();
                String lowerCaseAuth = auth.toLowerCase(Locale.ENGLISH);
                if (lowerCaseAuth.startsWith("basic")) {
                    req.getHeaders().remove(Header.Authorization.toString());
                    try {
                        req.getHeaders().add(Header.Authorization.toString(),
                                             AuthenticatorUtils.computeBasicAuthentication(realm));
                    } catch (UnsupportedEncodingException ignored) {
                    }
                } else if (lowerCaseAuth.startsWith("digest")) {
                    req.getHeaders().remove(Header.Authorization.toString());
                    try {
                        req.getHeaders().add(Header.Authorization.toString(),
                                             AuthenticatorUtils.computeDigestAuthentication(realm));
                    } catch (NoSuchAlgorithmException e) {
                        throw new IllegalStateException("Digest authentication not supported", e);
                    }
                } else {
                    throw new IllegalStateException("Unsupported authorization method: " + auth);
                }

                final ConnectionManager m = httpTransactionContext.provider.connectionManager;
                try {
                    final Connection c = m.obtainConnection(req,
                                                            httpTransactionContext.future);
                    final HttpTransactionContext newContext =
                            httpTransactionContext.copy();
                    httpTransactionContext.future = null;
                    HttpTransactionContext.set(c, newContext);
                    newContext.invocationStatus = InvocationStatus.STOP;
                    try {
                        httpTransactionContext.provider.execute(c,
                                                                req,
                                                                httpTransactionContext.handler,
                                                                httpTransactionContext.future,
                                                                false);
                        return false;
                    } catch (IOException ioe) {
                        newContext.abort(ioe);
                        return false;
                    }
                } catch (Exception e) {
                    httpTransactionContext.abort(e);
                }
                httpTransactionContext.invocationStatus = InvocationStatus.STOP;
                return false;
            }

        } // END AuthorizationHandler


        private static final class RedirectHandler implements StatusHandler {

            private static final RedirectHandler INSTANCE = new RedirectHandler();


            // ------------------------------------------ Methods from StatusHandler


            public boolean handlesStatus(int statusCode) {
                return (isRedirect(statusCode));
            }

            @SuppressWarnings({"unchecked"})
            public boolean handleStatus(final HttpResponsePacket responsePacket,
                                        final HttpTransactionContext httpTransactionContext,
                                        final FilterChainContext ctx) {

                final String redirectURL = responsePacket.getHeader(Header.Location);
                if (redirectURL == null) {
                    throw new IllegalStateException("redirect received, but no location header was present");
                }

                UriComponents orig;
                if (httpTransactionContext.lastRedirectURI == null) {
                    orig = httpTransactionContext.request.getURI();
                } else {
                    orig = UriComponents.create(httpTransactionContext.request.getURI(),
                                                httpTransactionContext.lastRedirectURI);
                }
                httpTransactionContext.lastRedirectURI = redirectURL;
                Request requestToSend;
                UriComponents uri = UriComponents.create(orig, redirectURL);
                if (!uri.toUrl().equalsIgnoreCase(orig.toUrl())) {
                    requestToSend = newRequest(uri,
                                               responsePacket,
                                               httpTransactionContext,
                                               sendAsGet(responsePacket,  httpTransactionContext));
                } else {
                    httpTransactionContext.statusHandler = null;
                    httpTransactionContext.invocationStatus = InvocationStatus.CONTINUE;
                        try {
                            httpTransactionContext.handler.onStatusReceived(httpTransactionContext.responseStatus);
                        } catch (Exception e) {
                            httpTransactionContext.abort(e);
                        }
                    return true;
                }

                final ConnectionManager m = httpTransactionContext.provider.connectionManager;
                try {
                    final Connection c = m.obtainConnection(requestToSend,
                                                            httpTransactionContext.future);
                    if (switchingSchemes(orig, uri)) {
                        try {
                            notifySchemeSwitch(ctx, c, uri);
                        } catch (IOException ioe) {
                            httpTransactionContext.abort(ioe);
                        }
                    }
                    final HttpTransactionContext newContext =
                            httpTransactionContext.copy();
                    httpTransactionContext.future = null;
                    newContext.invocationStatus = InvocationStatus.CONTINUE;
                    newContext.request = requestToSend;
                    newContext.requestUri = requestToSend.getURI();
                    HttpTransactionContext.set(c, newContext);
                    httpTransactionContext.provider.execute(c,
                                                            requestToSend,
                                                            newContext.handler,
                                                            newContext.future,
                                                            false);
                    return false;
                } catch (Exception e) {
                    httpTransactionContext.abort(e);
                }

                httpTransactionContext.invocationStatus = InvocationStatus.CONTINUE;
                return true;

            }


            // ------------------------------------------------- Private Methods

            private boolean sendAsGet(final HttpResponsePacket response,
                                      final HttpTransactionContext ctx) {
                final int statusCode = response.getStatus();
                return !(statusCode < 302 || statusCode > 303)
                          && !(statusCode == 302
                             && ctx.provider.clientConfig.isStrict302Handling());
            }


            private boolean switchingSchemes(final UriComponents oldUri,
                                             final UriComponents newUri) {

                return !oldUri.getScheme().equals(newUri.getScheme());

            }

            private void notifySchemeSwitch(final FilterChainContext ctx,
                                            final Connection c,
                                            final UriComponents uri) throws IOException {

                ctx.notifyDownstream(new SwitchingSSLFilter.SSLSwitchingEvent(
                                               "https".equals(uri.getScheme()), c));
            }

        } // END RedirectHandler


        // ----------------------------------------------------- Private Methods


        private static Request newRequest(final UriComponents uri,
                                          final HttpResponsePacket response,
                                          final HttpTransactionContext ctx,
                                          boolean asGet) {

            final RequestBuilder builder = new RequestBuilder(ctx.request);
            if (asGet) {
                builder.setMethod("GET");
            }
            builder.setUrl(uri.toString());

            if (ctx.provider.clientConfig.isRemoveQueryParamOnRedirect()) {
                builder.resetQuery();
            } else {
                builder.addQueryParams(ctx.request.getQueryParams());
            }
            for (String cookieStr : response.getHeaders().values(Header.Cookie)) {
                builder.addOrReplaceCookie(CookieDecoder.decode(cookieStr));
            }
            return builder.build();

        }


    } // END AsyncHttpClientEventFilter


    private static final class ClientEncodingFilter implements EncodingFilter {


        // ----------------------------------------- Methods from EncodingFilter


        public boolean applyEncoding(HttpHeader httpPacket) {

           httpPacket.addHeader(Header.AcceptEncoding, "gzip");
           return false;

        }


        public boolean applyDecoding(HttpHeader httpPacket) {

            final HttpResponsePacket httpResponse = (HttpResponsePacket) httpPacket;
            final DataChunk bc = httpResponse.getHeaders().getValue(Header.ContentEncoding);
            return bc != null && bc.indexOf("gzip", 0) != -1;

        }


    } // END ClientContentEncoding


    private static final class NonCachingPool implements ConnectionPool {


        // ---------------------------------------- Methods from ConnectionsPool


        public boolean offer(String uri, Connection connection) {
            return false;
        }

        public Connection poll(String uri) {
            return null;
        }

        public boolean removeAll(Connection connection) {
            return false;
        }

        public boolean canCacheConnection() {
            return true;
        }

        public void destroy() {
            // no-op
        }

    } // END NonCachingPool

    public static abstract class BodyHandler {

        public static int MAX_CHUNK_SIZE = 8192;

        public abstract boolean handlesBodyType(final Request request);

        public abstract boolean doHandle(final FilterChainContext ctx,
                final Request request, final HttpRequestPacket requestPacket)
                throws IOException;

        /**
         * Tries to predict request content-length based on the {@link Request}.
         * Not all the BodyHandlers can predict the content-length in
         * advance.
         *
         * @param request
         * @return the content-length, or -1 if the content-length
         * can't be predicted
         */
        protected long getContentLength(final Request request) {
            return request.getContentLength();
        }
    } // END BodyHandler


    private final class BodyHandlerFactory {

        private final BodyHandler[] HANDLERS = new BodyHandler[] {
            new StringBodyHandler(),
            new ByteArrayBodyHandler(),
            new ParamsBodyHandler(),
            new StreamDataBodyHandler(),
            new PartsBodyHandler(),
            new FileBodyHandler(),
            new BodyGeneratorBodyHandler()
        };

        public BodyHandler getBodyHandler(final Request request) {
            for (final BodyHandler h : HANDLERS) {
                if (h.handlesBodyType(request)) {
                    return h;
                }
            }
            
            return null;
        }

    } // END BodyHandlerFactory


    private static final class ExpectHandler extends BodyHandler {

        private final BodyHandler delegate;
        private Request request;
        private HttpRequestPacket requestPacket;

        // -------------------------------------------------------- Constructors


        private ExpectHandler(final BodyHandler delegate) {

            this.delegate = delegate;

        }


        // -------------------------------------------- Methods from BodyHandler


        public boolean handlesBodyType(Request request) {
            return delegate.handlesBodyType(request);
        }

        @SuppressWarnings({"unchecked"})
        public boolean doHandle(FilterChainContext ctx, Request request, HttpRequestPacket requestPacket) throws IOException {
            this.request = request;
            this.requestPacket = requestPacket;
            
            // Set content-length if possible
            final long contentLength = delegate.getContentLength(request);
            if (contentLength != -1) {
                requestPacket.setContentLengthLong(contentLength);
            }
            
            ctx.write(requestPacket, ((!requestPacket.isCommitted()) ? ctx.getTransportContext().getCompletionHandler() : null));
            return true;
        }

        public void finish(final FilterChainContext ctx) throws IOException {
            delegate.doHandle(ctx, request, requestPacket);
        }

    } // END ContinueHandler


    private final class ByteArrayBodyHandler extends BodyHandler {


        // -------------------------------------------- Methods from BodyHandler

        public boolean handlesBodyType(final Request request) {
            return (request.getByteData() != null);
        }

        @SuppressWarnings({"unchecked"})
        public boolean doHandle(final FilterChainContext ctx,
                             final Request request,
                             final HttpRequestPacket requestPacket)
        throws IOException {

            final MemoryManager mm = ctx.getMemoryManager();
            final byte[] data = request.getByteData();
            final Buffer gBuffer = Buffers.wrap(mm, data);
            if (requestPacket.getContentLength() == -1) {
                    if (!clientConfig.isCompressionEnabled()) {
                        requestPacket.setContentLengthLong(data.length);
                    }
                }
            final HttpContent content = requestPacket.httpContentBuilder().content(gBuffer).build();
            content.setLast(true);
            ctx.write(content, ((!requestPacket.isCommitted()) ? ctx.getTransportContext().getCompletionHandler() : null));
            return true;
        }
        
        @Override
        protected long getContentLength(final Request request) {
            if (request.getContentLength() >= 0) {
                return request.getContentLength();
            }

            return clientConfig.isCompressionEnabled()
                    ? -1
                    : request.getByteData().length;
        }        
    }


    private final class StringBodyHandler extends BodyHandler {


        // -------------------------------------------- Methods from BodyHandler


        public boolean handlesBodyType(final Request request) {
            return (request.getStringData() != null);
        }

        @SuppressWarnings({"unchecked"})
        public boolean doHandle(final FilterChainContext ctx,
                             final Request request,
                             final HttpRequestPacket requestPacket)
        throws IOException {

            String charset = request.getBodyEncoding();
            if (charset == null) {
                charset = Charsets.ASCII_CHARSET.name();
            }
            final byte[] data = request.getStringData().getBytes(charset);
            final MemoryManager mm = ctx.getMemoryManager();
            final Buffer gBuffer = Buffers.wrap(mm, data);
            if (requestPacket.getContentLength() == -1) {
                if (!clientConfig.isCompressionEnabled()) {
                    requestPacket.setContentLengthLong(data.length);
                }
            }
            final HttpContent content = requestPacket.httpContentBuilder().content(gBuffer).build();
            content.setLast(true);
            ctx.write(content, ((!requestPacket.isCommitted()) ? ctx.getTransportContext().getCompletionHandler() : null));
            return true;
        }

    } // END StringBodyHandler


    private final class ParamsBodyHandler extends BodyHandler {


        // -------------------------------------------- Methods from BodyHandler


        public boolean handlesBodyType(final Request request) {
            return isNonEmpty(request.getFormParams());
        }

        @SuppressWarnings({"unchecked"})
        public boolean doHandle(final FilterChainContext ctx,
                             final Request request,
                             final HttpRequestPacket requestPacket)
        throws IOException {

            if (requestPacket.getContentType() == null) {
                requestPacket.setContentType("application/x-www-form-urlencoded");
            }
            String charset = request.getBodyEncoding();
            if (charset == null) {
                charset = Charsets.ASCII_CHARSET.name();
            }
            
            if (isNonEmpty(request.getFormParams())) {
                StringBuilder sb = new StringBuilder(128);
                for (Param param : request.getFormParams()) {
                    String name = URLEncoder.encode(param.getName(), charset);
                    String value = URLEncoder.encode(param.getValue(), charset);
                    sb.append(name).append('=').append(value).append('&');
                }
                sb.setLength(sb.length() - 1);
                final byte[] data = sb.toString().getBytes(charset);
                final MemoryManager mm = ctx.getMemoryManager();
                final Buffer gBuffer = Buffers.wrap(mm, data);
                final HttpContent content = requestPacket.httpContentBuilder().content(gBuffer).build();
                if (requestPacket.getContentLength() == -1 && !clientConfig.isCompressionEnabled()) {
                    requestPacket.setContentLengthLong(data.length);
                }
                content.setLast(true);
                ctx.write(content, ((!requestPacket.isCommitted()) ? ctx.getTransportContext().getCompletionHandler() : null));
            }
            
            return true;
        }

    } // END ParamsBodyHandler

    private static final class StreamDataBodyHandler extends BodyHandler {

        // -------------------------------------------- Methods from BodyHandler


        public boolean handlesBodyType(final Request request) {
            return (request.getStreamData() != null);
        }

        @SuppressWarnings({"unchecked"})
        public boolean doHandle(final FilterChainContext ctx,
                             final Request request,
                             final HttpRequestPacket requestPacket)
        throws IOException {

            final MemoryManager mm = ctx.getMemoryManager();
            Buffer buffer = mm.allocate(512);
            final byte[] b = new byte[512];
            int read;
            final InputStream in = request.getStreamData();
            try {
                in.reset();
            } catch (IOException ioe) {
                if (LOGGER.isDebugEnabled()) {
                    LOGGER.debug(ioe.toString(), ioe);
                }
            }
            if (in.markSupported()) {
                in.mark(0);
            }

            while ((read = in.read(b)) != -1) {
                if (read > buffer.remaining()) {
                    buffer = mm.reallocate(buffer, buffer.capacity() + 512);
                }
                buffer.put(b, 0, read);
            }
            buffer.trim();
            if (buffer.hasRemaining()) {
                final HttpContent content = requestPacket.httpContentBuilder().content(buffer).build();
                buffer.allowBufferDispose(false);
                content.setLast(true);
                ctx.write(content, ((!requestPacket.isCommitted()) ? ctx.getTransportContext().getCompletionHandler() : null));
            }
            
            return true;
        }

    } // END StreamDataBodyHandler


    private static final class PartsBodyHandler extends BodyHandler {

        // -------------------------------------------- Methods from BodyHandler


        public boolean handlesBodyType(final Request request) {
            return isNonEmpty(request.getParts());
        }

        public boolean doHandle(final FilterChainContext ctx,
                                final Request request,
                                final HttpRequestPacket requestPacket)
                throws IOException {

            final List parts = request.getParts();
            final MultipartBody multipartBody = MultipartUtils.newMultipartBody(parts, request.getHeaders());
            final long contentLength = multipartBody.getContentLength();
            final String contentType = multipartBody.getContentType();
            requestPacket.setContentLengthLong(contentLength);
            requestPacket.setContentType(contentType);
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("REQUEST(modified): contentLength={}, contentType={}", new Object[]{requestPacket.getContentLength(), requestPacket.getContentType()});
            }

            final FeedableBodyGenerator generator = new FeedableBodyGenerator() {
                @Override
                public Body createBody() throws IOException {
                    return multipartBody;
                }
            };
            generator.setFeeder(new FeedableBodyGenerator.BaseFeeder(generator) {
                @Override
                public void flush() throws IOException {
                    final Body bodyLocal = feedableBodyGenerator.createBody();
                    try {
                        final MemoryManager mm = ctx.getMemoryManager();
                        boolean last = false;
                        while (!last) {
                            Buffer buffer = mm.allocate(BodyHandler.MAX_CHUNK_SIZE);
                            buffer.allowBufferDispose(true);
                            final long readBytes = bodyLocal.read(buffer.toByteBuffer());
                            if (readBytes > 0) {
                                buffer.position((int) readBytes);
                                buffer.trim();
                            } else {
                                buffer.dispose();
                                if (readBytes < 0) {
                                    last = true;
                                    buffer = Buffers.EMPTY_BUFFER;
                                } else {
                                    throw new IllegalStateException("MultipartBody unexpectedly returned 0 bytes available");
                                }
                            }
                            feed(buffer, last);
                        }
                    } finally {
                        if (bodyLocal != null) {
                            try {
                                bodyLocal.close();
                            } catch (IOException ignore) {
                            }
                        }
                    }
                }
            });
            generator.initializeAsynchronousTransfer(ctx, requestPacket);
            return false;
        }

    } // END PartsBodyHandler


    private final class FileBodyHandler extends BodyHandler {

        // -------------------------------------------- Methods from BodyHandler


        public boolean handlesBodyType(final Request request) {
            return (request.getFile() != null);
        }

        @SuppressWarnings({"unchecked"})
        public boolean doHandle(final FilterChainContext ctx,
                             final Request request,
                             final HttpRequestPacket requestPacket)
        throws IOException {

            final File f = request.getFile();
            requestPacket.setContentLengthLong(f.length());
            final HttpTransactionContext context = HttpTransactionContext.get(ctx.getConnection());
            if (clientConfig.isCompressionEnabled() || !SEND_FILE_SUPPORT ||
                    requestPacket.isSecure()) {
                
                final FileInputStream fis = new FileInputStream(request.getFile());
                final MemoryManager mm = ctx.getMemoryManager();
                AtomicInteger written = new AtomicInteger();
                boolean last = false;
                try {
                    for (byte[] buf = new byte[MAX_CHUNK_SIZE]; !last; ) {
                        Buffer b = null;
                        int read;
                        if ((read = fis.read(buf)) < 0) {
                            last = true;
                            b = Buffers.EMPTY_BUFFER;
                        }
                        if (b != Buffers.EMPTY_BUFFER) {
                            written.addAndGet(read);
                            b = Buffers.wrap(mm, buf, 0, read);
                        }

                        final HttpContent content =
                                requestPacket.httpContentBuilder().content(b).
                                        last(last).build();
                        ctx.write(content, ((!requestPacket.isCommitted()) ? ctx.getTransportContext().getCompletionHandler() : null));
                    }
                } finally {
                    try {
                        fis.close();
                    } catch (IOException ignored) {
                    }
                }
            } else {
                // write the headers
                ctx.write(requestPacket, ((!requestPacket.isCommitted()) ? ctx.getTransportContext().getCompletionHandler() : null));
                ctx.write(new FileTransfer(f), new EmptyCompletionHandler() {

                    @Override
                    public void updated(WriteResult result) {
                        final AsyncHandler handler = context.handler;
                        if (handler instanceof TransferCompletionHandler) {
                            final long written = result.getWrittenSize();
                            final long total = context.totalBodyWritten.addAndGet(written);
                            ((TransferCompletionHandler) handler).onContentWriteProgress(
                                    written,
                                    total,
                                    requestPacket.getContentLength());
                        }
                    }
                });
            }

            return true;
        }

        @Override
        protected long getContentLength(final Request request) {
            if (request.getContentLength() >= 0) {
                return request.getContentLength();
            }

            return clientConfig.isCompressionEnabled()
                    ? -1
                    : request.getFile().length();
        }        
    } // END FileBodyHandler


    private static final class BodyGeneratorBodyHandler extends BodyHandler {

        // -------------------------------------------- Methods from BodyHandler


        public boolean handlesBodyType(final Request request) {
            return (request.getBodyGenerator() != null);
        }

        @SuppressWarnings({"unchecked"})
        public boolean doHandle(final FilterChainContext ctx,
                             final Request request,
                             final HttpRequestPacket requestPacket)
        throws IOException {

            final BodyGenerator generator = request.getBodyGenerator();
            final Body bodyLocal = generator.createBody();
            final long len = bodyLocal.getContentLength();
            if (len >= 0) {
                requestPacket.setContentLengthLong(len);
            } else {
                requestPacket.setChunked(true);
            }

            final MemoryManager mm = ctx.getMemoryManager();
            boolean last = false;

            while (!last) {
                Buffer buffer = mm.allocate(MAX_CHUNK_SIZE);
                buffer.allowBufferDispose(true);
                
                final long readBytes = bodyLocal.read(buffer.toByteBuffer());
                if (readBytes > 0) {
                    buffer.position((int) readBytes);
                    buffer.trim();
                } else {
                    buffer.dispose();
                    
                    if (readBytes < 0) {
                        last = true;
                        buffer = Buffers.EMPTY_BUFFER;
                    } else {
                        // pass the context to bodyLocal to be able to
                        // continue body transferring once more data is available
                        if (generator instanceof FeedableBodyGenerator) {
                            ((FeedableBodyGenerator) generator).initializeAsynchronousTransfer(ctx, requestPacket);
                            return false;
                        } else {
                            throw new IllegalStateException("BodyGenerator unexpectedly returned 0 bytes available");
                        }
                    }
                }

                final HttpContent content =
                        requestPacket.httpContentBuilder().content(buffer).
                                last(last).build();
                ctx.write(content, ((!requestPacket.isCommitted()) ? ctx.getTransportContext().getCompletionHandler() : null));
            }
            
            return true;
        }

    } // END BodyGeneratorBodyHandler


    static class ConnectionManager {

        private static final Attribute DO_NOT_CACHE =
            Grizzly.DEFAULT_ATTRIBUTE_BUILDER.createAttribute(ConnectionManager.class.getName());
        private final ConnectionPool pool;
        private final TCPNIOConnectorHandler connectionHandler;
        private final ConnectionMonitor connectionMonitor;
        private final GrizzlyAsyncHttpProvider provider;

        // -------------------------------------------------------- Constructors

        ConnectionManager(final GrizzlyAsyncHttpProvider provider,
                          final TCPNIOTransport transport,
                          final GrizzlyAsyncHttpProviderConfig providerConfig) {

            ConnectionPool connectionPool;
            this.provider = provider;
            final AsyncHttpClientConfig config = provider.clientConfig;
            if (config.isAllowPoolingConnections()) {
                ConnectionPool pool = providerConfig != null ? providerConfig.getConnectionPool() : null;
                if (pool != null) {
                    connectionPool = pool;
                } else {
                    connectionPool = new GrizzlyConnectionPool((config));
                }
            } else {
                connectionPool = new NonCachingPool();
            }
            pool = connectionPool;
            connectionHandler = TCPNIOConnectorHandler.builder(transport).build();
            final int maxConns = provider.clientConfig.getMaxConnections();
            connectionMonitor = new ConnectionMonitor(maxConns);


        }

        // ----------------------------------------------------- Private Methods

        static void markConnectionAsDoNotCache(final Connection c) {
            DO_NOT_CACHE.set(c, Boolean.TRUE);
        }

        static boolean isConnectionCacheable(final Connection c) {
            final Boolean canCache =  DO_NOT_CACHE.get(c);
            return ((canCache != null) ? canCache : false);
        }

        void doAsyncTrackedConnection(final Request request,
                                      final GrizzlyResponseFuture requestFuture,
                                      final CompletionHandler connectHandler)
        throws IOException, ExecutionException, InterruptedException {
            Connection c = pool.poll(getPoolKey(request, requestFuture.getProxy()));
            if (c == null) {
                if (!connectionMonitor.acquire()) {
                    throw new IOException("Max connections exceeded");
                }
                doAsyncConnect(request, requestFuture, connectHandler);
            } else {
                provider.touchConnection(c, request);
                connectHandler.completed(c);
            }

        }

        Connection obtainConnection(final Request request,
                                    final GrizzlyResponseFuture requestFuture)
        throws IOException, ExecutionException, InterruptedException, TimeoutException {

            final Connection c = obtainConnection0(request, requestFuture);
            DO_NOT_CACHE.set(c, Boolean.TRUE);
            return c;

        }

        void doAsyncConnect(final Request request,
                            final GrizzlyResponseFuture requestFuture,
                            final CompletionHandler connectHandler)
        throws IOException, ExecutionException, InterruptedException {

            ProxyServer proxy = requestFuture.getProxy();
            final UriComponents uri = request.getURI();
            String host = ((proxy != null) ? proxy.getHost() : uri.getHost());
            int port = ((proxy != null) ? proxy.getPort() : uri.getPort());
            
            CompletionHandler completionHandler =
                    createConnectionCompletionHandler(request, requestFuture,
                            connectHandler);
            
            final HostnameVerifier verifier =
                    provider.clientConfig.getHostnameVerifier();
            
            if (Utils.isSecure(uri) && verifier != null) {
                completionHandler =
                        HostnameVerifierListener.wrapWithHostnameVerifierHandler(
                                completionHandler, verifier, uri.getHost());
            }

            if (request.getLocalAddress() != null) {
                connectionHandler.connect(new InetSocketAddress(host,
                        getPort(uri, port)),
                        new InetSocketAddress(request.getLocalAddress(), 0),
                        completionHandler);
            } else {
                connectionHandler.connect(new InetSocketAddress(host,
                        getPort(uri, port)),
                        completionHandler);
            }

        }

        private Connection obtainConnection0(final Request request,
                                             final GrizzlyResponseFuture requestFuture)
        throws IOException, ExecutionException, InterruptedException, TimeoutException {

            final UriComponents uri = request.getURI();
            final ProxyServer proxy = requestFuture.getProxy();
            String host = (proxy != null) ? proxy.getHost() : uri.getHost();
            int port = (proxy != null) ? proxy.getPort() : uri.getPort();
            int cTimeout = provider.clientConfig.getConnectionTimeout();
            FutureImpl future = Futures.createSafeFuture();
            CompletionHandler ch = Futures.toCompletionHandler(future,
                    createConnectionCompletionHandler(request, requestFuture, null));
            if (cTimeout > 0) {
                connectionHandler.connect(new InetSocketAddress(host, getPort(uri, port)),
                        ch);
                return future.get(cTimeout, TimeUnit.MILLISECONDS);
            } else {
                connectionHandler.connect(new InetSocketAddress(host, getPort(uri, port)),
                        ch);
                return future.get();
            }
        }

        boolean returnConnection(final Request request, final Connection c) {
            ProxyServer proxyServer = ProxyUtils.getProxyServer(provider.clientConfig, request);
            final boolean result = (DO_NOT_CACHE.get(c) == null
                                       && pool.offer(getPoolKey(request, proxyServer), c));
            if (result) {
                if (provider.resolver != null) {
                    provider.resolver.setTimeoutMillis(c, IdleTimeoutFilter.FOREVER);
                }
            }
            return result;
        }


        boolean canReturnConnection(final Connection c) {

            return (DO_NOT_CACHE.get(c) != null || pool.canCacheConnection());

        }


        void destroy() {

            pool.destroy();

        }
        
        CompletionHandler createConnectionCompletionHandler(final Request request,
                                                                        final GrizzlyResponseFuture future,
                                                                        final CompletionHandler wrappedHandler) {
            return new CompletionHandler() {
                public void cancelled() {
                    if (wrappedHandler != null) {
                        wrappedHandler.cancelled();
                    } else {
                        future.cancel(true);
                    }
                }

                public void failed(Throwable throwable) {
                    if (wrappedHandler != null) {
                        wrappedHandler.failed(throwable);
                    } else {
                        future.abort(throwable);
                    }
                }

                public void completed(Connection connection) {
                    future.setConnection(connection);
                    provider.touchConnection(connection, request);
                    if (wrappedHandler != null) {
                        connection.addCloseListener(connectionMonitor);
                        wrappedHandler.completed(connection);
                    }
                }

                public void updated(Connection result) {
                    if (wrappedHandler != null) {
                        wrappedHandler.updated(result);
                    }
                }
            };
        }

        private static String getPoolKey(Request request, ProxyServer proxyServer) {
            return request.getConnectionPoolKeyStrategy().getKey(request.getURI(), proxyServer);
        }

        // ------------------------------------------------------ Nested Classes

        private static class ConnectionMonitor implements Connection.CloseListener {

        private final Semaphore connections;

            // ------------------------------------------------------------ Constructors


            ConnectionMonitor(final int maxConnections) {
                if (maxConnections != -1) {
                    connections = new Semaphore(maxConnections);
                } else {
                    connections = null;
                }
            }

            // ----------------------------------- Methods from Connection.CloseListener


            public boolean acquire() {

                return (connections == null || connections.tryAcquire());

            }

            @Override
            public void onClosed(Connection connection, Connection.CloseType closeType) throws IOException {

                if (connections != null) {
                    connections.release();
                }

            }

        } // END ConnectionMonitor

    } // END ConnectionManager

    static final class SwitchingSSLFilter extends SSLFilter {

        private final boolean secureByDefault;
        final Attribute CONNECTION_IS_SECURE =
            Grizzly.DEFAULT_ATTRIBUTE_BUILDER.createAttribute(SwitchingSSLFilter.class.getName());

        // -------------------------------------------------------- Constructors


        SwitchingSSLFilter(final SSLEngineConfigurator clientConfig,
                           final boolean secureByDefault) {

            super(null, clientConfig);
            this.secureByDefault = secureByDefault;

        }


        // ---------------------------------------------- Methods from SSLFilter


        @Override
        public NextAction handleEvent(FilterChainContext ctx, FilterChainEvent event) throws IOException {

            if (event.type() == SSLSwitchingEvent.class) {
                final SSLSwitchingEvent se = (SSLSwitchingEvent) event;
                CONNECTION_IS_SECURE.set(se.connection, se.secure);
                return ctx.getStopAction();
            }
            return ctx.getInvokeAction();

        }

        @Override
        public NextAction handleRead(FilterChainContext ctx) throws IOException {

            if (isSecure(ctx.getConnection())) {
                return super.handleRead(ctx);
            }
            return ctx.getInvokeAction();

        }

        @Override
        public NextAction handleWrite(FilterChainContext ctx) throws IOException {

            if (isSecure(ctx.getConnection())) {
                return super.handleWrite(ctx);
            }
            return ctx.getInvokeAction();

        }

        @Override
        public void onFilterChainChanged(FilterChain filterChain) {
            // no-op
        }


        // ----------------------------------------------------- Private Methods


        private boolean isSecure(final Connection c) {

            Boolean secStatus = CONNECTION_IS_SECURE.get(c);
            if (secStatus == null) {
                secStatus = secureByDefault;
            }
            return secStatus;

        }


        // ------------------------------------------------------ Nested Classes

        static final class SSLSwitchingEvent implements FilterChainEvent {

            final boolean secure;
            final Connection connection;

            // ---------------------------------------------------- Constructors


            SSLSwitchingEvent(final boolean secure, final Connection c) {

                this.secure = secure;
                connection = c;

            }

            // ----------------------------------- Methods from FilterChainEvent


            @Override
            public Object type() {
                return SSLSwitchingEvent.class;
            }

        } // END SSLSwitchingEvent

    } // END SwitchingSSLFilter

    private static final class GrizzlyWebSocketAdapter implements WebSocket {
        
        final SimpleWebSocket gWebSocket;
        final boolean bufferFragments;

        // -------------------------------------------------------- Constructors
        
        
        GrizzlyWebSocketAdapter(final SimpleWebSocket gWebSocket,
                                final boolean bufferFragments) {
            this.gWebSocket = gWebSocket;
            this.bufferFragments = bufferFragments;
        }
        
        
        // ------------------------------------------ Methods from AHC WebSocket
        
        
        @Override
        public WebSocket sendMessage(byte[] message) {
            gWebSocket.send(message);
            return this;
        }

        @Override
        public WebSocket stream(byte[] fragment, boolean last) {
            if (isNonEmpty(fragment)) {
                gWebSocket.stream(last, fragment, 0, fragment.length);
            }
            return this;
        }

        @Override
        public WebSocket stream(byte[] fragment, int offset, int len, boolean last) {
        	if (isNonEmpty(fragment)) {
                gWebSocket.stream(last, fragment, offset, len);
            }
            return this;
        }

        @Override
        public WebSocket sendTextMessage(String message) {
            gWebSocket.send(message);
            return this;
        }

        @Override
        public WebSocket streamText(String fragment, boolean last) {
            gWebSocket.stream(last, fragment);
            return this;
        }

        @Override
        public WebSocket sendPing(byte[] payload) {
            gWebSocket.sendPing(payload);
            return this;
        }

        @Override
        public WebSocket sendPong(byte[] payload) {
            gWebSocket.sendPong(payload);
            return this;
        }

        @Override
        public WebSocket addWebSocketListener(WebSocketListener l) {
            gWebSocket.add(new AHCWebSocketListenerAdapter(l, this));
            return this;
        }

        @Override
        public WebSocket removeWebSocketListener(WebSocketListener l) {
            gWebSocket.remove(new AHCWebSocketListenerAdapter(l, this));
            return this;
        }

        @Override
        public boolean isOpen() {
            return gWebSocket.isConnected();
        }

        @Override
        public void close() {
            gWebSocket.close();
        }
        
    } // END GrizzlyWebSocketAdapter


    private static final class AHCWebSocketListenerAdapter implements org.glassfish.grizzly.websockets.WebSocketListener {

        private final WebSocketListener ahcListener;
        private final GrizzlyWebSocketAdapter webSocket;
        private final StringBuilder stringBuffer;
        private final ByteArrayOutputStream byteArrayOutputStream;


        // -------------------------------------------------------- Constructors


        AHCWebSocketListenerAdapter(final WebSocketListener ahcListener,
                                    final GrizzlyWebSocketAdapter webSocket) {
            this.ahcListener = ahcListener;
            this.webSocket = webSocket;
            if (webSocket.bufferFragments) {
                stringBuffer = new StringBuilder();
                byteArrayOutputStream = new ByteArrayOutputStream();
            } else {
                stringBuffer = null;
                byteArrayOutputStream = null;
            }
        }


        // ------------------------------ Methods from Grizzly WebSocketListener


        @Override
        public void onClose(org.glassfish.grizzly.websockets.WebSocket gWebSocket, DataFrame dataFrame) {
            try {
                if (ahcListener instanceof WebSocketCloseCodeReasonListener) {
                    ClosingFrame cf = ClosingFrame.class.cast(dataFrame);
                    WebSocketCloseCodeReasonListener.class.cast(ahcListener).onClose(webSocket, cf.getCode(), cf.getReason());
                } else {
                    ahcListener.onClose(webSocket);
                }
            } catch (Throwable e) {
                ahcListener.onError(e);
            }
        }

        @Override
        public void onConnect(org.glassfish.grizzly.websockets.WebSocket gWebSocket) {
            try {
                ahcListener.onOpen(webSocket);
            } catch (Throwable e) {
                ahcListener.onError(e);
            }
        }

        @Override
        public void onMessage(org.glassfish.grizzly.websockets.WebSocket webSocket, String s) {
            try {
                if (ahcListener instanceof WebSocketTextListener) {
                    WebSocketTextListener.class.cast(ahcListener).onMessage(s);
                }
            } catch (Throwable e) {
                ahcListener.onError(e);
            }
        }

        @Override
        public void onMessage(org.glassfish.grizzly.websockets.WebSocket webSocket, byte[] bytes) {
            try {
                if (ahcListener instanceof WebSocketByteListener) {
                    WebSocketByteListener.class.cast(ahcListener).onMessage(bytes);
                }
            } catch (Throwable e) {
                ahcListener.onError(e);
            }
        }

        @Override
        public void onPing(org.glassfish.grizzly.websockets.WebSocket webSocket, byte[] bytes) {
            try {
                if (ahcListener instanceof WebSocketPingListener) {
                    WebSocketPingListener.class.cast(ahcListener).onPing(bytes);
                }
            } catch (Throwable e) {
                ahcListener.onError(e);
            }
        }

        @Override
        public void onPong(org.glassfish.grizzly.websockets.WebSocket webSocket, byte[] bytes) {
            try {
                if (ahcListener instanceof WebSocketPongListener) {
                    WebSocketPongListener.class.cast(ahcListener).onPong(bytes);
                }
            } catch (Throwable e) {
                ahcListener.onError(e);
            }
        }

        @Override
        public void onFragment(org.glassfish.grizzly.websockets.WebSocket webSocket, String s, boolean last) {
            try {
                if (this.webSocket.bufferFragments) {
                    synchronized (this.webSocket) {
                        stringBuffer.append(s);
                        if (last) {
                            if (ahcListener instanceof WebSocketTextListener) {
                                final String message = stringBuffer.toString();
                                stringBuffer.setLength(0);
                                WebSocketTextListener.class.cast(ahcListener).onMessage(message);
                            }
                        }
                    }
                }
            } catch (Throwable e) {
                ahcListener.onError(e);
            }
        }

        @Override
        public void onFragment(org.glassfish.grizzly.websockets.WebSocket webSocket, byte[] bytes, boolean last) {
            try {
                if (this.webSocket.bufferFragments) {
                    synchronized (this.webSocket) {
                        byteArrayOutputStream.write(bytes);
                        if (last) {
                            if (ahcListener instanceof WebSocketByteListener) {
                                final byte[] bytesLocal = byteArrayOutputStream.toByteArray();
                                byteArrayOutputStream.reset();
                                WebSocketByteListener.class.cast(ahcListener).onMessage(bytesLocal);
                            }
                        }
                    }
                }
            } catch (Throwable e) {
                ahcListener.onError(e);
            }
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;

            AHCWebSocketListenerAdapter that = (AHCWebSocketListenerAdapter) o;

            if (ahcListener != null ? !ahcListener.equals(that.ahcListener) : that.ahcListener != null)
                return false;
            if (webSocket != null ? !webSocket.equals(that.webSocket) : that.webSocket != null)
                return false;

            return true;
        }

        @Override
        public int hashCode() {
            int result = ahcListener != null ? ahcListener.hashCode() : 0;
            result = 31 * result + (webSocket != null ? webSocket.hashCode() : 0);
            return result;
        }
    } // END AHCWebSocketListenerAdapter


    public static void main(String[] args) {
            SecureRandom secureRandom = new SecureRandom();
            SSLContext sslContext = null;
            try {
                sslContext = SSLContext.getInstance("TLS");
                sslContext.init(null, null, secureRandom);
            } catch (Exception e) {
                e.printStackTrace();
            }
            AsyncHttpClientConfig config = new AsyncHttpClientConfig.Builder()
                    .setConnectionTimeout(5000)
                    .setSSLContext(sslContext).build();
            AsyncHttpClient client = new AsyncHttpClient(new GrizzlyAsyncHttpProvider(config), config);
            try {
                long start = System.currentTimeMillis();
                try {
                    client.executeRequest(client.prepareGet("http://www.google.com").build()).get();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (ExecutionException e) {
                    e.printStackTrace();
                }
                System.out.println("COMPLETE: " + (System.currentTimeMillis() - start) + "ms");
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
}







© 2015 - 2025 Weber Informatics LLC | Privacy Policy