org.wso2.carbon.transport.http.netty.sender.websocket.WebSocketTargetHandler.java Source code

Java tutorial

Introduction

Here is the source code for org.wso2.carbon.transport.http.netty.sender.websocket.WebSocketTargetHandler.java

Source

/*
 *  Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
 *
 *  WSO2 Inc. licenses 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 org.wso2.carbon.transport.http.netty.sender.websocket;

import io.netty.buffer.ByteBuf;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPromise;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame;
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.WebSocketClientHandshaker;
import io.netty.handler.codec.http.websocketx.WebSocketFrame;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.util.CharsetUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.wso2.carbon.transport.http.netty.common.Constants;
import org.wso2.carbon.transport.http.netty.contract.ServerConnectorException;
import org.wso2.carbon.transport.http.netty.contract.websocket.WebSocketBinaryMessage;
import org.wso2.carbon.transport.http.netty.contract.websocket.WebSocketCloseMessage;
import org.wso2.carbon.transport.http.netty.contract.websocket.WebSocketConnectorListener;
import org.wso2.carbon.transport.http.netty.contract.websocket.WebSocketControlMessage;
import org.wso2.carbon.transport.http.netty.contract.websocket.WebSocketControlSignal;
import org.wso2.carbon.transport.http.netty.contract.websocket.WebSocketTextMessage;
import org.wso2.carbon.transport.http.netty.contractimpl.websocket.WebSocketMessageImpl;
import org.wso2.carbon.transport.http.netty.contractimpl.websocket.message.WebSocketBinaryMessageImpl;
import org.wso2.carbon.transport.http.netty.contractimpl.websocket.message.WebSocketCloseMessageImpl;
import org.wso2.carbon.transport.http.netty.contractimpl.websocket.message.WebSocketControlMessageImpl;
import org.wso2.carbon.transport.http.netty.contractimpl.websocket.message.WebSocketTextMessageImpl;
import org.wso2.carbon.transport.http.netty.exception.UnknownWebSocketFrameTypeException;
import org.wso2.carbon.transport.http.netty.internal.websocket.WebSocketSessionImpl;
import org.wso2.carbon.transport.http.netty.internal.websocket.WebSocketUtil;

import java.net.InetSocketAddress;
import java.net.URISyntaxException;
import java.nio.ByteBuffer;
import javax.websocket.Session;

/**
 * WebSocket Client Handler. This class responsible for handling the inbound messages for the WebSocket Client.
 * <i>Note: If the user uses both WebSocket Client and the server it is recommended to check the
 * <b>{@link Constants}.IS_WEBSOCKET_SERVER</b> property to identify whether the message is coming from the client
 * or the server in the application level.</i>
 */
public class WebSocketTargetHandler extends SimpleChannelInboundHandler<Object> {

    private static final Logger log = LoggerFactory.getLogger(WebSocketClient.class);

    private final WebSocketClientHandshaker handshaker;
    private final boolean isSecure;
    private final String requestedUri;
    private final WebSocketConnectorListener connectorListener;
    private final String target;
    private String actualSubProtocol = null;
    private WebSocketSessionImpl channelSession;
    private ChannelPromise handshakeFuture;

    public WebSocketTargetHandler(WebSocketClientHandshaker handshaker, boolean isSecure, String requestedUri,
            String target, WebSocketConnectorListener webSocketConnectorListener) {
        this.handshaker = handshaker;
        this.isSecure = isSecure;
        this.requestedUri = requestedUri;
        this.target = target;
        this.connectorListener = webSocketConnectorListener;
        handshakeFuture = null;
    }

    public ChannelFuture handshakeFuture() {
        return handshakeFuture;
    }

    public Session getChannelSession() {
        return channelSession;
    }

    public void setActualSubProtocol(String actualSubProtocol) {
        this.actualSubProtocol = actualSubProtocol;
    }

    @Override
    public void handlerAdded(ChannelHandlerContext ctx) {
        handshakeFuture = ctx.newPromise();
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws URISyntaxException {
        handshaker.handshake(ctx.channel());
        channelSession = WebSocketUtil.getSession(ctx, isSecure, requestedUri);
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws ServerConnectorException {
        if (channelSession != null) {
            channelSession.setIsOpen(false);
        }
        int statusCode = 1001;
        String reasonText = "Server is going away";
        notifyCloseMessage(statusCode, reasonText, ctx);
    }

    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        if (evt instanceof IdleStateEvent) {
            IdleStateEvent idleStateEvent = (IdleStateEvent) evt;
            if (idleStateEvent.state() == IdleStateEvent.ALL_IDLE_STATE_EVENT.state()) {
                notifyIdleTimeout(ctx);
            }
        }
    }

    @Override
    public void channelRead0(ChannelHandlerContext ctx, Object msg)
            throws UnknownWebSocketFrameTypeException, URISyntaxException, ServerConnectorException {
        Channel ch = ctx.channel();
        if (!handshaker.isHandshakeComplete()) {
            handshaker.finishHandshake(ch, (FullHttpResponse) msg);
            log.debug("WebSocket Client connected!");
            handshakeFuture.setSuccess();
            channelSession = WebSocketUtil.getSession(ctx, isSecure, requestedUri);
            return;
        }

        if (msg instanceof FullHttpResponse) {
            FullHttpResponse response = (FullHttpResponse) msg;
            throw new IllegalStateException("Unexpected FullHttpResponse (getStatus=" + response.status()
                    + ", content=" + response.content().toString(CharsetUtil.UTF_8) + ')');
        }
        WebSocketFrame frame = (WebSocketFrame) msg;
        if (frame instanceof TextWebSocketFrame) {
            notifyTextMessage((TextWebSocketFrame) frame, ctx);
        } else if (frame instanceof BinaryWebSocketFrame) {
            notifyBinaryMessage((BinaryWebSocketFrame) frame, ctx);
        } else if (frame instanceof PongWebSocketFrame) {
            notifyPongMessage((PongWebSocketFrame) frame, ctx);
        } else if (frame instanceof PingWebSocketFrame) {
            notifyPingMessage((PingWebSocketFrame) frame, ctx);
        } else if (frame instanceof CloseWebSocketFrame) {
            if (channelSession != null) {
                channelSession.setIsOpen(false);
            }
            notifyCloseMessage((CloseWebSocketFrame) frame, ctx);
            ch.close();
        } else {
            throw new UnknownWebSocketFrameTypeException("Cannot identify the WebSocket frame type");
        }
    }

    private void notifyTextMessage(TextWebSocketFrame textWebSocketFrame, ChannelHandlerContext ctx)
            throws ServerConnectorException {
        String text = textWebSocketFrame.text();
        boolean isFinalFragment = textWebSocketFrame.isFinalFragment();
        WebSocketMessageImpl webSocketTextMessage = new WebSocketTextMessageImpl(text, isFinalFragment);
        webSocketTextMessage = setupCommonProperties(webSocketTextMessage, ctx);
        connectorListener.onMessage((WebSocketTextMessage) webSocketTextMessage);
    }

    private void notifyBinaryMessage(BinaryWebSocketFrame binaryWebSocketFrame, ChannelHandlerContext ctx)
            throws ServerConnectorException {
        ByteBuf byteBuf = binaryWebSocketFrame.content();
        boolean finalFragment = binaryWebSocketFrame.isFinalFragment();
        ByteBuffer byteBuffer = byteBuf.nioBuffer();
        WebSocketMessageImpl webSocketBinaryMessage = new WebSocketBinaryMessageImpl(byteBuffer, finalFragment);
        webSocketBinaryMessage = setupCommonProperties(webSocketBinaryMessage, ctx);
        connectorListener.onMessage((WebSocketBinaryMessage) webSocketBinaryMessage);
    }

    private void notifyCloseMessage(CloseWebSocketFrame closeWebSocketFrame, ChannelHandlerContext ctx)
            throws ServerConnectorException {
        String reasonText = closeWebSocketFrame.reasonText();
        int statusCode = closeWebSocketFrame.statusCode();
        ctx.channel().close();
        if (channelSession == null) {
            throw new ServerConnectorException("Cannot find initialized channel session");
        }
        channelSession.setIsOpen(false);
        WebSocketMessageImpl webSocketCloseMessage = new WebSocketCloseMessageImpl(statusCode, reasonText);
        webSocketCloseMessage = setupCommonProperties(webSocketCloseMessage, ctx);
        connectorListener.onMessage((WebSocketCloseMessage) webSocketCloseMessage);
    }

    private void notifyCloseMessage(int statusCode, String reasonText, ChannelHandlerContext ctx)
            throws ServerConnectorException {
        ctx.channel().close();
        if (channelSession == null) {
            throw new ServerConnectorException("Cannot find initialized channel session");
        }
        channelSession.setIsOpen(false);
        WebSocketMessageImpl webSocketCloseMessage = new WebSocketCloseMessageImpl(statusCode, reasonText);
        webSocketCloseMessage = setupCommonProperties(webSocketCloseMessage, ctx);
        connectorListener.onMessage((WebSocketCloseMessage) webSocketCloseMessage);
    }

    private void notifyPingMessage(PingWebSocketFrame pingWebSocketFrame, ChannelHandlerContext ctx)
            throws ServerConnectorException {
        //Control message for WebSocket is Ping Message
        ByteBuf byteBuf = pingWebSocketFrame.content();
        ByteBuffer byteBuffer = byteBuf.nioBuffer();
        WebSocketMessageImpl webSocketControlMessage = new WebSocketControlMessageImpl(WebSocketControlSignal.PING,
                byteBuffer);
        webSocketControlMessage = setupCommonProperties(webSocketControlMessage, ctx);
        connectorListener.onMessage((WebSocketControlMessage) webSocketControlMessage);
    }

    private void notifyPongMessage(PongWebSocketFrame pongWebSocketFrame, ChannelHandlerContext ctx)
            throws ServerConnectorException {
        //Control message for WebSocket is Pong Message
        ByteBuf byteBuf = pongWebSocketFrame.content();
        ByteBuffer byteBuffer = byteBuf.nioBuffer();
        WebSocketMessageImpl webSocketControlMessage = new WebSocketControlMessageImpl(WebSocketControlSignal.PONG,
                byteBuffer);
        webSocketControlMessage = setupCommonProperties(webSocketControlMessage, ctx);
        connectorListener.onMessage((WebSocketControlMessage) webSocketControlMessage);
    }

    private void notifyIdleTimeout(ChannelHandlerContext ctx) throws ServerConnectorException {
        WebSocketMessageImpl websocketControlMessage = new WebSocketControlMessageImpl(
                WebSocketControlSignal.IDLE_TIMEOUT, null);
        websocketControlMessage = setupCommonProperties(websocketControlMessage, ctx);
        connectorListener.onIdleTimeout((WebSocketControlMessage) websocketControlMessage);
    }

    private WebSocketMessageImpl setupCommonProperties(WebSocketMessageImpl webSocketChannelContext,
            ChannelHandlerContext ctx) {
        webSocketChannelContext.setSubProtocol(actualSubProtocol);
        webSocketChannelContext.setTarget(target);
        webSocketChannelContext.setIsConnectionSecured(isSecure);
        webSocketChannelContext.setChannelSession(channelSession);
        webSocketChannelContext.setIsServerMessage(false);

        webSocketChannelContext.setProperty(Constants.SRC_HANDLER, this);
        webSocketChannelContext.setProperty(org.wso2.carbon.messaging.Constants.LISTENER_PORT,
                ((InetSocketAddress) ctx.channel().localAddress()).getPort());
        webSocketChannelContext.setProperty(Constants.LOCAL_ADDRESS, ctx.channel().localAddress());
        webSocketChannelContext.setProperty(Constants.LOCAL_NAME,
                ((InetSocketAddress) ctx.channel().localAddress()).getHostName());
        return webSocketChannelContext;
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        if (!handshakeFuture.isDone()) {
            handshakeFuture.setFailure(cause);
        }
        ctx.close();
        connectorListener.onError(cause);
    }
}