io.jsync.net.impl.DefaultNetServer.java Source code

Java tutorial

Introduction

Here is the source code for io.jsync.net.impl.DefaultNetServer.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.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 <a href="http://tfox.org">Tim Fox</a>
 */
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<Channel, DefaultNetSocket> socketMap = new ConcurrentHashMap<Channel, DefaultNetSocket>();
    private final AsyncEventLoopGroup availableWorkers = new AsyncEventLoopGroup();
    private final HandlerManager<NetSocket> handlerManager = new HandlerManager<>(availableWorkers);
    private Handler<NetSocket> 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<Runnable> 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<NetSocket> 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<AsyncResult<NetServer>> 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<AsyncResult<NetServer>> 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<Channel>() {
                    @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<NetServer>(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<NetServer> res;
                        if (actualServer.bindFuture.isSuccess()) {
                            res = new DefaultFutureResult<NetServer>(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<AsyncResult<Void>> 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<AsyncResult<Void>> 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<AsyncResult<Void>> done,
            final Exception e) {
        if (done != null) {
            closeContext.execute(new Runnable() {
                public void run() {
                    done.handle(new DefaultFutureResult<Void>(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<NetSocket> handler = handlerManager.chooseHandler(worker);
            if (handler == null) {
                //Ignore
                return;
            }

            if (tcpHelper.isSSL()) {
                SslHandler sslHandler = ch.pipeline().get(SslHandler.class);

                Future<Channel> fut = sslHandler.handshakeFuture();
                fut.addListener(new GenericFutureListener<Future<Channel>>() {
                    @Override
                    public void operationComplete(Future<Channel> 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<NetSocket> handler) {
            handler.context.execute(ch.eventLoop(), new Runnable() {
                public void run() {
                    doConnected(ch, handler);
                }
            });
        }

        private void doConnected(Channel ch, HandlerHolder<NetSocket> handler) {
            DefaultNetSocket sock = new DefaultNetSocket(async, ch, handler.context, tcpHelper, false);
            socketMap.put(ch, sock);
            handler.handler.handle(sock);
        }
    }
}