io.vertx.core.net.impl.NetClientImpl.java Source code

Java tutorial

Introduction

Here is the source code for io.vertx.core.net.impl.NetClientImpl.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.net.impl;

import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;

import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.Channel;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.FixedRecvByteBufAllocator;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.ssl.SslHandler;
import io.netty.handler.stream.ChunkedWriteHandler;
import io.netty.handler.timeout.IdleStateHandler;
import io.vertx.core.AsyncResult;
import io.vertx.core.Closeable;
import io.vertx.core.Future;
import io.vertx.core.Handler;
import io.vertx.core.buffer.Buffer;
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.NetClient;
import io.vertx.core.net.NetClientOptions;
import io.vertx.core.net.NetSocket;
import io.vertx.core.spi.metrics.Metrics;
import io.vertx.core.spi.metrics.MetricsProvider;
import io.vertx.core.spi.metrics.TCPMetrics;

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

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

    private final VertxInternal vertx;
    private final NetClientOptions options;
    private final SSLHelper sslHelper;
    private final Map<Channel, NetSocketImpl> socketMap = new ConcurrentHashMap<>();
    private final Closeable closeHook;
    private final ContextImpl creatingContext;
    private final TCPMetrics metrics;
    private final boolean logEnabled;
    private volatile boolean closed;

    public NetClientImpl(VertxInternal vertx, NetClientOptions options) {
        this(vertx, options, true);
    }

    public NetClientImpl(VertxInternal vertx, NetClientOptions options, boolean useCreatingContext) {
        this.vertx = vertx;
        this.options = new NetClientOptions(options);
        this.sslHelper = new SSLHelper(options, options.getKeyCertOptions(), options.getTrustOptions());
        this.logEnabled = options.getLogActivity();
        this.closeHook = completionHandler -> {
            NetClientImpl.this.close();
            completionHandler.handle(Future.succeededFuture());
        };
        if (useCreatingContext) {
            creatingContext = vertx.getContext();
            if (creatingContext != null) {
                if (creatingContext.isMultiThreadedWorkerContext()) {
                    throw new IllegalStateException("Cannot use NetClient in a multi-threaded worker verticle");
                }
                creatingContext.addCloseHook(closeHook);
            }
        } else {
            creatingContext = null;
        }
        this.metrics = vertx.metricsSPI().createMetrics(this, options);
    }

    public synchronized NetClient connect(int port, String host, Handler<AsyncResult<NetSocket>> connectHandler) {
        checkClosed();
        connect(port, host, connectHandler, options.getReconnectAttempts());
        return this;
    }

    @Override
    public void close() {
        if (!closed) {
            for (NetSocket sock : socketMap.values()) {
                sock.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;
    }

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

    private void applyConnectionOptions(Bootstrap bootstrap) {
        if (options.getLocalAddress() != null) {
            bootstrap.localAddress(options.getLocalAddress(), 0);
        }
        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()));
        }
        if (options.getSoLinger() != -1) {
            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());
    }

    private void connect(int port, String host, Handler<AsyncResult<NetSocket>> connectHandler,
            int remainingAttempts) {
        Objects.requireNonNull(host, "No null host accepted");
        Objects.requireNonNull(connectHandler, "No null connectHandler accepted");
        ContextImpl context = vertx.getOrCreateContext();
        sslHelper.validate(vertx);
        Bootstrap bootstrap = new Bootstrap();
        bootstrap.group(context.nettyEventLoop());
        bootstrap.channel(NioSocketChannel.class);

        applyConnectionOptions(bootstrap);

        ChannelProvider channelProvider;
        if (options.getProxyOptions() == null) {
            channelProvider = ChannelProvider.INSTANCE;
        } else {
            channelProvider = ProxyChannelProvider.INSTANCE;
        }

        Handler<Channel> channelInitializer = ch -> {

            if (sslHelper.isSSL()) {
                SslHandler sslHandler = sslHelper.createSslHandler(vertx, host, port);
                ch.pipeline().addLast("ssl", sslHandler);
            }

            ChannelPipeline pipeline = ch.pipeline();
            if (logEnabled) {
                pipeline.addLast("logging", new LoggingHandler());
            }
            if (sslHelper.isSSL()) {
                // only add ChunkedWriteHandler when SSL is enabled otherwise it is not needed as FileRegion is used.
                pipeline.addLast("chunkedWriter", new ChunkedWriteHandler()); // For large file / sendfile support
            }
            if (options.getIdleTimeout() > 0) {
                pipeline.addLast("idle", new IdleStateHandler(0, 0, options.getIdleTimeout()));
            }
            pipeline.addLast("handler", new VertxNetHandler<NetSocketImpl>(ch, socketMap) {
                @Override
                protected void handleMsgReceived(NetSocketImpl conn, Object msg) {
                    ByteBuf buf = (ByteBuf) msg;
                    conn.handleDataReceived(Buffer.buffer(buf));
                }
            });
        };

        Handler<AsyncResult<Channel>> channelHandler = res -> {
            if (res.succeeded()) {

                Channel ch = res.result();

                if (sslHelper.isSSL()) {
                    // TCP connected, so now we must do the SSL handshake
                    SslHandler sslHandler = (SslHandler) ch.pipeline().get("ssl");

                    io.netty.util.concurrent.Future<Channel> fut = sslHandler.handshakeFuture();
                    fut.addListener(future2 -> {
                        if (future2.isSuccess()) {
                            connected(context, ch, connectHandler, host, port);
                        } else {
                            failed(context, ch, future2.cause(), connectHandler);
                        }
                    });
                } else {
                    connected(context, ch, connectHandler, host, port);
                }

            } else {
                if (remainingAttempts > 0 || remainingAttempts == -1) {
                    context.executeFromIO(() -> {
                        log.debug("Failed to create connection. Will retry in " + options.getReconnectInterval()
                                + " milliseconds");
                        //Set a timer to retry connection
                        vertx.setTimer(options.getReconnectInterval(), tid -> connect(port, host, connectHandler,
                                remainingAttempts == -1 ? remainingAttempts : remainingAttempts - 1));
                    });
                } else {
                    failed(context, null, res.cause(), connectHandler);
                }
            }
        };

        channelProvider.connect(vertx, bootstrap, options.getProxyOptions(), host, port, channelInitializer,
                channelHandler);
    }

    private void connected(ContextImpl context, Channel ch, Handler<AsyncResult<NetSocket>> connectHandler,
            String host, int port) {
        // Need to set context before constructor is called as writehandler registration needs this
        ContextImpl.setContext(context);
        NetSocketImpl sock = new NetSocketImpl(vertx, ch, host, port, context, sslHelper, metrics);
        VertxNetHandler handler = ch.pipeline().get(VertxNetHandler.class);
        handler.conn = sock;
        socketMap.put(ch, sock);
        context.executeFromIO(() -> {
            sock.metric(metrics.connected(sock.remoteAddress(), sock.remoteName()));
            connectHandler.handle(Future.succeededFuture(sock));
        });
    }

    private void failed(ContextImpl context, Channel ch, Throwable th,
            Handler<AsyncResult<NetSocket>> connectHandler) {
        if (ch != null) {
            ch.close();
        }
        context.executeFromIO(() -> doFailed(connectHandler, th));
    }

    private static void doFailed(Handler<AsyncResult<NetSocket>> connectHandler, Throwable th) {
        connectHandler.handle(Future.failedFuture(th));
    }

    @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();
    }
}