/* * 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.listener; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.http.HttpRequest; 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.WebSocketFrame; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.wso2.carbon.messaging.BinaryCarbonMessage; import org.wso2.carbon.messaging.CarbonMessage; import org.wso2.carbon.messaging.CarbonMessageProcessor; import org.wso2.carbon.messaging.ControlCarbonMessage; import org.wso2.carbon.messaging.StatusCarbonMessage; import org.wso2.carbon.messaging.TextCarbonMessage; import org.wso2.carbon.transport.http.netty.common.Constants; import org.wso2.carbon.transport.http.netty.config.ListenerConfiguration; import org.wso2.carbon.transport.http.netty.exception.UnknownWebSocketFrameTypeException; import org.wso2.carbon.transport.http.netty.internal.HTTPTransportContextHolder; import org.wso2.carbon.transport.http.netty.internal.websocket.WebSocketSessionImpl; import org.wso2.carbon.transport.http.netty.sender.channel.pool.ConnectionManager; import java.net.InetSocketAddress; import java.net.URISyntaxException; import java.nio.ByteBuffer; /** * This class handles all kinds of WebSocketFrames * after connection is upgraded from HTTP to WebSocket. */ public class WebSocketSourceHandler extends SourceHandler { private static final Logger logger = LoggerFactory.getLogger(WebSocketSourceHandler.class); private final String uri; private CarbonMessage cMsg; private final String channelId; private final boolean isSecured; private final WebSocketSessionImpl session; /** * @param channelId This works as the session id of the WebSocket connection. * @param connectionManager connection manager for WebSocket connection. * @param listenerConfiguration Listener configuration for WebSocket connection. * @param httpRequest {@link HttpRequest} which contains the details of WebSocket Upgrade. * @param isSecured indication of whether the connection is secured or not. * @param ctx {@link ChannelHandlerContext} of WebSocket connection. */ public WebSocketSourceHandler(String channelId, ConnectionManager connectionManager, ListenerConfiguration listenerConfiguration, HttpRequest httpRequest, boolean isSecured, ChannelHandlerContext ctx) throws Exception { super(connectionManager, listenerConfiguration); this.uri = httpRequest.uri(); this.channelId = channelId; this.isSecured = isSecured; this.session = new WebSocketSessionImpl(ctx, isSecured, uri, channelId); httpRequest.headers().entries().forEach( header -> { session.addUserProperty(header.getKey(), header.getValue()); } ); sendOnOpenMessage(ctx, isSecured, uri); } @Override public void channelInactive(ChannelHandlerContext ctx) throws Exception { session.setIsOpen(false); int statusCode = 1001; // Client is going away. String reasonText = "Client is going away"; cMsg = new StatusCarbonMessage(org.wso2.carbon.messaging.Constants.STATUS_CLOSE, statusCode, reasonText); setupCarbonMessage(ctx); publishToMessageProcessor(cMsg); } @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws UnknownWebSocketFrameTypeException { cMsg = null; if (!(msg instanceof WebSocketFrame)) { logger.error("Expecting WebSocketFrame. Unknown type."); throw new UnknownWebSocketFrameTypeException("Expecting WebSocketFrame. Unknown type."); } if (msg instanceof TextWebSocketFrame) { TextWebSocketFrame textWebSocketFrame = (TextWebSocketFrame) msg; String text = textWebSocketFrame.text(); cMsg = new TextCarbonMessage(text); } else if (msg instanceof BinaryWebSocketFrame) { BinaryWebSocketFrame binaryWebSocketFrame = (BinaryWebSocketFrame) msg; boolean finalFragment = binaryWebSocketFrame.isFinalFragment(); ByteBuf byteBuf = binaryWebSocketFrame.content(); ByteBuffer byteBuffer = byteBuf.nioBuffer(); cMsg = new BinaryCarbonMessage(byteBuffer, finalFragment); } else if (msg instanceof CloseWebSocketFrame) { CloseWebSocketFrame closeWebSocketFrame = (CloseWebSocketFrame) msg; String reasonText = closeWebSocketFrame.reasonText(); int statusCode = closeWebSocketFrame.statusCode(); ctx.channel().close(); session.setIsOpen(false); cMsg = new StatusCarbonMessage(org.wso2.carbon.messaging.Constants.STATUS_CLOSE, statusCode, reasonText); } else if (msg instanceof PingWebSocketFrame) { PingWebSocketFrame pingWebSocketFrame = (PingWebSocketFrame) msg; ctx.channel().writeAndFlush(new PongWebSocketFrame(pingWebSocketFrame.content())); } else if (msg instanceof PongWebSocketFrame) { //Control message for WebSocket is Pong Message PongWebSocketFrame pongWebSocketFrame = (PongWebSocketFrame) msg; boolean finalFragment = pongWebSocketFrame.isFinalFragment(); ByteBuf byteBuf = pongWebSocketFrame.content(); ByteBuffer byteBuffer = byteBuf.nioBuffer(); cMsg = new ControlCarbonMessage(org.wso2.carbon.messaging.Constants.CONTROL_SIGNAL_HEARTBEAT, byteBuffer, finalFragment); setupCarbonMessage(ctx); } setupCarbonMessage(ctx); publishToMessageProcessor(cMsg); } /* Carbon Message is published to registered message processor Message Processor should return transport thread immediately */ @Override protected void publishToMessageProcessor(CarbonMessage cMsg) { if (HTTPTransportContextHolder.getInstance().getHandlerExecutor() != null) { HTTPTransportContextHolder.getInstance().getHandlerExecutor().executeAtSourceRequestReceiving(cMsg); } CarbonMessageProcessor carbonMessageProcessor = HTTPTransportContextHolder.getInstance() .getMessageProcessor(); if (carbonMessageProcessor != null) { try { carbonMessageProcessor.receive(cMsg, null); } catch (Exception e) { logger.error("Error while submitting CarbonMessage to CarbonMessageProcessor.", e); ctx.channel().close(); } } else { logger.error("Cannot find registered MessageProcessor to forward the message."); ctx.channel().close(); } } private void sendOnOpenMessage(ChannelHandlerContext ctx, boolean isSecured, String uri) throws URISyntaxException { cMsg = new StatusCarbonMessage(org.wso2.carbon.messaging.Constants.STATUS_OPEN, 0, null); setupCarbonMessage(ctx); cMsg.setProperty(Constants.CONNECTION, Constants.UPGRADE); cMsg.setProperty(Constants.UPGRADE, Constants.WEBSOCKET_UPGRADE); publishToMessageProcessor(cMsg); } /* Extract all the necessary properties from ChannelHandlerContext Add them into a CarbonMessage Note : This method only add details of ChannelHandlerContext to the CarbonMessage Adding other custom details should be done separately @return basic CarbonMessage with necessary details */ private void setupCarbonMessage(ChannelHandlerContext ctx) { if (HTTPTransportContextHolder.getInstance().getHandlerExecutor() != null) { HTTPTransportContextHolder.getInstance().getHandlerExecutor().executeAtSourceRequestReceiving(cMsg); } cMsg.setProperty(Constants.PORT, ((InetSocketAddress) ctx.channel().remoteAddress()).getPort()); cMsg.setProperty(Constants.HOST, ((InetSocketAddress) ctx.channel().remoteAddress()).getHostName()); cMsg.setProperty(Constants.TO, this.uri); cMsg.setProperty(org.wso2.carbon.messaging.Constants.LISTENER_PORT, ((InetSocketAddress) ctx.channel().localAddress()).getPort()); cMsg.setProperty(Constants.IS_SECURED_CONNECTION, isSecured); cMsg.setProperty(Constants.LOCAL_ADDRESS, ctx.channel().localAddress()); cMsg.setProperty(Constants.LOCAL_NAME, ((InetSocketAddress) ctx.channel().localAddress()).getHostName()); cMsg.setProperty(Constants.REMOTE_ADDRESS, ctx.channel().remoteAddress()); cMsg.setProperty(Constants.REMOTE_HOST, ((InetSocketAddress) ctx.channel().remoteAddress()).getHostName()); cMsg.setProperty(Constants.REMOTE_PORT, ((InetSocketAddress) ctx.channel().remoteAddress()).getPort()); cMsg.setProperty(Constants.CHANNEL_ID, channelId); cMsg.setProperty(Constants.PROTOCOL, Constants.WEBSOCKET_PROTOCOL); cMsg.setProperty(Constants.WEBSOCKET_SESSION, session); } }