package com.bitso.websockets; import java.net.URI; import java.net.URISyntaxException; import java.util.Observable; import javax.net.ssl.SSLException; import com.bitso.exceptions.BitsoWebSocketException; import io.netty.bootstrap.Bootstrap; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelPipeline; import io.netty.channel.ChannelPromise; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; 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.FullHttpResponse; import io.netty.handler.codec.http.HttpClientCodec; import io.netty.handler.codec.http.HttpObjectAggregator; import io.netty.handler.codec.http.websocketx.CloseWebSocketFrame; import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; import io.netty.handler.codec.http.websocketx.WebSocketClientHandshaker; import io.netty.handler.codec.http.websocketx.WebSocketClientHandshakerFactory; import io.netty.handler.codec.http.websocketx.WebSocketFrame; import io.netty.handler.codec.http.websocketx.WebSocketVersion; import io.netty.handler.ssl.SslContext; import io.netty.handler.ssl.SslContextBuilder; import io.netty.handler.ssl.util.InsecureTrustManagerFactory; import io.netty.util.CharsetUtil; public class BitsoWebSocket extends Observable{ private final String URL = "wss://ws.bitso.com"; private final int PORT = 443; private URI mUri; private SslContext mSslContext; private Channel mChannel; private EventLoopGroup mGroup; private String mMessageReceived; private Boolean mConnected; public BitsoWebSocket() throws SSLException, URISyntaxException{ mUri = new URI(URL); mSslContext = SslContextBuilder.forClient(). trustManager(InsecureTrustManagerFactory.INSTANCE).build(); mGroup = new NioEventLoopGroup(); mMessageReceived = ""; mConnected = Boolean.FALSE; } public void setConnected(Boolean connected){ mConnected = connected; setChanged(); notifyObservers(mConnected); } public void setMessageReceived(String messageReceived){ mMessageReceived = messageReceived; setChanged(); notifyObservers(mMessageReceived); } public void openConnection() throws InterruptedException{ Bootstrap bootstrap = new Bootstrap(); final WebSocketClientHandler handler = new WebSocketClientHandler( WebSocketClientHandshakerFactory.newHandshaker( mUri, WebSocketVersion.V08, null, false, new DefaultHttpHeaders())); bootstrap.group(mGroup) .channel(NioSocketChannel.class) .handler(new ChannelInitializer<SocketChannel>() { @Override public void initChannel(SocketChannel socketChannel){ ChannelPipeline channelPipeline = socketChannel.pipeline(); channelPipeline.addLast(mSslContext.newHandler( socketChannel.alloc(), mUri.getHost(), PORT)); channelPipeline.addLast(new HttpClientCodec(), new HttpObjectAggregator(8192), handler); } }); mChannel = bootstrap.connect(mUri.getHost(), PORT).sync().channel(); handler.handshakeFuture().sync(); setConnected(Boolean.TRUE); } public void subscribeBitsoChannel(String channel){ if(mConnected){ String frameMessage = "{ \"action\": \"subscribe\", \"book\": \"btc_mxn\", \"type\": \"" + channel + "\" }"; mChannel.writeAndFlush(new TextWebSocketFrame(frameMessage)); }else{ String message = "Subscription to any channel is not possible while web socket is not connected"; throw new BitsoWebSocketException(message); } } public void closeConnection() throws InterruptedException{ mChannel.writeAndFlush(new CloseWebSocketFrame()); mChannel.closeFuture().sync(); mGroup.shutdownGracefully(); } public class WebSocketClientHandler extends ChannelInboundHandlerAdapter { private final WebSocketClientHandshaker mHandshaker; private ChannelPromise mHandshakeFuture; public WebSocketClientHandler(WebSocketClientHandshaker handshaker) { mHandshaker = handshaker; } public ChannelFuture handshakeFuture() { return mHandshakeFuture; } @Override public void handlerAdded(ChannelHandlerContext ctx) throws Exception { mHandshakeFuture = ctx.newPromise(); } @Override public void channelActive(ChannelHandlerContext ctx){ mHandshaker.handshake(ctx.channel()); } @Override public void channelInactive(ChannelHandlerContext ctx) throws Exception {} @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { Channel channel = ctx.channel(); if(!mHandshaker.isHandshakeComplete()) { mHandshaker.finishHandshake(channel, (FullHttpResponse) msg); mHandshakeFuture.setSuccess(); return; } if (msg instanceof FullHttpResponse) { FullHttpResponse response = (FullHttpResponse) msg; throw new Exception("Unexpected FullHttpResponse (getStatus=" + response.status() + ", content=" + response.content().toString(CharsetUtil.UTF_8) + ')'); } WebSocketFrame frame = (WebSocketFrame) msg; if (frame instanceof TextWebSocketFrame) { TextWebSocketFrame textFrame = (TextWebSocketFrame) frame; setMessageReceived(textFrame.text()); } if(frame instanceof CloseWebSocketFrame){ setConnected(Boolean.FALSE); } } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { cause.printStackTrace(); if (!mHandshakeFuture.isDone()) { mHandshakeFuture.setFailure(cause); } ctx.close(); } } }