/*
* Copyright (c) 2015, 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.buffer.Unpooled;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelPipeline;
import io.netty.handler.codec.http.DefaultLastHttpContent;
import io.netty.handler.codec.http.FullHttpMessage;
import io.netty.handler.codec.http.HttpContent;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpMessage;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.LastHttpContent;
import io.netty.handler.codec.http.websocketx.CloseWebSocketFrame;
import io.netty.handler.codec.http.websocketx.WebSocketServerHandshaker;
import io.netty.handler.codec.http.websocketx.WebSocketServerHandshakerFactory;
import io.netty.util.concurrent.DefaultEventExecutorGroup;
import io.netty.util.concurrent.EventExecutorGroup;
import org.apache.commons.pool.impl.GenericObjectPool;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.wso2.carbon.messaging.CarbonCallback;
import org.wso2.carbon.messaging.CarbonMessage;
import org.wso2.carbon.messaging.CarbonMessageProcessor;
import org.wso2.carbon.transport.http.netty.common.Constants;
import org.wso2.carbon.transport.http.netty.common.HttpRoute;
import org.wso2.carbon.transport.http.netty.common.Util;
import org.wso2.carbon.transport.http.netty.config.ListenerConfiguration;
import org.wso2.carbon.transport.http.netty.internal.HTTPTransportContextHolder;
import org.wso2.carbon.transport.http.netty.message.HTTPCarbonMessage;
import org.wso2.carbon.transport.http.netty.sender.channel.TargetChannel;
import org.wso2.carbon.transport.http.netty.sender.channel.pool.ConnectionManager;
import org.wso2.carbon.transport.http.netty.sender.channel.pool.PoolConfiguration;
import java.net.InetSocketAddress;
import java.net.ProtocolException;
import java.net.URISyntaxException;
import java.util.HashMap;
import java.util.Map;
/**
* A Class responsible for handle incoming message through netty inbound pipeline.
*/
public class SourceHandler extends ChannelInboundHandlerAdapter {
private static Logger log = LoggerFactory.getLogger(SourceHandler.class);
protected ChannelHandlerContext ctx;
protected HTTPCarbonMessage cMsg;
protected ConnectionManager connectionManager;
private Map<String, TargetChannel> channelFutureMap = new HashMap<>();
protected Map<String, GenericObjectPool> targetChannelPool;
protected ListenerConfiguration listenerConfiguration;
private WebSocketServerHandshaker handshaker;
public ListenerConfiguration getListenerConfiguration() {
return listenerConfiguration;
}
public SourceHandler(ConnectionManager connectionManager, ListenerConfiguration listenerConfiguration)
throws Exception {
this.listenerConfiguration = listenerConfiguration;
this.connectionManager = connectionManager;
}
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
super.handlerAdded(ctx);
this.ctx = ctx;
this.targetChannelPool = connectionManager.getTargetChannelPool();
}
@Override
public void channelActive(final ChannelHandlerContext ctx) throws Exception {
// Start the server connection Timer
if (HTTPTransportContextHolder.getInstance().getHandlerExecutor() != null) {
HTTPTransportContextHolder.getInstance().getHandlerExecutor()
.executeAtSourceConnectionInitiation(Integer.toString(ctx.hashCode()));
}
this.ctx = ctx;
if (this.targetChannelPool == null) {
this.targetChannelPool = connectionManager.getTargetChannelPool();
}
}
@SuppressWarnings("unchecked")
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
if (msg instanceof FullHttpMessage) {
FullHttpMessage fullHttpMessage = (FullHttpMessage) msg;
cMsg = (HTTPCarbonMessage) setupCarbonMessage(fullHttpMessage);
publishToMessageProcessor(cMsg);
ByteBuf content = ((FullHttpMessage) msg).content();
cMsg.addHttpContent(new DefaultLastHttpContent(content));
cMsg.setEndOfMsgAdded(true);
if (HTTPTransportContextHolder.getInstance().getHandlerExecutor() != null) {
HTTPTransportContextHolder.getInstance().getHandlerExecutor().executeAtSourceRequestSending(cMsg);
}
} else if (msg instanceof HttpRequest) {
/*
Checks whether the given connection is a WebSocketUpgrade and add necessary components to it.
*/
HttpRequest httpRequest = (HttpRequest) msg;
HttpHeaders headers = httpRequest.headers();
if (Constants.UPGRADE.equalsIgnoreCase(headers.get(Constants.CONNECTION)) &&
Constants.WEBSOCKET_UPGRADE.equalsIgnoreCase(headers.get(Constants.UPGRADE))) {
log.info("Upgrading the connection from Http to WebSocket for " +
"channel : " + ctx.channel());
handleWebSocketHandshake(httpRequest);
} else {
cMsg = (HTTPCarbonMessage) setupCarbonMessage(httpRequest);
publishToMessageProcessor(cMsg);
}
//Publish message to CarbonMessageProcessor
} else {
if (cMsg != null) {
if (msg instanceof HttpContent) {
HttpContent httpContent = (HttpContent) msg;
cMsg.addHttpContent(httpContent);
if (msg instanceof LastHttpContent) {
cMsg.setEndOfMsgAdded(true);
if (HTTPTransportContextHolder.getInstance().getHandlerExecutor() != null) {
HTTPTransportContextHolder.getInstance().getHandlerExecutor().
executeAtSourceRequestSending(cMsg);
}
}
}
}
}
}
/*
This handles the WebSocket Handshake.
*/
private void handleWebSocketHandshake(HttpRequest httpRequest) throws ProtocolException {
try {
WebSocketServerHandshakerFactory wsFactory = new WebSocketServerHandshakerFactory(
getWebSocketURL(httpRequest), null, true);
handshaker = wsFactory.newHandshaker(httpRequest);
handshaker.handshake(ctx.channel(), httpRequest);
boolean isSecuredConnection = false;
if (listenerConfiguration.getSslConfig() != null) {
isSecuredConnection = true;
}
//Replace HTTP handlers with new Handlers for WebSocket in the pipeline
ChannelPipeline pipeline = ctx.pipeline();
int maxThreads = PoolConfiguration.getInstance().getEventGroupExecutorThreads();
EventExecutorGroup executorGroup = new DefaultEventExecutorGroup(maxThreads);
pipeline.addLast(executorGroup, "ws_handler",
new WebSocketSourceHandler(generateWebSocketChannelID(), this.connectionManager,
this.listenerConfiguration, httpRequest, isSecuredConnection,
ctx));
pipeline.remove(this);
} catch (Exception e) {
/*
Code 1002 : indicates that an endpoint is terminating the connection
due to a protocol error.
*/
ctx.channel().writeAndFlush(new CloseWebSocketFrame(1002, ""));
ctx.close();
throw new ProtocolException("Error occurred in HTTP to WebSocket Upgrade : " + e.getMessage());
}
}
/* Get the URL of the given connection */
private String getWebSocketURL(HttpRequest req) {
String protocol = "ws";
if (listenerConfiguration.getSslConfig() != null) {
protocol = "wss";
}
String url = protocol + "://" + req.headers().get("Host") + req.getUri();
return url;
}
//Carbon Message is published to registered message processor and Message Processor should return transport thread
//immediately
protected void publishToMessageProcessor(CarbonMessage cMsg) throws URISyntaxException {
if (HTTPTransportContextHolder.getInstance().getHandlerExecutor() != null) {
HTTPTransportContextHolder.getInstance().getHandlerExecutor().executeAtSourceRequestReceiving(cMsg);
}
boolean continueRequest = true;
if (HTTPTransportContextHolder.getInstance().getHandlerExecutor() != null) {
continueRequest = HTTPTransportContextHolder.getInstance().getHandlerExecutor()
.executeRequestContinuationValidator(cMsg, carbonMessage -> {
CarbonCallback responseCallback = (CarbonCallback) cMsg
.getProperty(org.wso2.carbon.messaging.Constants.CALL_BACK);
responseCallback.done(carbonMessage);
});
}
if (continueRequest) {
CarbonMessageProcessor carbonMessageProcessor = HTTPTransportContextHolder.getInstance()
.getMessageProcessor();
if (carbonMessageProcessor != null) {
try {
carbonMessageProcessor.receive(cMsg, new ResponseCallback(this.ctx, cMsg));
} catch (Exception e) {
log.error("Error while submitting CarbonMessage to CarbonMessageProcessor", e);
}
} else {
log.error("Cannot find registered MessageProcessor for forward the message");
}
}
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
// Stop the connector timer
ctx.close();
if (HTTPTransportContextHolder.getInstance().getHandlerExecutor() != null) {
HTTPTransportContextHolder.getInstance().getHandlerExecutor()
.executeAtSourceConnectionTermination(Integer.toString(ctx.hashCode()));
}
connectionManager.notifyChannelInactive();
}
public void addTargetChannel(HttpRoute route, TargetChannel targetChannel) {
channelFutureMap.put(route.toString(), targetChannel);
}
public TargetChannel getChannelFuture(HttpRoute route) {
return channelFutureMap.remove(route.toString());
}
public boolean isChannelFutureExists(HttpRoute route) {
return (channelFutureMap.get(route.toString()) != null);
}
public Map<String, GenericObjectPool> getTargetChannelPool() {
return targetChannelPool;
}
public ChannelHandlerContext getInboundChannelContext() {
return ctx;
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
if (ctx != null && ctx.channel().isActive()) {
ctx.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
}
}
protected CarbonMessage setupCarbonMessage(HttpMessage httpMessage) throws URISyntaxException {
cMsg = new HTTPCarbonMessage();
boolean isSecuredConnection = false;
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());
HttpRequest httpRequest = (HttpRequest) httpMessage;
cMsg.setProperty(Constants.CHNL_HNDLR_CTX, this.ctx);
cMsg.setProperty(Constants.SRC_HANDLER, this);
cMsg.setProperty(Constants.HTTP_VERSION, httpRequest.getProtocolVersion().text());
cMsg.setProperty(Constants.HTTP_METHOD, httpRequest.getMethod().name());
cMsg.setProperty(org.wso2.carbon.messaging.Constants.LISTENER_PORT,
((InetSocketAddress) ctx.channel().localAddress()).getPort());
cMsg.setProperty(org.wso2.carbon.messaging.Constants.LISTENER_INTERFACE_ID, listenerConfiguration.getId());
cMsg.setProperty(org.wso2.carbon.messaging.Constants.PROTOCOL, Constants.PROTOCOL_NAME);
if (listenerConfiguration.getSslConfig() != null) {
isSecuredConnection = true;
}
cMsg.setProperty(Constants.IS_SECURED_CONNECTION, isSecuredConnection);
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.REQUEST_URL, httpRequest.getUri());
ChannelHandler handler = ctx.handler();
cMsg.setProperty(Constants.CHANNEL_ID, ((SourceHandler) handler).getListenerConfiguration().getId());
cMsg.setProperty(Constants.TO, httpRequest.getUri());
cMsg.setHeaders(Util.getHeaders(httpRequest).getAll());
//Added protocol name as a string
return cMsg;
}
/*
Generate a ChannelId for WebSocket
*/
protected String generateWebSocketChannelID() {
return ctx.channel().id().asLongText();
}
}