
io.jsync.net.impl.DefaultNetServer Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jsync.io Show documentation
Show all versions of jsync.io Show documentation
jsync.io is a non-blocking, event-driven networking framework for Java
/*
* 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.jsync.net.impl;
import io.jsync.AsyncResult;
import io.jsync.Handler;
import io.jsync.VoidHandler;
import io.jsync.impl.AsyncInternal;
import io.jsync.impl.Closeable;
import io.jsync.impl.DefaultContext;
import io.jsync.impl.DefaultFutureResult;
import io.jsync.logging.Logger;
import io.jsync.logging.impl.LoggerFactory;
import io.jsync.net.NetServer;
import io.jsync.net.NetSocket;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.ChannelGroupFuture;
import io.netty.channel.group.ChannelGroupFutureListener;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.ssl.SslHandler;
import io.netty.handler.stream.ChunkedWriteHandler;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.GenericFutureListener;
import io.netty.util.concurrent.GlobalEventExecutor;
import javax.net.ssl.SSLContext;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.Map;
import java.util.Queue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
/**
* @author Tim Fox
*/
public class DefaultNetServer implements NetServer, Closeable {
private static final Logger log = LoggerFactory.getLogger(DefaultNetServer.class);
private final AsyncInternal async;
private final DefaultContext actualCtx;
private final TCPSSLHelper tcpHelper = new TCPSSLHelper();
private final Map socketMap = new ConcurrentHashMap();
private final AsyncEventLoopGroup availableWorkers = new AsyncEventLoopGroup();
private final HandlerManager handlerManager = new HandlerManager<>(availableWorkers);
private Handler connectHandler;
private ChannelGroup serverChannelGroup;
private boolean listening;
private volatile ServerID id;
private DefaultNetServer actualServer;
private String host;
private volatile int port;
private ChannelFuture bindFuture;
private Queue bindListeners = new ConcurrentLinkedQueue<>();
private boolean listenersRun;
public DefaultNetServer(AsyncInternal async) {
this.async = async;
actualCtx = async.getOrCreateContext();
actualCtx.addCloseHook(this);
tcpHelper.setReuseAddress(true);
}
@Override
public NetServer connectHandler(Handler connectHandler) {
this.connectHandler = connectHandler;
return this;
}
public NetServer listen(int port) {
listen(port, "0.0.0.0", null);
return this;
}
public NetServer listen(int port, Handler> listenHandler) {
listen(port, "0.0.0.0", listenHandler);
return this;
}
public NetServer listen(int port, String host) {
listen(port, host, null);
return this;
}
public NetServer listen(final int port, final String host, final Handler> listenHandler) {
if (connectHandler == null) {
throw new IllegalStateException("Set connect handler first");
}
if (listening) {
throw new IllegalStateException("Listen already called");
}
listening = true;
this.host = host;
synchronized (async.sharedNetServers()) {
id = new ServerID(port, host);
DefaultNetServer shared = (DefaultNetServer) async.sharedNetServers().get(id);
if (shared == null || port == 0) { // Wildcard port will imply a new actual server each time
serverChannelGroup = new DefaultChannelGroup("async-acceptor-channels", GlobalEventExecutor.INSTANCE);
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(availableWorkers);
bootstrap.channel(NioServerSocketChannel.class);
tcpHelper.checkSSL(async);
bootstrap.childHandler(new ChannelInitializer() {
@Override
protected void initChannel(Channel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
if (tcpHelper.isSSL()) {
SslHandler sslHandler = tcpHelper.createSslHandler(async, false);
pipeline.addLast("ssl", sslHandler);
}
if (tcpHelper.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
}
pipeline.addLast("handler", new ServerHandler());
}
});
tcpHelper.applyConnectionOptions(bootstrap);
if (connectHandler != null) {
// Share the event loop thread to also serve the NetServer's network traffic.
handlerManager.addHandler(connectHandler, actualCtx);
}
try {
InetSocketAddress addr = new InetSocketAddress(InetAddress.getByName(host), port);
bindFuture = bootstrap.bind(addr).addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
runListeners();
}
});
this.addListener(new Runnable() {
@Override
public void run() {
if (bindFuture.isSuccess()) {
log.trace("Net server listening on " + host + ":" + bindFuture.channel().localAddress());
// Update port to actual port - wildcard port 0 might have been used
DefaultNetServer.this.port = ((InetSocketAddress) bindFuture.channel().localAddress()).getPort();
DefaultNetServer.this.id = new ServerID(DefaultNetServer.this.port, id.host);
async.sharedNetServers().put(id, DefaultNetServer.this);
} else {
async.sharedNetServers().remove(id);
}
}
});
serverChannelGroup.add(bindFuture.channel());
} catch (final Throwable t) {
// Make sure we send the exception back through the handler (if any)
if (listenHandler != null) {
async.runOnContext(new VoidHandler() {
@Override
protected void handle() {
listenHandler.handle(new DefaultFutureResult(t));
}
});
} else {
// No handler - log so user can see failure
actualCtx.reportException(t);
}
listening = false;
return this;
}
if (port != 0) {
async.sharedNetServers().put(id, this);
}
actualServer = this;
} else {
// Server already exists with that host/port - we will use that
checkConfigs(actualServer, this);
actualServer = shared;
this.port = shared.port();
if (connectHandler != null) {
// Share the event loop thread to also serve the NetServer's network traffic.
actualServer.handlerManager.addHandler(connectHandler, actualCtx);
}
}
// just add it to the future so it gets notified once the bind is complete
actualServer.addListener(new Runnable() {
public void run() {
if (listenHandler != null) {
final AsyncResult res;
if (actualServer.bindFuture.isSuccess()) {
res = new DefaultFutureResult(DefaultNetServer.this);
} else {
listening = false;
res = new DefaultFutureResult<>(actualServer.bindFuture.cause());
}
actualCtx.execute(actualServer.bindFuture.channel().eventLoop(), new Runnable() {
@Override
public void run() {
listenHandler.handle(res);
}
});
} else if (!actualServer.bindFuture.isSuccess()) {
// No handler - log so user can see failure
actualCtx.reportException(actualServer.bindFuture.cause());
listening = false;
}
}
});
}
return this;
}
private synchronized void addListener(Runnable runner) {
if (!listenersRun) {
bindListeners.add(runner);
} else {
// Run it now
runner.run();
}
}
private synchronized void runListeners() {
Runnable runner;
while ((runner = bindListeners.poll()) != null) {
runner.run();
}
listenersRun = true;
}
public void close() {
close(null);
}
@Override
public void close(final Handler> done) {
if (!listening) {
if (done != null) {
executeCloseDone(actualCtx, done, null);
}
return;
}
listening = false;
synchronized (async.sharedNetServers()) {
if (actualServer != null) {
actualServer.handlerManager.removeHandler(connectHandler, actualCtx);
if (actualServer.handlerManager.hasHandlers()) {
// The actual server still has handlers so we don't actually close it
if (done != null) {
executeCloseDone(actualCtx, done, null);
}
} else {
// No Handlers left so close the actual server
// The done handler needs to be executed on the context that calls close, NOT the context
// of the actual server
actualServer.actualClose(actualCtx, done);
}
}
}
actualCtx.removeCloseHook(this);
}
@Override
public String host() {
return host;
}
@Override
public int port() {
return port;
}
@Override
public boolean isTCPNoDelay() {
return tcpHelper.isTCPNoDelay();
}
@Override
public int getSendBufferSize() {
return tcpHelper.getSendBufferSize();
}
@Override
public int getReceiveBufferSize() {
return tcpHelper.getReceiveBufferSize();
}
@Override
public boolean isTCPKeepAlive() {
return tcpHelper.isTCPKeepAlive();
}
@Override
public boolean isReuseAddress() {
return tcpHelper.isReuseAddress();
}
@Override
public int getSoLinger() {
return tcpHelper.getSoLinger();
}
@Override
public int getTrafficClass() {
return tcpHelper.getTrafficClass();
}
@Override
public int getAcceptBacklog() {
return tcpHelper.getAcceptBacklog();
}
@Override
public NetServer setTCPNoDelay(boolean tcpNoDelay) {
checkListening();
tcpHelper.setTCPNoDelay(tcpNoDelay);
return this;
}
@Override
public NetServer setSendBufferSize(int size) {
checkListening();
tcpHelper.setSendBufferSize(size);
return this;
}
@Override
public NetServer setReceiveBufferSize(int size) {
checkListening();
tcpHelper.setReceiveBufferSize(size);
return this;
}
@Override
public NetServer setTCPKeepAlive(boolean keepAlive) {
checkListening();
tcpHelper.setTCPKeepAlive(keepAlive);
return this;
}
@Override
public NetServer setReuseAddress(boolean reuse) {
checkListening();
tcpHelper.setReuseAddress(reuse);
return this;
}
@Override
public NetServer setSoLinger(int linger) {
checkListening();
tcpHelper.setSoLinger(linger);
return this;
}
@Override
public NetServer setTrafficClass(int trafficClass) {
checkListening();
tcpHelper.setTrafficClass(trafficClass);
return this;
}
@Override
public NetServer setAcceptBacklog(int backlog) {
checkListening();
tcpHelper.setAcceptBacklog(backlog);
return this;
}
@Override
public boolean isSSL() {
return tcpHelper.isSSL();
}
@Override
public String getKeyStorePath() {
return tcpHelper.getKeyStorePath();
}
@Override
public String getKeyStorePassword() {
return tcpHelper.getKeyStorePassword();
}
@Override
public String getTrustStorePath() {
return tcpHelper.getTrustStorePath();
}
@Override
public String getTrustStorePassword() {
return tcpHelper.getTrustStorePassword();
}
@Override
public boolean isClientAuthRequired() {
return tcpHelper.getClientAuth() == TCPSSLHelper.ClientAuth.REQUIRED;
}
@Override
public NetServer setSSL(boolean ssl) {
checkListening();
tcpHelper.setSSL(ssl);
return this;
}
@Override
public NetServer setSSLContext(SSLContext sslContext) {
checkListening();
tcpHelper.setExternalSSLContext(sslContext);
return this;
}
@Override
public NetServer setKeyStorePath(String path) {
checkListening();
tcpHelper.setKeyStorePath(path);
return this;
}
@Override
public NetServer setKeyStorePassword(String pwd) {
checkListening();
tcpHelper.setKeyStorePassword(pwd);
return this;
}
@Override
public NetServer setTrustStorePath(String path) {
checkListening();
tcpHelper.setTrustStorePath(path);
return this;
}
@Override
public NetServer setTrustStorePassword(String pwd) {
checkListening();
tcpHelper.setTrustStorePassword(pwd);
return this;
}
@Override
public NetServer setClientAuthRequired(boolean required) {
checkListening();
tcpHelper.setClientAuthRequired(required);
return this;
}
@Override
public NetServer setUsePooledBuffers(boolean pooledBuffers) {
checkListening();
tcpHelper.setUsePooledBuffers(pooledBuffers);
return this;
}
@Override
public boolean isUsePooledBuffers() {
return tcpHelper.isUsePooledBuffers();
}
private void actualClose(final DefaultContext closeContext, final Handler> done) {
if (id != null) {
async.sharedNetServers().remove(id);
}
for (DefaultNetSocket sock : socketMap.values()) {
sock.close();
}
// We need to reset it since sock.internalClose() above can call into the close handlers of sockets on the same thread
// which can cause context id for the thread to change!
async.setContext(closeContext);
ChannelGroupFuture fut = serverChannelGroup.close();
fut.addListener(new ChannelGroupFutureListener() {
public void operationComplete(ChannelGroupFuture fut) throws Exception {
executeCloseDone(closeContext, done, fut.cause());
}
});
}
private void checkConfigs(DefaultNetServer currentServer, DefaultNetServer newServer) {
//TODO check configs are the same
}
private void executeCloseDone(final DefaultContext closeContext, final Handler> done, final Exception e) {
if (done != null) {
closeContext.execute(new Runnable() {
public void run() {
done.handle(new DefaultFutureResult(e));
}
});
}
}
private void checkListening() {
if (listening) {
throw new IllegalStateException("Can't set property when server is listening");
}
}
private class ServerHandler extends AsyncNetHandler {
public ServerHandler() {
super(DefaultNetServer.this.async, socketMap);
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
final Channel ch = ctx.channel();
EventLoop worker = ch.eventLoop();
//Choose a handler
final HandlerHolder handler = handlerManager.chooseHandler(worker);
if (handler == null) {
//Ignore
return;
}
if (tcpHelper.isSSL()) {
SslHandler sslHandler = ch.pipeline().get(SslHandler.class);
Future fut = sslHandler.handshakeFuture();
fut.addListener(new GenericFutureListener>() {
@Override
public void operationComplete(Future future) throws Exception {
if (future.isSuccess()) {
connected(ch, handler);
} else {
log.error("Client from origin " + ch.remoteAddress() + " failed to connect over ssl");
}
}
});
} else {
connected(ch, handler);
}
}
private void connected(final Channel ch, final HandlerHolder handler) {
handler.context.execute(ch.eventLoop(), new Runnable() {
public void run() {
doConnected(ch, handler);
}
});
}
private void doConnected(Channel ch, HandlerHolder handler) {
DefaultNetSocket sock = new DefaultNetSocket(async, ch, handler.context, tcpHelper, false);
socketMap.put(ch, sock);
handler.handler.handle(sock);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy