cn.npt.net.websocket.WebSocketServerHandler.java Source code

Java tutorial

Introduction

Here is the source code for cn.npt.net.websocket.WebSocketServerHandler.java

Source

/*
 * Copyright 2012 The Netty Project
 *
 * The Netty Project licenses this file to you under the Apache License,
 * version 2.0 (the "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at:
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations
 * under the License.
 */
package cn.npt.net.websocket;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;

import org.apache.log4j.Logger;

import cn.npt.fs.CachePoolFactory;
import cn.npt.fs.cache.BaseMemoryCache;
import cn.npt.fs.cache.CachePool;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.websocketx.CloseWebSocketFrame;
import io.netty.handler.codec.http.websocketx.PingWebSocketFrame;
import io.netty.handler.codec.http.websocketx.PongWebSocketFrame;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.handler.codec.http.websocketx.WebSocketFrame;
import io.netty.handler.codec.http.websocketx.WebSocketServerHandshaker;
import io.netty.handler.codec.http.websocketx.WebSocketServerHandshakerFactory;
import io.netty.util.CharsetUtil;
import static io.netty.handler.codec.http.HttpHeaders.Names.*;
import static io.netty.handler.codec.http.HttpMethod.*;
import static io.netty.handler.codec.http.HttpResponseStatus.*;
import static io.netty.handler.codec.http.HttpVersion.*;

/**
 * <br>?,??????,????
 * <br>??websocket??{cmd:getSensorValue,depth:0,timeInterval:1000,sensorIds:[...]}
 * <br>cmd:?getSensorValue,updateSensorValue,getStartTime,getSensorCount
 * <br>depth:0--?;1--BS;2--BS;3--BS
 * <br>timeInterval:,??ms
 * <br>sensorIds:?ID
 * @author Leonardo
 *
 */
public class WebSocketServerHandler extends SimpleChannelInboundHandler<Object> {

    private String webSocketPath;
    private WebSocketServerHandshaker handshaker;
    private Timer timer;
    /**
     * 
     */
    private WebSocketTimerTask timerTask;
    /**
     * ??
     */
    private WebSocketTimerTask1 timerTask1;
    private boolean ssl;

    private static Logger log = Logger.getLogger(WebSocketServerHandler.class);

    public WebSocketServerHandler(String webSocketPath, boolean ssl) {
        this.webSocketPath = webSocketPath;
        this.ssl = ssl;
    }

    @Override
    public void channelRead0(ChannelHandlerContext ctx, Object msg) {
        if (msg instanceof FullHttpRequest) {
            handleHttpRequest(ctx, (FullHttpRequest) msg);
        } else if (msg instanceof WebSocketFrame) {
            handleWebSocketFrame(ctx, (WebSocketFrame) msg);
        }
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) {
        ctx.flush();
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) {
        this.timer.cancel();
        this.timer = null;
        log.info(ctx.channel() + " websocket disconnected!");
        ctx.close();
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) {
        this.timer = new Timer();
        log.info(ctx.channel() + " websocket connected!");
    }

    private void handleHttpRequest(ChannelHandlerContext ctx, FullHttpRequest req) {
        // Handle a bad request.
        if (!req.getDecoderResult().isSuccess()) {
            sendHttpResponse(ctx, req, new DefaultFullHttpResponse(HTTP_1_1, BAD_REQUEST));
            return;
        }

        // Allow only GET methods.
        if (req.getMethod() != GET) {
            sendHttpResponse(ctx, req, new DefaultFullHttpResponse(HTTP_1_1, FORBIDDEN));
            return;
        }

        // Send the demo page and favicon.ico
        if ("/".equals(req.getUri())) {
            ByteBuf content = WebSocketServerIndexPage.getContent(getWebSocketLocation(req));
            FullHttpResponse res = new DefaultFullHttpResponse(HTTP_1_1, OK, content);

            res.headers().set(CONTENT_TYPE, "text/html; charset=UTF-8");
            HttpHeaders.setContentLength(res, content.readableBytes());

            sendHttpResponse(ctx, req, res);
            return;
        }
        if ("/favicon.ico".equals(req.getUri())) {
            FullHttpResponse res = new DefaultFullHttpResponse(HTTP_1_1, NOT_FOUND);
            sendHttpResponse(ctx, req, res);
            return;
        }

        // Handshake
        WebSocketServerHandshakerFactory wsFactory = new WebSocketServerHandshakerFactory(getWebSocketLocation(req),
                null, true);
        handshaker = wsFactory.newHandshaker(req);
        if (handshaker == null) {
            WebSocketServerHandshakerFactory.sendUnsupportedVersionResponse(ctx.channel());
        } else {
            handshaker.handshake(ctx.channel(), req);
        }
    }

    private void handleWebSocketFrame(ChannelHandlerContext ctx, WebSocketFrame frame) {

        // Check for closing frame
        if (frame instanceof CloseWebSocketFrame) {
            handshaker.close(ctx.channel(), (CloseWebSocketFrame) frame.retain());
            return;
        }
        if (frame instanceof PingWebSocketFrame) {
            ctx.channel().write(new PongWebSocketFrame(frame.content().retain()));
            return;
        }
        if (!(frame instanceof TextWebSocketFrame)) {
            throw new UnsupportedOperationException(
                    String.format("%s frame types not supported", frame.getClass().getName()));
        }

        //??{cmd:getSensorValue,depth:0,timeInterval:1000,sensorIds:[...]},?????
        String request = ((TextWebSocketFrame) frame).text();
        JSONObject reqObj = JSON.parseObject(request);
        if (reqObj.containsKey("cmd")) {
            String cmd = reqObj.getString("cmd");
            switch (cmd) {
            case "getStartTime":
                getStartTime(ctx);
                break;
            case "getSensorCount":
                getSensorCount(ctx);
                break;
            case "getSensorValue"://?
                getSensorValue(reqObj, ctx);
                break;
            case "updateSensorValue"://?
                updateSensorValue(reqObj, ctx);
                break;
            case "getCachePoolDepth":
                getCachePoolDepth(reqObj, ctx);
                break;
            case "getSensorHandlers":
                getSensorHandlers(reqObj, ctx);
                break;
            default:
                ctx.writeAndFlush(new TextWebSocketFrame(""));
                break;
            }
        } else {
            log.warn("unknown command");
            ctx.writeAndFlush(new TextWebSocketFrame("?"));
        }

        //ctx.executor().scheduleAtFixedRate(paramRunnable, paramLong1, paramLong2, paramTimeUnit)

    }

    /**
     * 
     * @param ctx
     */
    private void getStartTime(ChannelHandlerContext ctx) {
        JSONObject rs = new JSONObject();
        rs.put("firstCreatedTime", CachePoolFactory.firstCreatedTime.toString());
        ctx.writeAndFlush(new TextWebSocketFrame(rs.toString()));
    }

    /**
     * ??sensor?
     * @param ctx
     */
    private void getSensorCount(ChannelHandlerContext ctx) {
        int count = CachePoolFactory.getSensorCount();
        JSONObject rs = new JSONObject();
        rs.put("sensorCount", count);
        ctx.writeAndFlush(new TextWebSocketFrame(rs.toString()));
    }

    /**
     * <br>??websocket??{depth:0,timeInterval:1000,sensorIds:[...]}
    * <br>depth:0--?;1--BS;2--BS;3--BS
    * <br>timeInterval:,??ms
    * <br>sensorIds:?ID
     * @param reqObj
     * @param ctx
     * @see #updateSensorValue(JSONObject, ChannelHandlerContext)
     */
    private void getSensorValue(JSONObject reqObj, ChannelHandlerContext ctx) {
        if (reqObj.containsKey("timeInterval") && reqObj.containsKey("depth") && reqObj.containsKey("sensorIds")) {
            int timeInterval = reqObj.getIntValue("timeInterval");
            if (timeInterval < 50) {
                ctx.writeAndFlush(
                        new TextWebSocketFrame(",?timeInterval50"));
            } else {
                if (this.timerTask != null) {
                    this.timerTask.cancel();
                }
                if (this.timerTask1 != null) {//
                    this.timerTask1.cancel();
                }
                this.timerTask = new WebSocketTimerTask(ctx, reqObj);
                this.timer.schedule(this.timerTask, 0, timeInterval);
            }
        } else {
            //log.warn("");
            ctx.writeAndFlush(new TextWebSocketFrame(
                    "?,???{cmd:'getSensorValue',depth:0,timeInterval:1000,sensorIds:[...]}"));
        }
    }

    /**
     * ???
     * @param reqObj
     * @param ctx
     * @see #getSensorValue(JSONObject, ChannelHandlerContext)
     */
    private void updateSensorValue(JSONObject reqObj, ChannelHandlerContext ctx) {
        if (reqObj.containsKey("timeInterval") && reqObj.containsKey("depth") && reqObj.containsKey("sensorIds")) {
            int timeInterval = reqObj.getIntValue("timeInterval");
            if (timeInterval < 50) {
                ctx.writeAndFlush(
                        new TextWebSocketFrame(",?timeInterval50"));
            } else {
                if (this.timerTask1 != null) {
                    this.timerTask1.cancel();
                }
                if (this.timerTask != null) {
                    this.timerTask.cancel();
                }
                this.timerTask1 = new WebSocketTimerTask1(ctx, reqObj);
                this.timer.schedule(this.timerTask1, 0, timeInterval);
            }
        } else {
            //log.warn("");
            ctx.writeAndFlush(new TextWebSocketFrame(
                    "?,???{cmd:'getSensorValue',depth:0,timeInterval:1000,sensorIds:[...]}"));
        }
    }

    /**
     * ??value
     * @author Leonardo
     *
     */
    private class WebSocketTimerTask extends TimerTask {
        private ChannelHandlerContext ctx;
        private JSONObject reqObj;
        private JSONArray sensorIds;
        private int depth;
        private final List<CachePool<?>> pools;//??
        private StringBuilder errorInfo;

        public WebSocketTimerTask(ChannelHandlerContext ctx, JSONObject reqObj) {
            this.ctx = ctx;
            this.reqObj = reqObj;
            this.sensorIds = this.reqObj.getJSONArray("sensorIds");
            this.depth = this.reqObj.getIntValue("depth");
            this.pools = new ArrayList<CachePool<?>>();
            this.errorInfo = new StringBuilder();
            for (int i = 0; i < sensorIds.size(); i++) {
                boolean sensorExist = false;
                long sensorId = sensorIds.getLongValue(i);

                for (BaseMemoryCache cachePool : CachePoolFactory.getCachePools().values()) {
                    CachePool<?> cp = cachePool.getCachePool(sensorId, depth);
                    if (cp != null) {
                        this.pools.add(cp);
                        sensorExist = true;
                        break;
                    }
                }
                if (!sensorExist) {
                    if (depth == 0) {
                        this.errorInfo.append("sensorId:").append(sensorId).append(" ?\n");
                    } else {//?(?sensorId)
                        this.errorInfo.append("sensorId:").append(sensorId)
                                .append(" ??").append(depth)
                                .append("\n");
                    }
                }
            }
        }

        @Override
        public void run() {
            if (this.errorInfo.length() == 0) {//?
                JSONObject rs = new JSONObject();

                for (CachePool<?> pool : this.pools) {
                    rs.putAll(pool.currentV2JSON());
                }
                ctx.channel().writeAndFlush(new TextWebSocketFrame(rs.toJSONString()));
            } else {
                ctx.channel().writeAndFlush(new TextWebSocketFrame(this.errorInfo.toString()));
                this.cancel();
            }
        }
    }

    /**
     * ?value???
     * @author Leonardo
     *
     */
    private class WebSocketTimerTask1 extends TimerTask {
        private ChannelHandlerContext ctx;
        private JSONObject reqObj;
        private JSONArray sensorIds;
        private int depth;
        private final List<CachePool<?>> pools;//??
        /**
         * ?????
         */
        private List<Integer> sensorPreviousIndexs;
        private StringBuilder errorInfo;

        private SimpleDateFormat sdf;

        public WebSocketTimerTask1(ChannelHandlerContext ctx, JSONObject reqObj) {
            this.ctx = ctx;
            this.reqObj = reqObj;
            this.sensorIds = this.reqObj.getJSONArray("sensorIds");
            this.depth = this.reqObj.getIntValue("depth");
            this.pools = new ArrayList<CachePool<?>>();
            this.sensorPreviousIndexs = new ArrayList<Integer>();
            this.errorInfo = new StringBuilder();
            this.sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            for (int i = 0; i < sensorIds.size(); i++) {
                boolean sensorExist = false;
                long sensorId = sensorIds.getLongValue(i);

                for (BaseMemoryCache cachePool : CachePoolFactory.getCachePools().values()) {
                    CachePool<?> cp = cachePool.getCachePool(sensorId, depth);
                    if (cp != null) {
                        this.pools.add(cp);
                        this.sensorPreviousIndexs.add(cp.getIndex());
                        sensorExist = true;
                        break;
                    }
                }
                if (!sensorExist) {
                    if (depth == 0) {
                        this.errorInfo.append("sensorId:").append(sensorId).append(" ?\n");
                    } else {//?(?sensorId)
                        this.errorInfo.append("sensorId:").append(sensorId)
                                .append(" ??").append(depth)
                                .append("\n");
                    }
                }
            }
        }

        @Override
        public void run() {
            if (this.errorInfo.length() == 0) {//?
                JSONObject rs = new JSONObject();

                for (int i = 0; i < this.pools.size(); i++) {
                    CachePool<?> pool = this.pools.get(i);
                    if (!this.sensorPreviousIndexs.get(i).equals(pool.getIndex())) {
                        this.sensorPreviousIndexs.set(i, pool.getIndex());
                        rs.putAll(pool.currentV2JSON());
                        rs.put("time", this.sdf.format(new Date(pool.getCurrentTime())));
                    }
                }
                if (!rs.isEmpty()) {
                    ctx.channel().writeAndFlush(new TextWebSocketFrame(rs.toJSONString()));
                }
            } else {
                ctx.channel().writeAndFlush(new TextWebSocketFrame(this.errorInfo.toString()));
                this.cancel();
            }
        }
    }

    private void getCachePoolDepth(JSONObject reqObj, ChannelHandlerContext ctx) {
        Long sensorId = reqObj.getLong("sensorId");
        int depth = CachePoolFactory.getCachePool(sensorId).getDepth(sensorId);
        ctx.writeAndFlush(new TextWebSocketFrame("depth:" + depth));
    }

    private void getSensorHandlers(JSONObject reqObj, ChannelHandlerContext ctx) {

        ctx.writeAndFlush(new TextWebSocketFrame("?"));
    }

    private static void sendHttpResponse(ChannelHandlerContext ctx, FullHttpRequest req, FullHttpResponse res) {
        // Generate an error page if response getStatus code is not OK (200).
        if (res.getStatus().code() != 200) {
            ByteBuf buf = Unpooled.copiedBuffer(res.getStatus().toString(), CharsetUtil.UTF_8);
            res.content().writeBytes(buf);
            buf.release();
            HttpHeaders.setContentLength(res, res.content().readableBytes());
        }

        // Send the response and close the connection if necessary.
        ChannelFuture f = ctx.channel().writeAndFlush(res);
        if (!HttpHeaders.isKeepAlive(req) || res.getStatus().code() != 200) {
            f.addListener(ChannelFutureListener.CLOSE);
        }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        log.error(cause.getMessage());
        cause.printStackTrace();
        ctx.close();
    }

    private String getWebSocketLocation(FullHttpRequest req) {
        String location = req.headers().get(HOST) + this.webSocketPath;
        if (this.ssl) {
            return "wss://" + location;
        } else {
            return "ws://" + location;
        }
    }
}