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

Java tutorial

Introduction

Here is the source code for io.vertx.core.net.impl.NetClientBase.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 io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBufAllocator;
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.ssl.SslHandler;
import io.vertx.core.AsyncResult;
import io.vertx.core.Closeable;
import io.vertx.core.Future;
import io.vertx.core.Handler;
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.NetClientOptions;
import io.vertx.core.spi.metrics.Metrics;
import io.vertx.core.spi.metrics.MetricsProvider;
import io.vertx.core.spi.metrics.TCPMetrics;

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 abstract class NetClientBase<C extends ConnectionBase> implements MetricsProvider {

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

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

    public NetClientBase(VertxInternal vertx, NetClientOptions options, boolean useCreatingContext) {
        this.vertx = vertx;
        this.options = new NetClientOptions(options);
        this.sslHelper = new SSLHelper(options, options.getKeyCertOptions(), options.getTrustOptions());
        this.closeHook = completionHandler -> {
            NetClientBase.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(options);
    }

    /**
     * Create a connection for a channel.
     *
     * @return the created connection
     */
    protected abstract C createConnection(VertxInternal vertx, Channel channel, String host, int port,
            ContextImpl context, SSLHelper helper, TCPMetrics metrics);

    protected abstract void handleMsgReceived(C conn, Object msg);

    protected abstract void initChannel(ChannelPipeline pipeline);

    protected abstract Object safeObject(Object msg, ByteBufAllocator allocator);

    public void close() {
        if (!closed) {
            for (C 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());
    }

    protected void doConnect(int port, String host, String serverName, Handler<AsyncResult<C>> connectHandler) {
        doConnect(port, host, serverName, connectHandler, options.getReconnectAttempts());
    }

    protected void doConnect(int port, String host, String serverName, Handler<AsyncResult<C>> connectHandler,
            int remainingAttempts) {
        checkClosed();
        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 -> {
            ChannelPipeline pipeline = ch.pipeline();
            if (sslHelper.isSSL()) {
                SslHandler sslHandler = new SslHandler(sslHelper.createEngine(vertx, host, port, serverName));
                ch.pipeline().addLast("ssl", sslHandler);
            }
            initChannel(pipeline);
            pipeline.addLast("handler", new VertxNetHandler<C>(ch, socketMap) {
                @Override
                protected Object safeObject(Object msg, ByteBufAllocator allocator) throws Exception {
                    return NetClientBase.this.safeObject(msg, allocator);
                }

                @Override
                protected void handleMsgReceived(C conn, Object msg) {
                    NetClientBase.this.handleMsgReceived(conn, msg);
                }
            });
        };

        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 -> doConnect(port, host, serverName, 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<C>> connectHandler, String host,
            int port) {
        // Need to set context before constructor is called as writehandler registration needs this
        ContextImpl.setContext(context);
        C sock = createConnection(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<C>> connectHandler) {
        if (ch != null) {
            ch.close();
        }
        context.executeFromIO(() -> doFailed(connectHandler, th));
    }

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