divconq.api.internal.ClientHandler.java Source code

Java tutorial

Introduction

Here is the source code for divconq.api.internal.ClientHandler.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.internal;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Semaphore;

import divconq.api.ClientInfo;
import divconq.api.ApiSession;
import divconq.api.ClientInfo.ConnectorKind;
import divconq.bus.Message;
import divconq.bus.MessageUtil;
import divconq.lang.Memory;
import divconq.lang.op.FuncResult;
import divconq.log.Logger;
import divconq.struct.CompositeParser;
import divconq.struct.CompositeStruct;
import divconq.struct.RecordStruct;
import divconq.web.HttpBodyRequestDecoder;
import divconq.web.IBodyCallback;
import divconq.web.IContentDecoder;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.ClientCookieEncoder;
import io.netty.handler.codec.http.Cookie;
import io.netty.handler.codec.http.CookieDecoder;
import io.netty.handler.codec.http.DefaultFullHttpRequest;
import io.netty.handler.codec.http.HttpContent;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpHeaders.Names;
import io.netty.handler.codec.http.HttpMethod;
import io.netty.handler.codec.http.HttpObject;
import io.netty.handler.codec.http.HttpResponse;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.handler.codec.http.websocketx.WebSocketClientProtocolHandler;
import io.netty.handler.codec.http.websocketx.WebSocketFrame;
import io.netty.util.CharsetUtil;

public class ClientHandler extends SimpleChannelInboundHandler<Object> {
    //protected WebSocketClientHandshaker handshaker = null;
    protected ClientInfo info = null;
    protected Channel chan = null;
    protected ApiSession session = null;
    protected Map<String, Cookie> cookies = new HashMap<>();
    protected IContentDecoder decoder = null;
    protected Semaphore wsready = new Semaphore(0);

    public void setChannel(Channel v) {
        this.chan = v;
    }

    public Map<String, Cookie> getCookies() {
        return this.cookies;
    }

    public ClientHandler(ApiSession session, ClientInfo info) {
        this.session = session;
        this.info = info;
    }

    public void send(Message msg) {
        Logger.debug("Sending message: " + msg);

        try {
            if (this.chan != null) {
                if (this.info.getKind() == ConnectorKind.WebSocket)
                    this.chan.writeAndFlush(new TextWebSocketFrame(msg.toString()));
                else {
                    DefaultFullHttpRequest req = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST,
                            this.info.getPath());

                    req.headers().set(Names.HOST, this.info.getHost());
                    req.headers().set(Names.USER_AGENT, "DivConq HyperAPI Client 1.0");
                    req.headers().set(Names.CONNECTION, HttpHeaders.Values.KEEP_ALIVE);
                    req.headers().set(Names.CONTENT_ENCODING, "UTF-8");
                    req.headers().set(Names.CONTENT_TYPE, "application/json; charset=utf-8");
                    req.headers().set(Names.COOKIE, ClientCookieEncoder.encode(this.cookies.values()));

                    // TODO make more efficient - UTF8 encode directly to buffer
                    ByteBuf buf = Unpooled.copiedBuffer(msg.toString(), CharsetUtil.UTF_8);
                    int clen = buf.readableBytes();
                    req.content().writeBytes(buf);
                    buf.release();

                    // Add 'Content-Length' header only for a keep-alive connection.
                    req.headers().set(Names.CONTENT_LENGTH, clen);

                    this.chan.writeAndFlush(req);
                }
            }
        } catch (Exception x) {
            Logger.error("Send HTTP Message error: " + x);
        }
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        this.chan = ctx.channel();

        Logger.debug("ca");

        /*
        if (this.info.getKind() == ConnectorKind.WebSocket) {
             HttpHeaders customHeaders = new DefaultHttpHeaders();
             customHeaders.add("x-DivConq-Mode", Hub.instance.getResources().getMode());
                 
             this.handshaker = WebSocketClientHandshakerFactory.newHandshaker(this.info.getUri(), WebSocketVersion.V13, null, false, customHeaders);
                 
           this.handshaker.handshake(this.chan);
         }
         */
    }

    @Override
    public void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
        Logger.debug("Got object: " + msg);

        /*
        if ((this.handshaker != null) && !this.handshaker.isHandshakeComplete()) {
           HttpResponse httpres = (HttpResponse) msg;             
               
            DefaultFullHttpResponse freq = new DefaultFullHttpResponse(httpres.getProtocolVersion(), httpres.getStatus());
                
            freq.headers().add(httpres.headers());
                
            this.handshaker.finishHandshake(ctx.channel(), freq);
               
            Logger.info("Web Client connected!");            
                
            return;
        }
        */

        if (msg instanceof HttpObject)
            this.handleHttpRequest(ctx, (HttpObject) msg);
        else if (msg instanceof WebSocketFrame)
            this.handleWebSocketFrame(ctx, (WebSocketFrame) msg);
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        Logger.info("Web Client disconnected!");

        this.session.stopped();
    }

    public void handleHttpRequest(ChannelHandlerContext ctx, HttpObject obj) throws Exception {
        Logger.debug("got http message " + obj);

        if (obj instanceof HttpContent) {
            if (this.decoder == null) {
                Logger.error("Got chunk before getting headers!");
                return;
            }

            this.decoder.offer((HttpContent) obj);
            return;
        }

        if (!(obj instanceof HttpResponse)) {
            Logger.error("Got unknown instead of headers!");
            return;
        }

        HttpResponse resp = (HttpResponse) obj;

        // keep the cookies - especially Session!
        List<String> cookies = resp.headers().getAll(Names.SET_COOKIE);

        for (String cookie : cookies) {
            Set<Cookie> cset = CookieDecoder.decode(cookie);

            for (Cookie c : cset)
                this.cookies.put(c.getName(), c);
        }

        this.decoder = new HttpBodyRequestDecoder(4096 * 1024, new IBodyCallback() {
            @Override
            public void ready(Memory mem) {
                // if response is empty ignore
                if (mem.getLength() == 0)
                    return;

                FuncResult<CompositeStruct> pres = CompositeParser.parseJson(mem);

                if (pres.hasErrors()) {
                    Logger.error("Error parsing response JSON!");
                    return;
                }

                CompositeStruct croot = pres.getResult();

                if ((croot == null) || !(croot instanceof RecordStruct)) {
                    Logger.error("Error parsing response JSON!");
                    return;
                }

                ClientHandler.this.session.receiveMessage(MessageUtil.fromRecord((RecordStruct) croot));
            }

            @Override
            public void fail() {
                Logger.error("Failure processing http response");
            }
        });
    }

    public void handleWebSocketFrame(ChannelHandlerContext ctx, WebSocketFrame frame) {
        Channel ch = ctx.channel();

        Logger.debug("ws1: " + frame);

        if (frame instanceof TextWebSocketFrame) {
            String data = ((TextWebSocketFrame) frame).text();

            Logger.debug("Web client received message: " + data);

            FuncResult<CompositeStruct> res = CompositeParser.parseJson(data);

            if (res.hasErrors()) {
                Logger.error("Web client got a bad message: " + res.getMessage());
                ch.close(); // don't stay with bad messages
                return;
            }

            this.session.receiveMessage(MessageUtil.fromRecord((RecordStruct) res.getResult()));
            return;
        }

        Logger.error("unhandled frame type");

        // unhandled frame type drop connection
        ch.close();
    }

    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        super.userEventTriggered(ctx, evt);

        Logger.debug("ue: " + evt);

        if (evt == WebSocketClientProtocolHandler.ClientHandshakeStateEvent.HANDSHAKE_COMPLETE) {
            Logger.debug("handshake done");
            //this.handshaker = null;
            this.wsready.release();
        }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        Logger.error("Error with api session channel: " + cause);
        ctx.close();
    }

    public void close() {
        try {
            if (this.chan != null)
                this.chan.close().await(2000);
        } catch (InterruptedException x) {
            // ignore 
        }
    }

    public void waitConnect() {
        if (this.info.getKind() == ConnectorKind.WebSocket)
            try {
                this.wsready.acquire();
            } catch (InterruptedException x) {
            }
    }
}