/** Copyright 2013 the original author or authors. * * 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. */ /** * * @author <a href='mailto:th33musk3t33rs@gmail.com'>3.musket33rs</a> * * @since 0.1 */ package org.threemusketeers.websocket; import io.netty.channel.*; import io.netty.handler.codec.http.DefaultHttpHeaders; import io.netty.handler.codec.http.FullHttpResponse; import io.netty.handler.codec.http.HttpHeaders; import io.netty.handler.codec.http.websocketx.*; import io.netty.util.CharsetUtil; import java.net.URI; import java.util.concurrent.TimeUnit; @ChannelHandler.Sharable public class WebSocketClientHandler extends ChannelInboundMessageHandlerAdapter<Object> { private WebSocketClientHandshaker handshaker; private WebSocket webSocket; boolean closed = false; long reconnectDelay = 3000; private Channel channel; WebSocketNotification notification; URI uri; HttpHeaders customHeaders; public WebSocketClientHandler(URI uri, WebSocketNotification notification, WebSocket webSocket) { customHeaders = new DefaultHttpHeaders(); customHeaders.add("MyHeader", "MyValue"); this.uri = uri; // Connect with V13 (RFC 6455 aka HyBi-17). You can change it to V08 or V00. // If you change it to V00, ping is not supported and remember to change // HttpResponseDecoder to WebSocketHttpResponseDecoder in the pipeline. this.handshaker = WebSocketClientHandshakerFactory.newHandshaker( uri, WebSocketVersion.V13, null, false, customHeaders); this.webSocket = webSocket; this.notification = notification; } @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { channel = ctx.channel(); handshaker.handshake(channel); } @Override public void channelInactive(ChannelHandlerContext ctx) throws Exception { System.out.println("WebSocket Client disconnected!"); if (!closed) { final EventLoop loop = ctx.channel().eventLoop(); loop.schedule(new Runnable() { @Override public void run() { System.out.println("Reconnecting"); try { handshaker = WebSocketClientHandshakerFactory.newHandshaker( uri, WebSocketVersion.V13, null, false, customHeaders); webSocket.createBootstrap(); } catch (InterruptedException e) { notification.onerror("Unable to reconnect WebSocket"); } System.out.println("After Reconnecting"); } }, reconnectDelay, TimeUnit.MILLISECONDS); } } @Override public void messageReceived(ChannelHandlerContext ctx, Object msg) throws Exception { if (!handshaker.isHandshakeComplete()) { // try catch for on error handshaker.finishHandshake(channel, (FullHttpResponse) msg); notification.onopen(); return; } if (msg instanceof FullHttpResponse) { FullHttpResponse response = (FullHttpResponse) msg; notification.onerror("Unexpected FullHttpResponse (getStatus=" + response.getStatus() + ')'); } WebSocketFrame frame = (WebSocketFrame) msg; if (frame instanceof TextWebSocketFrame) { TextWebSocketFrame textFrame = (TextWebSocketFrame) frame; notification.onmessage(textFrame.text()); } else if (frame instanceof CloseWebSocketFrame) { channel.close(); notification.onclose(); } } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { notification.onerror(cause.getMessage()); ctx.close(); } public void close() { this.closed = true; } protected void finalize() throws Throwable { channel.close(); } }