/* * 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)); } }