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

Java tutorial

Introduction

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

Source

/*
 * Copyright (c) 2011-2017 Contributors to the Eclipse Foundation
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
 * which is available at https://www.apache.org/licenses/LICENSE-2.0.
 *
 * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
 */

package io.vertx.core.http.impl;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelPipeline;
import io.netty.handler.codec.http.*;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.ssl.SslHandler;
import io.netty.handler.timeout.IdleStateHandler;
import io.vertx.core.AsyncResult;
import io.vertx.core.Future;
import io.vertx.core.Handler;
import io.vertx.core.http.HttpClientOptions;
import io.vertx.core.http.HttpVersion;
import io.vertx.core.http.impl.pool.ConnectResult;
import io.vertx.core.http.impl.pool.ConnectionListener;
import io.vertx.core.http.impl.pool.ConnectionProvider;
import io.vertx.core.impl.ContextInternal;
import io.vertx.core.net.ProxyType;
import io.vertx.core.net.SocketAddress;
import io.vertx.core.net.impl.ChannelProvider;
import io.vertx.core.net.impl.ProxyChannelProvider;
import io.vertx.core.net.impl.SSLHelper;
import io.vertx.core.spi.metrics.HttpClientMetrics;

import javax.net.ssl.SSLHandshakeException;

/**
 * Performs the channel configuration and connection according to the client options and the protocol version.
 *
 * @author <a href="mailto:julien@julienviet.com">Julien Viet</a>
 */
class HttpChannelConnector implements ConnectionProvider<HttpClientConnection> {

    private final HttpClientImpl client;
    private final HttpClientOptions options;
    private final HttpClientMetrics metrics;
    private final SSLHelper sslHelper;
    private final HttpVersion version;
    private final long weight;
    private final long http1Weight;
    private final long http2Weight;
    private final long http1MaxConcurrency;
    private final boolean ssl;
    private final String peerHost;
    private final String host;
    private final int port;
    private final Object metric;

    HttpChannelConnector(HttpClientImpl client, Object metric, HttpVersion version, boolean ssl, String peerHost,
            String host, int port) {
        this.client = client;
        this.metric = metric;
        this.options = client.getOptions();
        this.metrics = client.metrics();
        this.sslHelper = client.getSslHelper();
        this.version = version;
        // this is actually normal (although it sounds weird)
        // the pool uses a weight mechanism to keep track of the max number of connections
        // for instance when http2Size = 2 and http1Size= 5 then maxWeight = 10
        // which means that the pool can contain
        // - maxWeight / http1Weight = 5 HTTP/1.1 connections
        // - maxWeight / http2Weight = 2 HTTP/2 connections
        this.http1Weight = options.getHttp2MaxPoolSize();
        this.http2Weight = options.getMaxPoolSize();
        this.weight = version == HttpVersion.HTTP_2 ? http2Weight : http1Weight;
        this.http1MaxConcurrency = options.isPipelining() ? options.getPipeliningLimit() : 1;
        this.ssl = ssl;
        this.peerHost = peerHost;
        this.host = host;
        this.port = port;
    }

    public long weight() {
        return weight;
    }

    @Override
    public void close(HttpClientConnection conn) {
        conn.close();
    }

    @Override
    public void connect(ConnectionListener<HttpClientConnection> listener, ContextInternal context,
            Handler<AsyncResult<ConnectResult<HttpClientConnection>>> handler) {
        Future<ConnectResult<HttpClientConnection>> future = Future.<ConnectResult<HttpClientConnection>>future()
                .setHandler(handler);
        try {
            doConnect(listener, context, future);
        } catch (Exception e) {
            future.tryFail(e);
        }
    }

    @Override
    public void activate(HttpClientConnection conn) {
        if (options.getIdleTimeout() > 0) {
            ChannelPipeline pipeline = conn.channelHandlerContext().pipeline();
            pipeline.addFirst("idle",
                    new IdleStateHandler(0, 0, options.getIdleTimeout(), options.getIdleTimeoutUnit()));
        }
    }

    @Override
    public void deactivate(HttpClientConnection conn) {
        if (options.getIdleTimeout() > 0) {
            ChannelPipeline pipeline = conn.channelHandlerContext().pipeline();
            pipeline.remove("idle");
        }
    }

    private void doConnect(ConnectionListener<HttpClientConnection> listener, ContextInternal context,
            Future<ConnectResult<HttpClientConnection>> future) {

        Bootstrap bootstrap = new Bootstrap();
        bootstrap.group(context.nettyEventLoop());
        bootstrap.channelFactory(client.getVertx().transport().channelFactory(false));

        applyConnectionOptions(bootstrap);

        ChannelProvider channelProvider;
        // http proxy requests are handled in HttpClientImpl, everything else can use netty proxy handler
        if (options.getProxyOptions() == null || !ssl && options.getProxyOptions().getType() == ProxyType.HTTP) {
            channelProvider = ChannelProvider.INSTANCE;
        } else {
            channelProvider = ProxyChannelProvider.INSTANCE;
        }

        boolean useAlpn = options.isUseAlpn();
        Handler<Channel> channelInitializer = ch -> {

            // Configure pipeline
            ChannelPipeline pipeline = ch.pipeline();
            if (ssl) {
                SslHandler sslHandler = new SslHandler(sslHelper.createEngine(client.getVertx(), peerHost, port,
                        options.isForceSni() ? peerHost : null));
                ch.pipeline().addLast("ssl", sslHandler);
                // TCP connected, so now we must do the SSL handshake
                sslHandler.handshakeFuture().addListener(fut -> {
                    if (fut.isSuccess()) {
                        String protocol = sslHandler.applicationProtocol();
                        if (useAlpn) {
                            if ("h2".equals(protocol)) {
                                applyHttp2ConnectionOptions(ch.pipeline());
                                http2Connected(listener, context, ch, future);
                            } else {
                                applyHttp1xConnectionOptions(ch.pipeline());
                                HttpVersion fallbackProtocol = "http/1.0".equals(protocol) ? HttpVersion.HTTP_1_0
                                        : HttpVersion.HTTP_1_1;
                                http1xConnected(listener, fallbackProtocol, host, port, true, context, ch,
                                        http1Weight, future);
                            }
                        } else {
                            applyHttp1xConnectionOptions(ch.pipeline());
                            http1xConnected(listener, version, host, port, true, context, ch, http1Weight, future);
                        }
                    } else {
                        handshakeFailure(ch, fut.cause(), listener, future);
                    }
                });
            } else {
                if (version == HttpVersion.HTTP_2) {
                    if (options.isHttp2ClearTextUpgrade()) {
                        applyHttp1xConnectionOptions(pipeline);
                    } else {
                        applyHttp2ConnectionOptions(pipeline);
                    }
                } else {
                    applyHttp1xConnectionOptions(pipeline);
                }
            }
        };

        Handler<AsyncResult<Channel>> channelHandler = res -> {

            if (res.succeeded()) {
                Channel ch = res.result();
                if (!ssl) {
                    if (version == HttpVersion.HTTP_2) {
                        if (options.isHttp2ClearTextUpgrade()) {
                            http1xConnected(listener, version, host, port, false, context, ch, http2Weight, future);
                        } else {
                            http2Connected(listener, context, ch, future);
                        }
                    } else {
                        http1xConnected(listener, version, host, port, false, context, ch, http1Weight, future);
                    }
                }
            } else {
                connectFailed(null, listener, res.cause(), future);
            }
        };

        channelProvider.connect(client.getVertx(), bootstrap, options.getProxyOptions(),
                SocketAddress.inetSocketAddress(port, host), channelInitializer, channelHandler);
    }

    private void applyConnectionOptions(Bootstrap bootstrap) {
        client.getVertx().transport().configure(options, bootstrap);
    }

    private void applyHttp2ConnectionOptions(ChannelPipeline pipeline) {
        // No specific options
    }

    private void applyHttp1xConnectionOptions(ChannelPipeline pipeline) {
        if (options.getLogActivity()) {
            pipeline.addLast("logging", new LoggingHandler());
        }
        pipeline.addLast("codec", new HttpClientCodec(options.getMaxInitialLineLength(), options.getMaxHeaderSize(),
                options.getMaxChunkSize(), false, false, options.getDecoderInitialBufferSize()));
        if (options.isTryUseCompression()) {
            pipeline.addLast("inflater", new HttpContentDecompressor(true));
        }
    }

    private void handshakeFailure(Channel ch, Throwable cause, ConnectionListener<HttpClientConnection> listener,
            Future<ConnectResult<HttpClientConnection>> future) {
        SSLHandshakeException sslException = new SSLHandshakeException("Failed to create SSL connection");
        if (cause != null) {
            sslException.initCause(cause);
        }
        connectFailed(ch, listener, sslException, future);
    }

    private void http1xConnected(ConnectionListener<HttpClientConnection> listener, HttpVersion version,
            String host, int port, boolean ssl, ContextInternal context, Channel ch, long weight,
            Future<ConnectResult<HttpClientConnection>> future) {
        boolean upgrade = version == HttpVersion.HTTP_2 && options.isHttp2ClearTextUpgrade();
        Http1xClientHandler clientHandler = new Http1xClientHandler(listener, context,
                upgrade ? HttpVersion.HTTP_1_1 : version, peerHost, host, port, ssl, client, metric,
                client.metrics());
        clientHandler.addHandler(conn -> {
            if (upgrade) {
                future.complete(new ConnectResult<>(new Http2UpgradedClientConnection(client, conn), 1, ch, context,
                        http2Weight));
            } else {
                future.complete(new ConnectResult<>(conn, http1MaxConcurrency, ch, context, http1Weight));
            }
        });
        clientHandler.removeHandler(conn -> {
            listener.onDiscard();
        });
        ch.pipeline().addLast("handler", clientHandler);
    }

    private void http2Connected(ConnectionListener<HttpClientConnection> listener, ContextInternal context,
            Channel ch, Future<ConnectResult<HttpClientConnection>> future) {
        try {
            VertxHttp2ConnectionHandler<Http2ClientConnection> clientHandler = Http2ClientConnection
                    .createHttp2ConnectionHandler(client, metric, listener, context, (conn, concurrency) -> {
                        future.complete(new ConnectResult<>(conn, concurrency, ch, context, http2Weight));
                    });
            ch.pipeline().addLast("handler", clientHandler);
            ch.flush();
        } catch (Exception e) {
            connectFailed(ch, listener, e, future);
        }
    }

    private void connectFailed(Channel ch, ConnectionListener<HttpClientConnection> listener, Throwable t,
            Future<ConnectResult<HttpClientConnection>> future) {
        if (ch != null) {
            try {
                ch.close();
            } catch (Exception ignore) {
            }
        }
        future.tryFail(t);
    }
}