divconq.api.HyperSession.java Source code

Java tutorial

Introduction

Here is the source code for divconq.api.HyperSession.java

Source

/* ************************************************************************
#
#  DivConq
#
#  http://divconq.com/
#
#  Copyright:
#    Copyright 2014 eTimeline, LLC. All rights reserved.
#
#  License:
#    See the license.txt file in the project's top-level directory for details.
#
#  Authors:
#    * Andy White
#
************************************************************************ */
package divconq.api;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.http.DefaultHttpHeaders;
import io.netty.handler.codec.http.HttpClientCodec;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpRequestEncoder;
import io.netty.handler.codec.http.HttpResponseDecoder;
import io.netty.handler.codec.http.websocketx.WebSocketClientHandshaker;
import io.netty.handler.codec.http.websocketx.WebSocketClientHandshakerFactory;
import io.netty.handler.codec.http.websocketx.WebSocketClientProtocolHandler;
import io.netty.handler.codec.http.websocketx.WebSocketVersion;
import divconq.net.ssl.SslHandler;
import io.netty.util.concurrent.Future;

import java.nio.channels.ScatteringByteChannel;
import java.nio.channels.WritableByteChannel;
import java.util.HashMap;
import java.util.concurrent.atomic.AtomicReference;

import divconq.api.ClientInfo.ConnectorKind;
import divconq.api.internal.ApiSslContextFactory;
import divconq.api.internal.ClientHandler;
import divconq.api.internal.DownloadHandler;
import divconq.api.internal.UploadPutHandler;
import divconq.bus.Message;
import divconq.hub.Hub;
import divconq.lang.op.OperationCallback;
import divconq.lang.op.OperationResult;
import divconq.lang.op.UserContext;
import divconq.work.ISynchronousWork;
import divconq.work.TaskRun;
import divconq.xml.XElement;

public class HyperSession extends ApiSession {
    protected ClientInfo info = null;
    protected ApiSslContextFactory sslfac = null;

    protected ClientHandler handler = null;

    protected HashMap<String, UploadPutHandler> uploadstreams = new HashMap<>();
    protected HashMap<String, DownloadHandler> downloadstreams = new HashMap<>();

    public ClientInfo getInfo() {
        return this.info;
    }

    public ApiSslContextFactory getSsl() {
        return this.sslfac;
    }

    @Override
    public void init(XElement config) {
        // run only once even if call multiple times
        if (this.info != null)
            return;

        if (config == null)
            return;

        this.info = new ClientInfo();
        this.info.loadConfig(config);
        this.info.kind = ConnectorKind.Http;

        this.sslfac = new ApiSslContextFactory();
        this.sslfac.init(config);

        this.user = UserContext.allocateGuest();
    }

    public OperationResult connect() {
        OperationResult or = new OperationResult();

        // only ever one connection for now
        if (this.handler != null)
            return or;

        this.handler = new ClientHandler(this, this.info);

        if (this.info.getKind() == ConnectorKind.WebSocket)
            this.allocateWsChannel(this.handler, or);
        else
            this.allocateHttpChannel(this.handler, or);

        return or;
    }

    public Channel allocateHttpChannel(final ChannelHandler handler, OperationResult or) {
        final AtomicReference<Future<Channel>> sslready = new AtomicReference<>();

        Bootstrap b = new Bootstrap();

        b.group(Hub.instance.getEventLoopGroup()).channel(NioSocketChannel.class)
                .option(ChannelOption.ALLOCATOR, Hub.instance.getBufferAllocator())
                .handler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    public void initChannel(SocketChannel ch) throws Exception {
                        ChannelPipeline pipeline = ch.pipeline();

                        if (HyperSession.this.info.isSecurel()) {
                            SslHandler sh = new SslHandler(HyperSession.this.sslfac.getClientEngine());
                            sslready.set(sh.handshakeFuture());
                            pipeline.addLast("ssl", sh);
                        }

                        pipeline.addLast("decoder", new HttpResponseDecoder());
                        pipeline.addLast("encoder", new HttpRequestEncoder());

                        // TODO maybe
                        //pipeline.addLast("deflater", new HttpContentCompressor());

                        pipeline.addLast("handler", handler);
                    }
                });

        or.info("Web Client connecting");

        try {
            // must wait here to make sure we don't release connectLock too soon
            // we want channel init (above) to complete before we try connect again
            ChannelFuture f = b.connect(this.info.getAddress()).sync();

            if (!f.isSuccess()) {
                or.error(1, "Web Client unable to successfully connect: " + f.cause());
            }

            // it has appeared that sometimes we "overshoot" the ssl handshake in code - to prevent
            // that lets wait for the handshake to be done for sure
            if (sslready.get() != null) {
                Future<Channel> sf = sslready.get().sync();

                if (!sf.isSuccess()) {
                    or.error(1, "Web Client unable to securely connect: " + sf.cause());
                }
            }

            if (handler instanceof ClientHandler)
                ((ClientHandler) handler).waitConnect();

            return f.channel();
        } catch (InterruptedException x) {
            or.error(1, "Web Client interrupted while connecting: " + x);
        } catch (Exception x) {
            or.error(1, "Web Client unable to connect: " + x);
        }

        return null;
    }

    public Channel allocateWsChannel(final ChannelHandler handler, OperationResult or) {
        final AtomicReference<Future<Channel>> sslready = new AtomicReference<>();

        Bootstrap b = new Bootstrap();

        b.group(Hub.instance.getEventLoopGroup()).option(ChannelOption.ALLOCATOR, Hub.instance.getBufferAllocator())
                .channel(NioSocketChannel.class).handler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    public void initChannel(SocketChannel ch) throws Exception {
                        HttpHeaders customHeaders = new DefaultHttpHeaders();
                        customHeaders.add("x-DivConq-Mode", Hub.instance.getResources().getMode());

                        WebSocketClientHandshaker handshaker = WebSocketClientHandshakerFactory.newHandshaker(
                                HyperSession.this.info.getUri(), WebSocketVersion.V13, null, false, customHeaders);

                        ChannelPipeline pipeline = ch.pipeline();

                        if (HyperSession.this.info.isSecurel()) {
                            SslHandler sh = new SslHandler(HyperSession.this.sslfac.getClientEngine());
                            sslready.set(sh.handshakeFuture());
                            pipeline.addLast("ssl", sh);
                        }

                        pipeline.addLast("http-codec", new HttpClientCodec());
                        pipeline.addLast("aggregator", new HttpObjectAggregator(65536));
                        pipeline.addLast("ws-handler", new WebSocketClientProtocolHandler(handshaker));

                        pipeline.addLast("handler", handler);

                        /*
                        pipeline.addLast("handler", new SimpleChannelInboundHandler<Object>() {
                            
                        @Override
                        protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
                         System.out.println("read: " + msg);
                        }
                            
                            
                        @Override
                        public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
                          super.userEventTriggered(ctx, evt);
                              
                          Logger.debug("ue: " + evt);
                        }
                        });
                        */
                    }
                });

        or.info("Web Client connecting");

        try {
            // must wait here to make sure we don't release connectLock too soon
            // we want channel init (above) to complete before we try connect again
            ChannelFuture f = b.connect(this.info.getAddress()).sync();

            if (!f.isSuccess()) {
                or.error(1, "Web Client unable to successfully connect: " + f.cause());
            }

            // it has appeared that sometimes we "overshoot" the ssl handshake in code - to prevent
            // that lets wait for the handshake to be done for sure
            if (sslready.get() != null) {
                Future<Channel> sf = sslready.get().sync();

                if (!sf.isSuccess()) {
                    or.error(1, "Web Client unable to securely connect: " + sf.cause());
                }
            }

            if (handler instanceof ClientHandler)
                ((ClientHandler) handler).waitConnect();

            return f.channel();
        } catch (InterruptedException x) {
            or.error(1, "Web Client interrupted while connecting: " + x);
        } catch (Exception x) {
            or.error(1, "Web Client unable to connect: " + x);
        }

        return null;
    }

    @Override
    public void stopped() {
        if (this.handler != null)
            this.handler.close();

        this.replies.forgetReplyAll();
    }

    @Override
    public void receiveMessage(final Message msg) {
        // throw this work into another thread so socket reader can go back to work      
        Hub.instance.getWorkPool().submit(new ISynchronousWork() {
            @Override
            public void run(TaskRun task) {
                String to = msg.getFieldAsString("Service");

                if ("Replies".equals(to))
                    HyperSession.this.replies.handle(msg);
                else
                    HyperSession.super.receiveMessage(msg);
            }
        });
    }

    @Override
    public void sendForgetMessage(Message msg) {
        msg.setField("RespondTag", "SendForget");

        if (this.connect().hasErrors())
            return;

        this.handler.send(msg);
    }

    @Override
    public void sendMessage(final Message msg, final ServiceResult callback) {
        OperationResult or = this.connect();

        if (or.hasErrors()) {
            callback.complete();
            return;
        }

        callback.setSession(this);

        this.replies.registerForReplySerial(msg, callback);

        this.handler.send(msg);
    }

    @Override
    public void abortStream(String channelid) {
        UploadPutHandler uphandler = this.uploadstreams.get(channelid);

        if (uphandler != null)
            uphandler.closeDest();

        DownloadHandler dhandler = this.downloadstreams.get(channelid);

        if (dhandler != null)
            dhandler.closeSource();
    }

    @Override
    public void sendStream(ScatteringByteChannel in, long size, long offset, String chanid,
            OperationCallback callback) {
        // we also have UploadMultipartPostHandler...much slower
        UploadPutHandler uphandler = new UploadPutHandler();

        this.uploadstreams.put(chanid, uphandler);

        uphandler.start(this, in, chanid, this.handler.getCookies(), size, offset, callback);
    }

    @Override
    public void receiveStream(WritableByteChannel out, long size, long offset, String chanid,
            OperationCallback callback) {
        DownloadHandler dhandler = new DownloadHandler();

        this.downloadstreams.put(chanid, dhandler);

        dhandler.start(this, out, chanid, this.handler.getCookies(), size, offset, callback);
    }

    @Override
    public void freeDataChannel(String channelid, OperationCallback callback) {
        this.uploadstreams.remove(channelid);
        this.downloadstreams.remove(channelid);

        super.freeDataChannel(channelid, callback);
    }
}