io.vertx.core.http.impl.HttpClientImpl.java Source code

Java tutorial

Introduction

Here is the source code for io.vertx.core.http.impl.HttpClientImpl.java

Source

/*
 * Copyright (c) 2011-2013 The original author or authors
 * ------------------------------------------------------
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * and Apache License v2.0 which accompanies this distribution.
 *
 *     The Eclipse Public License is available at
 *     http://www.eclipse.org/legal/epl-v10.html
 *
 *     The Apache License v2.0 is available at
 *     http://www.opensource.org/licenses/apache2.0.php
 *
 * You may elect to redistribute this code under either of these licenses.
 */

package io.vertx.core.http.impl;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.handler.codec.http.*;
import io.netty.handler.ssl.SslHandler;
import io.netty.handler.timeout.IdleStateHandler;
import io.vertx.core.Future;
import io.vertx.core.Handler;
import io.vertx.core.MultiMap;
import io.vertx.core.VertxException;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.http.*;
import io.vertx.core.http.HttpMethod;
import io.vertx.core.http.impl.ws.WebSocketFrameImpl;
import io.vertx.core.http.impl.ws.WebSocketFrameInternal;
import io.vertx.core.impl.Closeable;
import io.vertx.core.impl.ContextImpl;
import io.vertx.core.impl.VertxInternal;
import io.vertx.core.logging.Logger;
import io.vertx.core.logging.LoggerFactory;
import io.vertx.core.net.impl.KeyStoreHelper;
import io.vertx.core.net.impl.PartialPooledByteBufAllocator;
import io.vertx.core.net.impl.SSLHelper;
import io.vertx.core.spi.metrics.HttpClientMetrics;
import io.vertx.core.spi.metrics.Metrics;
import io.vertx.core.spi.metrics.MetricsProvider;

import javax.net.ssl.SSLHandshakeException;
import java.net.InetSocketAddress;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;

/**
 *
 * This class is thread-safe
 *
 * @author <a href="http://tfox.org">Tim Fox</a>
 */
public class HttpClientImpl implements HttpClient, MetricsProvider {

    private static final Logger log = LoggerFactory.getLogger(HttpClientImpl.class);

    private final VertxInternal vertx;
    private final HttpClientOptions options;
    private final Map<Channel, ClientConnection> connectionMap = new ConcurrentHashMap<>();
    private final ContextImpl creatingContext;
    private final ConnectionManager pool;
    private final Closeable closeHook;
    private final SSLHelper sslHelper;
    private final HttpClientMetrics metrics;
    private volatile boolean closed;

    public HttpClientImpl(VertxInternal vertx, HttpClientOptions options) {
        this.vertx = vertx;
        this.options = new HttpClientOptions(options);
        this.sslHelper = new SSLHelper(options, KeyStoreHelper.create(vertx, options.getKeyCertOptions()),
                KeyStoreHelper.create(vertx, options.getTrustOptions()));
        this.creatingContext = vertx.getContext();
        closeHook = completionHandler -> {
            HttpClientImpl.this.close();
            completionHandler.handle(Future.succeededFuture());
        };
        if (creatingContext != null) {
            if (creatingContext.isMultiThreadedWorkerContext()) {
                throw new IllegalStateException("Cannot use HttpClient in a multi-threaded worker verticle");
            }
            creatingContext.addCloseHook(closeHook);
        }
        pool = new ConnectionManager(options.getMaxPoolSize(), options.isKeepAlive(), options.isPipelining()) {
            protected void connect(String host, int port, Handler<ClientConnection> connectHandler,
                    Handler<Throwable> connectErrorHandler, ContextImpl context,
                    ConnectionLifeCycleListener listener) {
                internalConnect(context, port, host, connectHandler, connectErrorHandler, listener);
            }
        };
        this.metrics = vertx.metricsSPI().createMetrics(this, options);
    }

    @Override
    public HttpClient websocket(int port, String host, String requestURI, Handler<WebSocket> wsConnect) {
        websocketStream(port, host, requestURI, null, null).handler(wsConnect);
        return this;
    }

    @Override
    public HttpClient websocket(String host, String requestURI, Handler<WebSocket> wsConnect) {
        return websocket(options.getDefaultPort(), host, requestURI, wsConnect);
    }

    @Override
    public HttpClient websocket(int port, String host, String requestURI, MultiMap headers,
            Handler<WebSocket> wsConnect) {
        websocketStream(port, host, requestURI, headers, null).handler(wsConnect);
        return this;
    }

    @Override
    public HttpClient websocket(String host, String requestURI, MultiMap headers, Handler<WebSocket> wsConnect) {
        return websocket(options.getDefaultPort(), host, requestURI, headers, wsConnect);
    }

    @Override
    public HttpClient websocket(int port, String host, String requestURI, MultiMap headers,
            WebsocketVersion version, Handler<WebSocket> wsConnect) {
        websocketStream(port, host, requestURI, headers, version, null).handler(wsConnect);
        return this;
    }

    @Override
    public HttpClient websocket(String host, String requestURI, MultiMap headers, WebsocketVersion version,
            Handler<WebSocket> wsConnect) {
        return websocket(options.getDefaultPort(), host, requestURI, headers, version, wsConnect);
    }

    @Override
    public HttpClient websocket(int port, String host, String requestURI, MultiMap headers,
            WebsocketVersion version, String subProtocols, Handler<WebSocket> wsConnect) {
        websocketStream(port, host, requestURI, headers, version, subProtocols).handler(wsConnect);
        return this;
    }

    @Override
    public HttpClient websocket(String host, String requestURI, MultiMap headers, WebsocketVersion version,
            String subProtocols, Handler<WebSocket> wsConnect) {
        return websocket(options.getDefaultPort(), host, requestURI, headers, version, subProtocols, wsConnect);
    }

    @Override
    public HttpClient websocket(String requestURI, Handler<WebSocket> wsConnect) {
        return websocket(options.getDefaultPort(), options.getDefaultHost(), requestURI, wsConnect);
    }

    @Override
    public HttpClient websocket(String requestURI, MultiMap headers, Handler<WebSocket> wsConnect) {
        return websocket(options.getDefaultPort(), options.getDefaultHost(), requestURI, headers, wsConnect);
    }

    @Override
    public HttpClient websocket(String requestURI, MultiMap headers, WebsocketVersion version,
            Handler<WebSocket> wsConnect) {
        return websocket(options.getDefaultPort(), options.getDefaultHost(), requestURI, headers, version,
                wsConnect);
    }

    @Override
    public HttpClient websocket(String requestURI, MultiMap headers, WebsocketVersion version, String subProtocols,
            Handler<WebSocket> wsConnect) {
        return websocket(options.getDefaultPort(), options.getDefaultHost(), requestURI, headers, version,
                subProtocols, wsConnect);
    }

    @Override
    public WebSocketStream websocketStream(int port, String host, String requestURI) {
        return websocketStream(port, host, requestURI, null, null);
    }

    @Override
    public WebSocketStream websocketStream(String host, String requestURI) {
        return websocketStream(options.getDefaultPort(), host, requestURI);
    }

    @Override
    public WebSocketStream websocketStream(int port, String host, String requestURI, MultiMap headers) {
        return websocketStream(port, host, requestURI, headers, null);
    }

    @Override
    public WebSocketStream websocketStream(String host, String requestURI, MultiMap headers) {
        return websocketStream(options.getDefaultPort(), host, requestURI, headers);
    }

    @Override
    public WebSocketStream websocketStream(int port, String host, String requestURI, MultiMap headers,
            WebsocketVersion version) {
        return websocketStream(port, host, requestURI, headers, version, null);
    }

    @Override
    public WebSocketStream websocketStream(String host, String requestURI, MultiMap headers,
            WebsocketVersion version) {
        return websocketStream(options.getDefaultPort(), host, requestURI, headers, version);
    }

    @Override
    public WebSocketStream websocketStream(int port, String host, String requestURI, MultiMap headers,
            WebsocketVersion version, String subProtocols) {
        return new WebSocketStreamImpl(port, host, requestURI, headers, version, subProtocols);
    }

    @Override
    public WebSocketStream websocketStream(String host, String requestURI, MultiMap headers,
            WebsocketVersion version, String subProtocols) {
        return websocketStream(options.getDefaultPort(), host, requestURI, headers, version, subProtocols);
    }

    @Override
    public WebSocketStream websocketStream(String requestURI) {
        return websocketStream(options.getDefaultPort(), options.getDefaultHost(), requestURI);
    }

    @Override
    public WebSocketStream websocketStream(String requestURI, MultiMap headers) {
        return websocketStream(options.getDefaultPort(), options.getDefaultHost(), requestURI, headers);
    }

    @Override
    public WebSocketStream websocketStream(String requestURI, MultiMap headers, WebsocketVersion version) {
        return websocketStream(options.getDefaultPort(), options.getDefaultHost(), requestURI, headers, version);
    }

    @Override
    public WebSocketStream websocketStream(String requestURI, MultiMap headers, WebsocketVersion version,
            String subProtocols) {
        return websocketStream(options.getDefaultPort(), options.getDefaultHost(), requestURI, headers, version,
                subProtocols);
    }

    @Override
    public HttpClientRequest requestAbs(HttpMethod method, String absoluteURI,
            Handler<HttpClientResponse> responseHandler) {
        Objects.requireNonNull(responseHandler, "no null responseHandler accepted");
        return requestAbs(method, absoluteURI).handler(responseHandler);
    }

    @Override
    public HttpClientRequest request(HttpMethod method, int port, String host, String requestURI,
            Handler<HttpClientResponse> responseHandler) {
        Objects.requireNonNull(responseHandler, "no null responseHandler accepted");
        return request(method, port, host, requestURI).handler(responseHandler);
    }

    @Override
    public HttpClientRequest request(HttpMethod method, String host, String requestURI,
            Handler<HttpClientResponse> responseHandler) {
        return request(method, options.getDefaultPort(), host, requestURI, responseHandler);
    }

    @Override
    public HttpClientRequest request(HttpMethod method, String requestURI) {
        return request(method, options.getDefaultPort(), options.getDefaultHost(), requestURI);
    }

    @Override
    public HttpClientRequest request(HttpMethod method, String requestURI,
            Handler<HttpClientResponse> responseHandler) {
        return request(method, options.getDefaultPort(), options.getDefaultHost(), requestURI, responseHandler);
    }

    @Override
    public HttpClientRequest requestAbs(HttpMethod method, String absoluteURI) {
        URL url = parseUrl(absoluteURI);
        int port = url.getPort();
        if (port == -1) {
            String protocol = url.getProtocol();
            char chend = protocol.charAt(protocol.length() - 1);
            if (chend == 'p') {
                port = 80;
            } else if (chend == 's') {
                port = 443;
            }
        }
        return doRequest(method, url.getHost(), port, url.getFile(), null);
    }

    @Override
    public HttpClientRequest request(HttpMethod method, int port, String host, String requestURI) {
        return doRequest(method, host, port, requestURI, null);
    }

    @Override
    public HttpClientRequest request(HttpMethod method, String host, String requestURI) {
        return request(method, options.getDefaultPort(), host, requestURI);
    }

    @Override
    public HttpClientRequest get(int port, String host, String requestURI) {
        return request(HttpMethod.GET, port, host, requestURI);
    }

    @Override
    public HttpClientRequest get(String host, String requestURI) {
        return get(options.getDefaultPort(), host, requestURI);
    }

    @Override
    public HttpClientRequest get(int port, String host, String requestURI,
            Handler<HttpClientResponse> responseHandler) {
        return request(HttpMethod.GET, port, host, requestURI, responseHandler);
    }

    @Override
    public HttpClientRequest get(String host, String requestURI, Handler<HttpClientResponse> responseHandler) {
        return get(options.getDefaultPort(), host, requestURI, responseHandler);
    }

    @Override
    public HttpClientRequest get(String requestURI) {
        return request(HttpMethod.GET, requestURI);
    }

    @Override
    public HttpClientRequest get(String requestURI, Handler<HttpClientResponse> responseHandler) {
        return request(HttpMethod.GET, requestURI, responseHandler);
    }

    @Override
    public HttpClientRequest getAbs(String absoluteURI) {
        return requestAbs(HttpMethod.GET, absoluteURI);
    }

    @Override
    public HttpClientRequest getAbs(String absoluteURI, Handler<HttpClientResponse> responseHandler) {
        return requestAbs(HttpMethod.GET, absoluteURI, responseHandler);
    }

    @Override
    public HttpClient getNow(int port, String host, String requestURI,
            Handler<HttpClientResponse> responseHandler) {
        get(port, host, requestURI, responseHandler).end();
        return this;
    }

    @Override
    public HttpClient getNow(String host, String requestURI, Handler<HttpClientResponse> responseHandler) {
        return getNow(options.getDefaultPort(), host, requestURI, responseHandler);
    }

    @Override
    public HttpClient getNow(String requestURI, Handler<HttpClientResponse> responseHandler) {
        get(requestURI, responseHandler).end();
        return this;
    }

    @Override
    public HttpClientRequest post(int port, String host, String requestURI) {
        return request(HttpMethod.POST, port, host, requestURI);
    }

    @Override
    public HttpClientRequest post(String host, String requestURI) {
        return post(options.getDefaultPort(), host, requestURI);
    }

    @Override
    public HttpClientRequest post(int port, String host, String requestURI,
            Handler<HttpClientResponse> responseHandler) {
        return request(HttpMethod.POST, port, host, requestURI, responseHandler);
    }

    @Override
    public HttpClientRequest post(String host, String requestURI, Handler<HttpClientResponse> responseHandler) {
        return post(options.getDefaultPort(), host, requestURI, responseHandler);
    }

    @Override
    public HttpClientRequest post(String requestURI) {
        return request(HttpMethod.POST, requestURI);
    }

    @Override
    public HttpClientRequest post(String requestURI, Handler<HttpClientResponse> responseHandler) {
        return request(HttpMethod.POST, requestURI, responseHandler);
    }

    @Override
    public HttpClientRequest postAbs(String absoluteURI) {
        return requestAbs(HttpMethod.POST, absoluteURI);
    }

    @Override
    public HttpClientRequest postAbs(String absoluteURI, Handler<HttpClientResponse> responseHandler) {
        return requestAbs(HttpMethod.POST, absoluteURI, responseHandler);
    }

    @Override
    public HttpClientRequest head(int port, String host, String requestURI) {
        return request(HttpMethod.HEAD, port, host, requestURI);
    }

    @Override
    public HttpClientRequest head(String host, String requestURI) {
        return head(options.getDefaultPort(), host, requestURI);
    }

    @Override
    public HttpClientRequest head(int port, String host, String requestURI,
            Handler<HttpClientResponse> responseHandler) {
        return request(HttpMethod.HEAD, port, host, requestURI, responseHandler);
    }

    @Override
    public HttpClientRequest head(String host, String requestURI, Handler<HttpClientResponse> responseHandler) {
        return head(options.getDefaultPort(), host, requestURI, responseHandler);
    }

    @Override
    public HttpClientRequest head(String requestURI) {
        return request(HttpMethod.HEAD, requestURI);
    }

    @Override
    public HttpClientRequest head(String requestURI, Handler<HttpClientResponse> responseHandler) {
        return request(HttpMethod.HEAD, requestURI, responseHandler);
    }

    @Override
    public HttpClientRequest headAbs(String absoluteURI) {
        return requestAbs(HttpMethod.HEAD, absoluteURI);
    }

    @Override
    public HttpClientRequest headAbs(String absoluteURI, Handler<HttpClientResponse> responseHandler) {
        return requestAbs(HttpMethod.HEAD, absoluteURI, responseHandler);
    }

    @Override
    public HttpClient headNow(int port, String host, String requestURI,
            Handler<HttpClientResponse> responseHandler) {
        head(port, host, requestURI, responseHandler).end();
        return this;
    }

    @Override
    public HttpClient headNow(String host, String requestURI, Handler<HttpClientResponse> responseHandler) {
        return headNow(options.getDefaultPort(), host, requestURI, responseHandler);
    }

    @Override
    public HttpClient headNow(String requestURI, Handler<HttpClientResponse> responseHandler) {
        head(requestURI, responseHandler).end();
        return this;
    }

    @Override
    public HttpClientRequest options(int port, String host, String requestURI) {
        return request(HttpMethod.OPTIONS, port, host, requestURI);
    }

    @Override
    public HttpClientRequest options(String host, String requestURI) {
        return options(options.getDefaultPort(), host, requestURI);
    }

    @Override
    public HttpClientRequest options(int port, String host, String requestURI,
            Handler<HttpClientResponse> responseHandler) {
        return request(HttpMethod.OPTIONS, port, host, requestURI, responseHandler);
    }

    @Override
    public HttpClientRequest options(String host, String requestURI, Handler<HttpClientResponse> responseHandler) {
        return options(options.getDefaultPort(), host, requestURI, responseHandler);
    }

    @Override
    public HttpClientRequest options(String requestURI) {
        return request(HttpMethod.OPTIONS, requestURI);
    }

    @Override
    public HttpClientRequest options(String requestURI, Handler<HttpClientResponse> responseHandler) {
        return request(HttpMethod.OPTIONS, requestURI, responseHandler);
    }

    @Override
    public HttpClientRequest optionsAbs(String absoluteURI) {
        return requestAbs(HttpMethod.OPTIONS, absoluteURI);
    }

    @Override
    public HttpClientRequest optionsAbs(String absoluteURI, Handler<HttpClientResponse> responseHandler) {
        return requestAbs(HttpMethod.OPTIONS, absoluteURI, responseHandler);
    }

    @Override
    public HttpClient optionsNow(int port, String host, String requestURI,
            Handler<HttpClientResponse> responseHandler) {
        options(port, host, requestURI, responseHandler).end();
        return this;
    }

    @Override
    public HttpClient optionsNow(String host, String requestURI, Handler<HttpClientResponse> responseHandler) {
        return optionsNow(options.getDefaultPort(), host, requestURI, responseHandler);
    }

    @Override
    public HttpClient optionsNow(String requestURI, Handler<HttpClientResponse> responseHandler) {
        options(requestURI, responseHandler).end();
        return this;
    }

    @Override
    public HttpClientRequest put(int port, String host, String requestURI) {
        return request(HttpMethod.PUT, port, host, requestURI);
    }

    @Override
    public HttpClientRequest put(String host, String requestURI) {
        return put(options.getDefaultPort(), host, requestURI);
    }

    @Override
    public HttpClientRequest put(int port, String host, String requestURI,
            Handler<HttpClientResponse> responseHandler) {
        return request(HttpMethod.PUT, port, host, requestURI, responseHandler);
    }

    @Override
    public HttpClientRequest put(String host, String requestURI, Handler<HttpClientResponse> responseHandler) {
        return put(options.getDefaultPort(), host, requestURI, responseHandler);
    }

    @Override
    public HttpClientRequest put(String requestURI) {
        return request(HttpMethod.PUT, requestURI);
    }

    @Override
    public HttpClientRequest put(String requestURI, Handler<HttpClientResponse> responseHandler) {
        return request(HttpMethod.PUT, requestURI, responseHandler);
    }

    @Override
    public HttpClientRequest putAbs(String absoluteURI) {
        return requestAbs(HttpMethod.PUT, absoluteURI);
    }

    @Override
    public HttpClientRequest putAbs(String absoluteURI, Handler<HttpClientResponse> responseHandler) {
        return requestAbs(HttpMethod.PUT, absoluteURI, responseHandler);
    }

    @Override
    public HttpClientRequest delete(int port, String host, String requestURI) {
        return request(HttpMethod.DELETE, port, host, requestURI);
    }

    @Override
    public HttpClientRequest delete(String host, String requestURI) {
        return delete(options.getDefaultPort(), host, requestURI);
    }

    @Override
    public HttpClientRequest delete(int port, String host, String requestURI,
            Handler<HttpClientResponse> responseHandler) {
        return request(HttpMethod.DELETE, port, host, requestURI, responseHandler);
    }

    @Override
    public HttpClientRequest delete(String host, String requestURI, Handler<HttpClientResponse> responseHandler) {
        return delete(options.getDefaultPort(), host, requestURI, responseHandler);
    }

    @Override
    public HttpClientRequest delete(String requestURI) {
        return request(HttpMethod.DELETE, requestURI);
    }

    @Override
    public HttpClientRequest delete(String requestURI, Handler<HttpClientResponse> responseHandler) {
        return request(HttpMethod.DELETE, requestURI, responseHandler);
    }

    @Override
    public HttpClientRequest deleteAbs(String absoluteURI) {
        return requestAbs(HttpMethod.DELETE, absoluteURI);
    }

    @Override
    public HttpClientRequest deleteAbs(String absoluteURI, Handler<HttpClientResponse> responseHandler) {
        return requestAbs(HttpMethod.DELETE, absoluteURI, responseHandler);
    }

    @Override
    public synchronized void close() {
        checkClosed();
        pool.close();
        for (ClientConnection conn : connectionMap.values()) {
            conn.close();
        }
        if (creatingContext != null) {
            creatingContext.removeCloseHook(closeHook);
        }
        closed = true;
        metrics.close();
    }

    @Override
    public boolean isMetricsEnabled() {
        return metrics != null && metrics.isEnabled();
    }

    @Override
    public Metrics getMetrics() {
        return metrics;
    }

    HttpClientOptions getOptions() {
        return options;
    }

    void getConnection(int port, String host, Handler<ClientConnection> handler,
            Handler<Throwable> connectionExceptionHandler, ContextImpl context) {
        pool.getConnection(port, host, handler, connectionExceptionHandler, context);
    }

    /**
     * @return the vertx, for use in package related classes only.
     */
    VertxInternal getVertx() {
        return vertx;
    }

    SSLHelper getSslHelper() {
        return sslHelper;
    }

    void removeChannel(Channel channel) {
        connectionMap.remove(channel);
    }

    HttpClientMetrics httpClientMetrics() {
        return metrics;
    }

    private void applyConnectionOptions(Bootstrap bootstrap) {
        bootstrap.option(ChannelOption.TCP_NODELAY, options.isTcpNoDelay());
        if (options.getSendBufferSize() != -1) {
            bootstrap.option(ChannelOption.SO_SNDBUF, options.getSendBufferSize());
        }
        if (options.getReceiveBufferSize() != -1) {
            bootstrap.option(ChannelOption.SO_RCVBUF, options.getReceiveBufferSize());
            bootstrap.option(ChannelOption.RCVBUF_ALLOCATOR,
                    new FixedRecvByteBufAllocator(options.getReceiveBufferSize()));
        }
        bootstrap.option(ChannelOption.SO_LINGER, options.getSoLinger());
        if (options.getTrafficClass() != -1) {
            bootstrap.option(ChannelOption.IP_TOS, options.getTrafficClass());
        }
        bootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, options.getConnectTimeout());
        bootstrap.option(ChannelOption.ALLOCATOR, PartialPooledByteBufAllocator.INSTANCE);
        bootstrap.option(ChannelOption.SO_KEEPALIVE, options.isTcpKeepAlive());
        bootstrap.option(ChannelOption.SO_REUSEADDR, options.isReuseAddress());
    }

    private void internalConnect(ContextImpl context, int port, String host,
            Handler<ClientConnection> connectHandler, Handler<Throwable> connectErrorHandler,
            ConnectionLifeCycleListener listener) {
        Bootstrap bootstrap = new Bootstrap();
        bootstrap.group(context.eventLoop());
        bootstrap.channelFactory(new VertxNioSocketChannelFactory());
        sslHelper.validate(vertx);
        bootstrap.handler(new ChannelInitializer<Channel>() {
            @Override
            protected void initChannel(Channel ch) throws Exception {
                ChannelPipeline pipeline = ch.pipeline();
                if (options.isSsl()) {
                    pipeline.addLast("ssl", sslHelper.createSslHandler(vertx, true, host, port));
                }

                pipeline.addLast("codec", new HttpClientCodec(4096, 8192, 8192, false, false));
                if (options.isTryUseCompression()) {
                    pipeline.addLast("inflater", new HttpContentDecompressor(true));
                }
                if (options.getIdleTimeout() > 0) {
                    pipeline.addLast("idle", new IdleStateHandler(0, 0, options.getIdleTimeout()));
                }
                pipeline.addLast("handler", new ClientHandler(vertx, context));
            }
        });
        applyConnectionOptions(bootstrap);
        ChannelFuture future = bootstrap.connect(new InetSocketAddress(host, port));
        future.addListener((ChannelFuture channelFuture) -> {
            Channel ch = channelFuture.channel();
            if (channelFuture.isSuccess()) {
                if (options.isSsl()) {
                    // TCP connected, so now we must do the SSL handshake

                    SslHandler sslHandler = ch.pipeline().get(SslHandler.class);

                    io.netty.util.concurrent.Future<Channel> fut = sslHandler.handshakeFuture();
                    fut.addListener(fut2 -> {
                        if (fut2.isSuccess()) {
                            connected(context, port, host, ch, connectHandler, connectErrorHandler, listener);
                        } else {
                            connectionFailed(context, ch, connectErrorHandler,
                                    new SSLHandshakeException("Failed to create SSL connection"), listener);
                        }
                    });
                } else {
                    connected(context, port, host, ch, connectHandler, connectErrorHandler, listener);
                }
            } else {
                connectionFailed(context, ch, connectErrorHandler, channelFuture.cause(), listener);
            }
        });
    }

    private URL parseUrl(String surl) {
        // Note - parsing a URL this way is slower than specifying host, port and relativeURI
        try {
            return new URL(surl);
        } catch (MalformedURLException e) {
            throw new VertxException("Invalid url: " + surl);
        }
    }

    private HttpClientRequest doRequest(HttpMethod method, String host, int port, String relativeURI,
            MultiMap headers) {
        Objects.requireNonNull(method, "no null method accepted");
        Objects.requireNonNull(host, "no null host accepted");
        Objects.requireNonNull(relativeURI, "no null relativeURI accepted");
        checkClosed();
        HttpClientRequest req = new HttpClientRequestImpl(this, method, host, port, relativeURI, vertx);
        if (headers != null) {
            req.headers().setAll(headers);
        }
        return req;
    }

    private synchronized void checkClosed() {
        if (closed) {
            throw new IllegalStateException("Client is closed");
        }
    }

    private void connected(ContextImpl context, int port, String host, Channel ch,
            Handler<ClientConnection> connectHandler, Handler<Throwable> exceptionHandler,
            ConnectionLifeCycleListener listener) {
        context.executeFromIO(
                () -> createConn(context, port, host, ch, connectHandler, exceptionHandler, listener));
    }

    private void createConn(ContextImpl context, int port, String host, Channel ch,
            Handler<ClientConnection> connectHandler, Handler<Throwable> exceptionHandler,
            ConnectionLifeCycleListener listener) {
        ClientConnection conn = new ClientConnection(vertx, HttpClientImpl.this, exceptionHandler, ch,
                options.isSsl(), host, port, context, listener, metrics);
        conn.closeHandler(v -> {
            // The connection has been closed - tell the pool about it, this allows the pool to create more
            // connections. Note the pool doesn't actually remove the connection, when the next person to get a connection
            // gets the closed on, they will check if it's closed and if so get another one.
            listener.connectionClosed(conn);
        });
        connectionMap.put(ch, conn);
        connectHandler.handle(conn);
    }

    private void connectionFailed(ContextImpl context, Channel ch, Handler<Throwable> connectionExceptionHandler,
            Throwable t, ConnectionLifeCycleListener listener) {
        // If no specific exception handler is provided, fall back to the HttpClient's exception handler.
        // If that doesn't exist just log it
        Handler<Throwable> exHandler = connectionExceptionHandler == null ? log::error : connectionExceptionHandler;

        context.executeFromIO(() -> {
            listener.connectionClosed(null);
            try {
                ch.close();
            } catch (Exception ignore) {
            }
            if (exHandler != null) {
                exHandler.handle(t);
            } else {
                log.error(t);
            }
        });
    }

    private class ClientHandler extends VertxHttpHandler<ClientConnection> {
        private boolean closeFrameSent;
        private ContextImpl context;

        public ClientHandler(VertxInternal vertx, ContextImpl context) {
            super(HttpClientImpl.this.connectionMap);
            this.context = context;
        }

        @Override
        protected ContextImpl getContext(ClientConnection connection) {
            return context;
        }

        @Override
        protected void doMessageReceived(ClientConnection conn, ChannelHandlerContext ctx, Object msg) {
            if (conn == null) {
                return;
            }
            boolean valid = false;
            if (msg instanceof HttpResponse) {
                HttpResponse response = (HttpResponse) msg;
                conn.handleResponse(response);
                valid = true;
            }
            if (msg instanceof HttpContent) {
                HttpContent chunk = (HttpContent) msg;
                if (chunk.content().isReadable()) {
                    Buffer buff = Buffer.buffer(chunk.content().slice());
                    conn.handleResponseChunk(buff);
                }
                if (chunk instanceof LastHttpContent) {
                    conn.handleResponseEnd((LastHttpContent) chunk);
                }
                valid = true;
            } else if (msg instanceof WebSocketFrameInternal) {
                WebSocketFrameInternal frame = (WebSocketFrameInternal) msg;
                switch (frame.type()) {
                case BINARY:
                case CONTINUATION:
                case TEXT:
                    conn.handleWsFrame(frame);
                    break;
                case PING:
                    // Echo back the content of the PING frame as PONG frame as specified in RFC 6455 Section 5.5.2
                    ctx.writeAndFlush(new WebSocketFrameImpl(FrameType.PONG, frame.getBinaryData()));
                    break;
                case PONG:
                    // Just ignore it
                    break;
                case CLOSE:
                    if (!closeFrameSent) {
                        // Echo back close frame and close the connection once it was written.
                        // This is specified in the WebSockets RFC 6455 Section  5.4.1
                        ctx.writeAndFlush(frame).addListener(ChannelFutureListener.CLOSE);
                        closeFrameSent = true;
                    }
                    break;
                default:
                    throw new IllegalStateException("Invalid type: " + frame.type());
                }
                valid = true;
            }
            if (!valid) {
                throw new IllegalStateException("Invalid object " + msg);
            }
        }
    }

    private class WebSocketStreamImpl implements WebSocketStream {

        final int port;
        final String host;
        final String requestURI;
        final MultiMap headers;
        final WebsocketVersion version;
        final String subProtocols;
        private Handler<WebSocket> handler;
        private Handler<Throwable> exceptionHandler;
        private Handler<Void> endHandler;

        public WebSocketStreamImpl(int port, String host, String requestURI, MultiMap headers,
                WebsocketVersion version, String subProtocols) {
            this.port = port;
            this.host = host;
            this.requestURI = requestURI;
            this.headers = headers;
            this.version = version;
            this.subProtocols = subProtocols;
        }

        @Override
        public synchronized WebSocketStream exceptionHandler(Handler<Throwable> handler) {
            exceptionHandler = handler;
            return this;
        }

        @Override
        public synchronized WebSocketStream handler(Handler<WebSocket> handler) {
            if (this.handler == null && handler != null) {
                this.handler = handler;
                checkClosed();
                ContextImpl context = vertx.getOrCreateContext();
                Handler<Throwable> connectionExceptionHandler = exceptionHandler;
                if (connectionExceptionHandler == null) {
                    connectionExceptionHandler = log::error;
                }
                Handler<WebSocket> wsConnect;
                if (endHandler != null) {
                    Handler<Void> endCallback = endHandler;
                    wsConnect = ws -> {
                        handler.handle(ws);
                        endCallback.handle(null);
                    };
                } else {
                    wsConnect = handler;
                }
                getConnection(port, host, conn -> {
                    if (!conn.isClosed()) {
                        conn.toWebSocket(requestURI, headers, version, subProtocols,
                                options.getMaxWebsocketFrameSize(), wsConnect);
                    } else {
                        websocket(port, host, requestURI, headers, version, subProtocols, wsConnect);
                    }
                }, connectionExceptionHandler, context);
            }
            return this;
        }

        @Override
        public synchronized WebSocketStream endHandler(Handler<Void> endHandler) {
            this.endHandler = endHandler;
            return this;
        }

        @Override
        public WebSocketStream pause() {
            return this;
        }

        @Override
        public WebSocketStream resume() {
            return this;
        }
    }

    @Override
    protected void finalize() throws Throwable {
        // Make sure this gets cleaned up if there are no more references to it
        // so as not to leave connections and resources dangling until the system is shutdown
        // which could make the JVM run out of file handles.
        close();
        super.finalize();
    }

}