/* * 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.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelOutboundHandler; import io.netty.handler.ssl.SslHandler; import io.netty.util.CharsetUtil; import io.vertx.core.AsyncResult; import io.vertx.core.Future; import io.vertx.core.Handler; import io.vertx.core.buffer.Buffer; import io.vertx.core.eventbus.Message; import io.vertx.core.eventbus.MessageConsumer; 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.NetSocket; import io.vertx.core.net.SocketAddress; import io.vertx.core.spi.metrics.TCPMetrics; import javax.net.ssl.SSLPeerUnverifiedException; import javax.security.cert.X509Certificate; import java.io.File; import java.io.IOException; import java.io.RandomAccessFile; import java.nio.charset.Charset; import java.util.UUID; /** * * This class is optimised for performance when used on the same event loop that is was passed to the handler with. * However it can be used safely from other threads. * * The internal state is protected using the synchronized keyword. If always used on the same event loop, then * we benefit from biased locking which makes the overhead of synchronized near zero. * * @author <a href="http://tfox.org">Tim Fox</a> */ public class NetSocketImpl extends ConnectionBase implements NetSocket { private static final Logger log = LoggerFactory.getLogger(NetSocketImpl.class); private final String writeHandlerID; private final MessageConsumer registration; private final SSLHelper helper; private final String host; private final int port; private final TCPMetrics metrics; private Handler<Buffer> dataHandler; private Handler<Void> endHandler; private Handler<Void> drainHandler; private Buffer pendingData; private boolean paused = false; private ChannelFuture writeFuture; public NetSocketImpl(VertxInternal vertx, Channel channel, ContextImpl context, SSLHelper helper, TCPMetrics metrics) { this(vertx, channel, null, 0, context, helper, metrics); } public NetSocketImpl(VertxInternal vertx, Channel channel, String host, int port, ContextImpl context, SSLHelper helper, TCPMetrics metrics) { super(vertx, channel, context); this.helper = helper; this.writeHandlerID = UUID.randomUUID().toString(); this.host = host; this.port = port; this.metrics = metrics; Handler<Message<Buffer>> writeHandler = msg -> write(msg.body()); registration = vertx.eventBus().<Buffer>localConsumer(writeHandlerID).handler(writeHandler); } @Override public TCPMetrics metrics() { return metrics; } @Override public String writeHandlerID() { return writeHandlerID; } @Override public NetSocket write(Buffer data) { ByteBuf buf = data.getByteBuf(); write(buf); return this; } @Override public NetSocket write(String str) { write(Unpooled.copiedBuffer(str, CharsetUtil.UTF_8)); return this; } @Override public NetSocket write(String str, String enc) { if (enc == null) { write(str); } else { write(Unpooled.copiedBuffer(str, Charset.forName(enc))); } return this; } @Override public synchronized NetSocket handler(Handler<Buffer> dataHandler) { this.dataHandler = dataHandler; return this; } @Override public synchronized NetSocket pause() { if (!paused) { paused = true; doPause(); } return this; } @Override public synchronized NetSocket resume() { if (paused) { paused = false; if (pendingData != null) { // Send empty buffer to trigger sending of pending data context.runOnContext(v -> handleDataReceived(Buffer.buffer())); } doResume(); } return this; } @Override public NetSocket setWriteQueueMaxSize(int maxSize) { doSetWriteQueueMaxSize(maxSize); return this; } @Override public boolean writeQueueFull() { return isNotWritable(); } @Override public synchronized NetSocket endHandler(Handler<Void> endHandler) { this.endHandler = endHandler; return this; } @Override public synchronized NetSocket drainHandler(Handler<Void> drainHandler) { this.drainHandler = drainHandler; vertx.runOnContext(v -> callDrainHandler()); //If the channel is already drained, we want to call it immediately return this; } @Override public NetSocket sendFile(String filename, long offset, long length) { return sendFile(filename, offset, length, null); } @Override public NetSocket sendFile(String filename, long offset, long length, final Handler<AsyncResult<Void>> resultHandler) { File f = vertx.resolveFile(filename); if (f.isDirectory()) { throw new IllegalArgumentException("filename must point to a file and not to a directory"); } RandomAccessFile raf = null; try { raf = new RandomAccessFile(f, "r"); ChannelFuture future = super.sendFile(raf, Math.min(offset, f.length()), Math.min(length, f.length() - offset)); if (resultHandler != null) { future.addListener(fut -> { final AsyncResult<Void> res; if (future.isSuccess()) { res = Future.succeededFuture(); } else { res = Future.failedFuture(future.cause()); } vertx.runOnContext(v -> resultHandler.handle(res)); }); } } catch (IOException e) { try { if (raf != null) { raf.close(); } } catch (IOException ignore) { } if (resultHandler != null) { vertx.runOnContext(v -> resultHandler.handle(Future.failedFuture(e))); } else { log.error("Failed to send file", e); } } return this; } @Override public SocketAddress remoteAddress() { return super.remoteAddress(); } public SocketAddress localAddress() { return super.localAddress(); } @Override public NetSocketImpl exceptionHandler(Handler<Throwable> handler) { return (NetSocketImpl) super.exceptionHandler(handler); } @Override public NetSocketImpl closeHandler(Handler<Void> handler) { return (NetSocketImpl) super.closeHandler(handler); } @Override public synchronized void close() { if (writeFuture != null) { // Close after all data is written writeFuture.addListener(ChannelFutureListener.CLOSE); channel.flush(); } else { super.close(); } } @Override public NetSocket upgradeToSsl(Handler<Void> handler) { return upgradeToSsl(null, handler); } @Override public NetSocket upgradeToSsl(String serverName, Handler<Void> handler) { ChannelOutboundHandler sslHandler = (ChannelOutboundHandler) channel.pipeline().get("ssl"); if (sslHandler == null) { if (host != null) { sslHandler = new SslHandler(helper.createEngine(vertx, host, port, serverName)); } else { if (helper.isSNI()) { sslHandler = new VertxSniHandler(helper, vertx); } else { sslHandler = new SslHandler(helper.createEngine(vertx)); } } channel.pipeline().addFirst("ssl", sslHandler); } io.netty.util.concurrent.Future<Channel> handshakeFuture; if (sslHandler instanceof SslHandler) { handshakeFuture = ((SslHandler) sslHandler).handshakeFuture(); } else { handshakeFuture = ((VertxSniHandler) sslHandler).handshakeFuture(); } handshakeFuture.addListener(future -> context.executeFromIO(() -> { if (future.isSuccess()) { handler.handle(null); } else { log.error(future.cause()); } })); return this; } @Override protected synchronized void handleInterestedOpsChanged() { checkContext(); callDrainHandler(); } @Override public void end() { close(); } @Override protected synchronized void handleClosed() { checkContext(); if (endHandler != null) { endHandler.handle(null); } super.handleClosed(); if (vertx.eventBus() != null) { registration.unregister(); } } public synchronized void handleDataReceived(Buffer data) { checkContext(); if (paused) { if (pendingData == null) { pendingData = data.copy(); } else { pendingData.appendBuffer(data); } return; } if (pendingData != null) { data = pendingData.appendBuffer(data); pendingData = null; } reportBytesRead(data.length()); if (dataHandler != null) { dataHandler.handle(data); } } private void write(ByteBuf buff) { reportBytesWritten(buff.readableBytes()); writeFuture = super.writeToChannel(buff); } private synchronized void callDrainHandler() { if (drainHandler != null) { if (!writeQueueFull()) { drainHandler.handle(null); } } } }