package rfx.server.http.websocket; import static io.netty.handler.codec.http.HttpHeaders.setContentLength; import static io.netty.handler.codec.http.HttpHeaders.Names.CONTENT_TYPE; import static io.netty.handler.codec.http.HttpHeaders.Names.HOST; import static io.netty.handler.codec.http.HttpMethod.GET; import static io.netty.handler.codec.http.HttpResponseStatus.BAD_REQUEST; import static io.netty.handler.codec.http.HttpResponseStatus.FORBIDDEN; import static io.netty.handler.codec.http.HttpResponseStatus.NOT_FOUND; import static io.netty.handler.codec.http.HttpResponseStatus.OK; import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; 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.QueryStringDecoder; 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 java.util.HashMap; import java.util.List; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; import rfx.server.http.common.NettyHttpUtil; import rfx.server.util.FileUtils; import rfx.server.util.StringUtil; public class WebSocketServerHandler extends SimpleChannelInboundHandler<Object> { private static final Logger logger = Logger.getLogger(WebSocketServerHandler.class.getName()); static WebSocketChannelManager wsChannelManager = WebSocketChannelManager.get(); //static final SubscribedChannelManager compactStats = new SubscribedChannelManager(SubscribedChannelManager.CHANNEL_NAME_COMPACT_STATS); static Map<String, SubscribedChannelManager> rfQuerySubMap = new HashMap<String, SubscribedChannelManager>(); static { String rfQCompact = StringUtil.join(":", "rfq",SubscribedChannelManager.CHANNEL_COMPACT_STATS); rfQuerySubMap.put(rfQCompact, new SubscribedChannelManager(SubscribedChannelManager.CHANNEL_COMPACT_STATS)); } private static final String WEBSOCKET_PATH = "/websocket"; private WebSocketServerHandshaker handshaker; @Override public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { ctx.flush(); } @Override public void channelRegistered(ChannelHandlerContext ctx) throws Exception { super.channelRegistered(ctx); System.out.println(" channelRegistered "+ctx.channel()); } @Override public void channelUnregistered(ChannelHandlerContext ctx) throws Exception { super.channelUnregistered(ctx); System.out.println(" channelUnregistered, removeWebSocketChannel:"+ctx.channel()); } @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { super.channelActive(ctx); System.out.println(" channelActive "+ctx.channel()); } @Override public void channelInactive(ChannelHandlerContext ctx) throws Exception { super.channelInactive(ctx); System.out.println("channelInactive "+ctx.channel()); wsChannelManager.removeWebSocketChannel(ctx.channel(),false); System.out.println(" channelInactive,channelUnregistered "+ctx.channel()); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { cause.printStackTrace(); ctx.close(); } @Override protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception { if (msg instanceof FullHttpRequest) { handleHttpRequest(ctx, (FullHttpRequest) msg); } else if (msg instanceof WebSocketFrame) { handleWebSocketFrame(ctx, (WebSocketFrame) msg); } } //////////////////////////////// handlers and utils ///////////////////////////// private void handleWebSocketFrame(ChannelHandlerContext ctx, WebSocketFrame frame) { // Check for closing frame if (frame instanceof CloseWebSocketFrame) { handshaker.close(ctx.channel(),(CloseWebSocketFrame) frame.retain()); wsChannelManager.removeWebSocketChannel(ctx.channel(),false); 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())); } // Send the uppercase string back. String request = ((TextWebSocketFrame) frame).text(); if (logger.isLoggable(Level.FINE)) { logger.fine(String.format("%s received %s", ctx.channel(), request)); } ctx.channel().write(new TextWebSocketFrame(request.toUpperCase())); } private void handleHttpRequest(ChannelHandlerContext ctx, FullHttpRequest req) throws Exception { String uri = req.getUri(); QueryStringDecoder qdecoder = new QueryStringDecoder(uri); Map<String, List<String>> params = qdecoder.parameters(); System.out.println("WebSocketServerHandler uri: "+uri); // Handle a bad request. if (!req.getDecoderResult().isSuccess()) { NettyHttpUtil.sendHttpResponse(ctx, req, new DefaultFullHttpResponse(HTTP_1_1,BAD_REQUEST)); return; } // Allow only GET methods. if (req.getMethod() != GET) { NettyHttpUtil.sendHttpResponse(ctx, req, new DefaultFullHttpResponse(HTTP_1_1,FORBIDDEN)); return; } // Send the demo page and favicon.ico if (uri.startsWith("/realtime-monitor")) { realtimeViewHandler(ctx, req); return; } else if ("/favicon.ico".equals(uri)) { NettyHttpUtil.response1pxGifImage(ctx); return; } else if ("/".equals(uri) || "/jsonp".equalsIgnoreCase(uri)){ statisticsHandler(uri, ctx, req, params); return; } // Handshake WebSocketServerHandshakerFactory wsFactory = new WebSocketServerHandshakerFactory(getWebSocketLocation(req), null, false); handshaker = wsFactory.newHandshaker(req); if (handshaker == null) { WebSocketServerHandshakerFactory.sendUnsupportedVersionResponse(ctx.channel()); } else { if(uri.startsWith("/websocket")){ //TODO collect params and make a RfQuery String display = NettyHttpUtil.getParamValue("display", params, SubscribedChannelManager.CHANNEL_COMPACT_STATS); String network = NettyHttpUtil.getParamValue("network", params, "0"); String filter = NettyHttpUtil.getParamValue("filter", params, "impression"); String rfQuery = StringUtil.join(":", "rfq",display,network,filter); try { handshaker.handshake(ctx.channel(), req); //handshaker OK, add channel to list wsChannelManager.addChannel(rfQuery, ctx.channel(),ctx.channel()); System.out.println(" websocketReq = true,channelRegistered "+ctx.channel()); return; } catch (Exception e) { e.printStackTrace(); } } NettyHttpUtil.sendHttpResponse(ctx, req, new DefaultFullHttpResponse(HTTP_1_1,NOT_FOUND)); } } private static String getWebSocketLocation(FullHttpRequest req) { return "ws://" + req.headers().get(HOST) + WEBSOCKET_PATH; } public static void statisticsHandler(String uri, ChannelHandlerContext ctx, FullHttpRequest req, Map<String, List<String>> params){ String callbackFunc = NettyHttpUtil.getParamValue("callback", params); //build the data from redisSubcribeListener.message SubscribedChannelManager compactStats = rfQuerySubMap.get(SubscribedChannelManager.CHANNEL_COMPACT_STATS); byte[] data = NettyHttpUtil.responseAsJsonp(callbackFunc, compactStats.getMessage() ).getBytes(); ByteBuf content = Unpooled.copiedBuffer(data); FullHttpResponse res = new DefaultFullHttpResponse(HTTP_1_1, OK, content); String contentType; if(callbackFunc.isEmpty()){ contentType = "application/json"; } else { contentType = "application/x-javascript; charset=utf-8"; } res.headers().set(CONTENT_TYPE, contentType); setContentLength(res, content.readableBytes()); NettyHttpUtil.sendHttpResponse(ctx, req, res); } void realtimeViewHandler(ChannelHandlerContext ctx, FullHttpRequest req){ byte[] data = "".getBytes(); try { data = FileUtils.readFileAsString("static/realtime-monitor.html").getBytes(); } catch (Exception e) { e.printStackTrace(); } ByteBuf content = Unpooled.copiedBuffer(data); FullHttpResponse res = new DefaultFullHttpResponse(HTTP_1_1, OK, content); res.headers().set(CONTENT_TYPE, "text/html; charset=UTF-8"); setContentLength(res, content.readableBytes()); NettyHttpUtil.sendHttpResponse(ctx, req, res); } }