Java tutorial
/* * Copyright (c) 2012 Jeremy Goetsch * * Licensed 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 com.jgoetsch.eventtrader.source; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.net.InetSocketAddress; import java.net.URI; import java.net.URISyntaxException; import java.util.concurrent.Executors; import java.util.concurrent.ThreadFactory; import java.util.concurrent.atomic.AtomicInteger; import javax.annotation.PreDestroy; import org.jboss.netty.bootstrap.ClientBootstrap; import org.jboss.netty.channel.Channel; import org.jboss.netty.channel.ChannelFuture; import org.jboss.netty.channel.ChannelHandlerContext; import org.jboss.netty.channel.ChannelPipeline; import org.jboss.netty.channel.ChannelPipelineFactory; import org.jboss.netty.channel.ChannelStateEvent; import org.jboss.netty.channel.Channels; import org.jboss.netty.channel.ExceptionEvent; import org.jboss.netty.channel.MessageEvent; import org.jboss.netty.channel.SimpleChannelUpstreamHandler; import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory; import org.jboss.netty.handler.codec.http.HttpRequestEncoder; import org.jboss.netty.handler.codec.http.HttpResponse; import org.jboss.netty.handler.codec.http.HttpResponseDecoder; import org.jboss.netty.handler.codec.http.websocketx.TextWebSocketFrame; import org.jboss.netty.handler.codec.http.websocketx.WebSocketClientHandshaker; import org.jboss.netty.handler.codec.http.websocketx.WebSocketClientHandshakerFactory; import org.jboss.netty.handler.codec.http.websocketx.WebSocketVersion; import org.jboss.netty.util.ThreadNameDeterminer; import org.jboss.netty.util.ThreadRenamingRunnable; import org.json.simple.JSONArray; import org.json.simple.JSONObject; import org.json.simple.JSONValue; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.jgoetsch.eventtrader.source.parser.structured.StructuredMsgParser; /** * Source for streaming messages from a Socket.IO server * using WebSockets as the transport. * * As of March, 2012 this is the preferred source for Profiding alerts. */ public class SocketIOWebSocketMsgSource extends UrlBasedMsgSource { private Logger log = LoggerFactory.getLogger(getClass()); private StructuredMsgParser msgParser; private ClientBootstrap bootstrap; private Channel ch; protected String getTokenUrl() throws IOException, URISyntaxException { URI base = new URI(getUrl()); BufferedReader tokenReader = new BufferedReader( new InputStreamReader(new URI("http", base.getUserInfo(), base.getHost(), base.getPort(), base.getPath(), base.getQuery(), base.getFragment()).toURL().openStream())); String token = tokenReader.readLine(); tokenReader.close(); String r[] = token.split(":"); String comp[] = getUrl().split("\\?"); if (!comp[0].endsWith("/")) comp[0] += '/'; return comp[0] + "websocket/" + r[0] + (comp.length > 0 ? "?" + comp[1] : ""); } @Override protected void receiveMsgs() { final String baseThreadName = Thread.currentThread().getName(); ThreadRenamingRunnable.setThreadNameDeterminer(ThreadNameDeterminer.CURRENT); ThreadFactory threadFactory = new ThreadFactory() { private AtomicInteger n = new AtomicInteger(); public Thread newThread(Runnable r) { return new Thread(r, baseThreadName + "-w-" + n.incrementAndGet()); } }; bootstrap = new ClientBootstrap(new NioClientSocketChannelFactory( Executors.newCachedThreadPool(threadFactory), Executors.newCachedThreadPool(threadFactory))); try { URI url = new URI(getTokenUrl()); final WebSocketClientHandshaker handshaker = new WebSocketClientHandshakerFactory().newHandshaker(url, WebSocketVersion.V13, null, false, null); bootstrap.setPipelineFactory(new ChannelPipelineFactory() { public ChannelPipeline getPipeline() throws Exception { ChannelPipeline pipeline = Channels.pipeline(); pipeline.addLast("decoder", new HttpResponseDecoder()); pipeline.addLast("encoder", new HttpRequestEncoder()); pipeline.addLast("ws-handler", new WebSocketClientHandler(handshaker)); pipeline.addLast("sio-handler", new SocketIOClientHandler()); return pipeline; } }); ChannelFuture future = bootstrap .connect(new InetSocketAddress(url.getHost(), url.getPort() == -1 ? 80 : url.getPort())); future.syncUninterruptibly(); ch = future.getChannel(); handshaker.handshake(ch).syncUninterruptibly(); ch.getCloseFuture().awaitUninterruptibly(); } catch (URISyntaxException use) { log.error("Invalid URL: {}", getUrl(), use); } catch (Exception e) { log.error("Error getting token", e); } finally { if (ch != null) ch.close(); bootstrap.releaseExternalResources(); } } @PreDestroy public void shutdown() { if (ch != null) ch.close(); joinMainThread(2000); } private class WebSocketClientHandler extends SimpleChannelUpstreamHandler { private final WebSocketClientHandshaker handshaker; public WebSocketClientHandler(WebSocketClientHandshaker handshaker) { this.handshaker = handshaker; } @Override public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception { if (!handshaker.isHandshakeComplete()) { handshaker.finishHandshake(ctx.getChannel(), (HttpResponse) e.getMessage()); log.info("Connected to websocket host {}", ctx.getChannel().getRemoteAddress()); return; } super.messageReceived(ctx, e); } @Override public void channelClosed(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception { log.info("Websocket connection to {} closed", ctx.getChannel().getRemoteAddress()); } @Override public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception { log.error("Websocket exception", e.getCause()); e.getChannel().close(); } } private class SocketIOClientHandler extends SimpleChannelUpstreamHandler { @Override public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception { if (e.getMessage() instanceof TextWebSocketFrame) { TextWebSocketFrame message = (TextWebSocketFrame) e.getMessage(); if (message.getText().equals("2::")) { log.trace("Heartbeat received"); ch.write(new TextWebSocketFrame("2::")); } else { log.debug("Received: {}", message.getText()); try { int idx = message.getText().indexOf('{'); if (idx >= 0) { JSONObject json = (JSONObject) JSONValue.parse(message.getText().substring(idx)); Object data = json.get("args"); if (data != null && data.getClass().isAssignableFrom(JSONArray.class)) { data = ((JSONArray) json.get("args")).get(0); if (data.getClass().isAssignableFrom(JSONObject.class)) { if (!msgParser.parseData((String) json.get("name"), (JSONObject) data, SocketIOWebSocketMsgSource.this)) { ctx.getChannel().close(); } } } } } catch (Exception ex) { log.error("Exception occured parsing message: " + message.getText(), ex); } } } super.messageReceived(ctx, e); } } public void setMsgParser(StructuredMsgParser msgParser) { this.msgParser = msgParser; } public StructuredMsgParser getMsgParser() { return msgParser; } }