/**
* Copyright 2015 Santhosh Kumar Tekuri
*
* The JLibs authors license 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 jlibs.wamp4j.netty;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufInputStream;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelPromise;
import io.netty.channel.nio.AbstractNioChannel;
import io.netty.handler.codec.http.websocketx.*;
import jlibs.wamp4j.spi.Listener;
import jlibs.wamp4j.spi.MessageType;
import jlibs.wamp4j.spi.WAMPOutputStream;
import jlibs.wamp4j.spi.WAMPSocket;
import java.lang.reflect.Method;
import java.nio.channels.SocketChannel;
/**
* @author Santhosh Kumar Tekuri
*/
public class NettyWebSocket extends ChannelInboundHandlerAdapter implements WAMPSocket{
private final WebSocketServerHandshaker handshaker;
private final String subProtocol;
protected ChannelHandlerContext ctx;
private ChannelPromise voidPromise;
public NettyWebSocket(WebSocketServerHandshaker handshaker, String subProtocol){
this.handshaker = handshaker;
this.subProtocol = subProtocol;
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception{
this.ctx = ctx;
voidPromise = ctx.channel().voidPromise();
if(listener!=null)
listener.readyToWrite(this);
super.channelActive(ctx);
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception{
if(listener!=null)
listener.onClose(this);
super.channelInactive(ctx);
}
private final NettyInputStream is = new NettyInputStream();
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception{
if(msg instanceof WebSocketFrame){
WebSocketFrame frame = (WebSocketFrame)msg;
try{
if(frame instanceof TextWebSocketFrame || frame instanceof BinaryWebSocketFrame){
if(listener!=null){
MessageType type = frame instanceof TextWebSocketFrame ? MessageType.text : MessageType.binary;
is.reset(frame.content());
listener.onMessage(this, type, is);
}
}else if(frame instanceof PingWebSocketFrame)
ctx.write(new PongWebSocketFrame(frame.content().retain()));
else if(frame instanceof CloseWebSocketFrame)
handshaker.close(ctx.channel(), (CloseWebSocketFrame)frame.retain());
}finally{
frame.release();
}
}else
ctx.fireChannelRead(msg);
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception{
if(listener!=null)
listener.onReadComplete(this);
ctx.fireChannelReadComplete();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception{
if(listener!=null)
listener.onError(this, cause);
}
@Override
public String subProtocol(){
return subProtocol;
}
protected Listener listener;
@Override
public void setListener(Listener listener){
this.listener = listener;
}
@Override
public void send(MessageType type, WAMPOutputStream out){
ByteBuf buffer = ((NettyOutputStream)out).buffer;
WebSocketFrame frame = type==MessageType.text ? new TextWebSocketFrame(buffer) : new BinaryWebSocketFrame(buffer);
ctx.write(frame, voidPromise);
}
@Override
public boolean isAutoRead(){
return ctx.channel().config().isAutoRead();
}
@Override
public void setAutoRead(boolean autoRead){
ctx.channel().config().setAutoRead(autoRead);
}
@Override
public boolean isWritable(){
return ctx.channel().isWritable();
}
@Override
public void flush(){
ctx.flush();
}
@Override
public boolean isOpen(){
return ctx.channel().isOpen();
}
@Override
public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception{
if(ctx.channel().isWritable() && listener!=null)
listener.readyToWrite(this);
}
@Override
public void close(){
ctx.writeAndFlush(new CloseWebSocketFrame()).addListener(ChannelFutureListener.CLOSE);
}
@Override
public void kill(){
try{
Method method = AbstractNioChannel.class.getDeclaredMethod("javaChannel");
method.setAccessible(true);
SocketChannel channel = (SocketChannel)method.invoke(ctx.channel());
channel.close();
}catch(Exception ex){
ex.printStackTrace();
ctx.close();
}
}
}