/* * Copyright (c) 2015, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. * * 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. */ package org.wso2.carbon.transport.http.netty.sender; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.http.DefaultHttpContent; import io.netty.handler.codec.http.HttpContent; import io.netty.handler.codec.http.HttpResponse; import io.netty.handler.codec.http.LastHttpContent; import io.netty.handler.timeout.ReadTimeoutHandler; 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.messaging.DefaultCarbonMessage; import org.wso2.carbon.messaging.exceptions.MessagingException; import org.wso2.carbon.transport.http.netty.common.Constants; import org.wso2.carbon.transport.http.netty.common.Util; 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 java.net.InetSocketAddress; import java.nio.charset.Charset; import java.util.HashMap; import java.util.Map; /** * A class responsible for handling responses coming from BE. * * TODO: Need to redesign this. This has an incorrect usage of ReadTimeoutHandler. * Timer tasks in IdleStateHandler (parent of ReadTimeoutHandler) is not working properly with overridden methods which * causes timeout issues when TargetHandler is re-used from the ConnectionManager. * */ public class TargetHandler extends ReadTimeoutHandler { protected static final Logger LOG = LoggerFactory.getLogger(TargetHandler.class); protected CarbonCallback callback; protected CarbonMessage cMsg; protected ConnectionManager connectionManager; protected TargetChannel targetChannel; protected CarbonMessage incomingMsg; public TargetHandler(int timeoutSeconds) { super(timeoutSeconds); } @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { if (HTTPTransportContextHolder.getInstance().getHandlerExecutor() != null) { HTTPTransportContextHolder.getInstance().getHandlerExecutor() .executeAtTargetConnectionInitiation(Integer.toString(ctx.hashCode())); } super.channelActive(ctx); } @SuppressWarnings("unchecked") @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { if (msg instanceof HttpResponse) { cMsg = setUpCarbonMessage(ctx, msg); if (HTTPTransportContextHolder.getInstance().getHandlerExecutor() != null) { HTTPTransportContextHolder.getInstance().getHandlerExecutor(). executeAtTargetResponseReceiving(cMsg); } CarbonMessageProcessor carbonMessageProcessor = HTTPTransportContextHolder.getInstance() .getMessageProcessor(); if (carbonMessageProcessor != null) { try { HTTPTransportContextHolder.getInstance().getMessageProcessor().receive(cMsg, callback); } catch (Exception e) { LOG.error("Error while handover response to MessageProcessor ", e); } } else { LOG.error("Cannot correlate callback with request callback is null "); } } else { if (cMsg != null) { if (msg instanceof LastHttpContent) { HttpContent httpContent = (LastHttpContent) msg; ((HTTPCarbonMessage) cMsg).addHttpContent(httpContent); cMsg.setEndOfMsgAdded(true); if (HTTPTransportContextHolder.getInstance().getHandlerExecutor() != null) { HTTPTransportContextHolder.getInstance().getHandlerExecutor(). executeAtTargetResponseSending(cMsg); } targetChannel.setRequestWritten(false); connectionManager.returnChannel(targetChannel); } else { HttpContent httpContent = (DefaultHttpContent) msg; ((HTTPCarbonMessage) cMsg).addHttpContent(httpContent); } } } } @Override public void channelInactive(ChannelHandlerContext ctx) throws Exception { ctx.close(); if (HTTPTransportContextHolder.getInstance().getHandlerExecutor() != null) { HTTPTransportContextHolder.getInstance().getHandlerExecutor() .executeAtTargetConnectionTermination(Integer.toString(ctx.hashCode())); } LOG.debug("Target channel closed."); } public void setCallback(CarbonCallback callback) { this.callback = callback; } public void setConnectionManager(ConnectionManager connectionManager) { this.connectionManager = connectionManager; } public void setIncomingMsg(CarbonMessage incomingMsg) { this.incomingMsg = incomingMsg; } public void setTargetChannel(TargetChannel targetChannel) { this.targetChannel = targetChannel; } @Override protected void readTimedOut(ChannelHandlerContext ctx) { ctx.channel().close(); if (targetChannel.isRequestWritten()) { String payload = "<errorMessage>" + "ReadTimeoutException occurred for endpoint " + targetChannel. getHttpRoute().toString() + "</errorMessage>"; CarbonMessageProcessor carbonMessageProcessor = HTTPTransportContextHolder.getInstance() .getMessageProcessor(); if (carbonMessageProcessor != null) { try { HTTPTransportContextHolder.getInstance().getMessageProcessor() .receive(createErrorMessage(payload), callback); } catch (Exception e) { LOG.error("Error while handover response to MessageProcessor ", e); } } else { LOG.error("Cannot correlate callback with request callback is null "); } } } protected CarbonMessage setUpCarbonMessage(ChannelHandlerContext ctx, Object msg) { cMsg = new HTTPCarbonMessage(); if (HTTPTransportContextHolder.getInstance().getHandlerExecutor() != null) { HTTPTransportContextHolder.getInstance().getHandlerExecutor().executeAtTargetResponseReceiving(cMsg); } cMsg.setProperty(Constants.PORT, ((InetSocketAddress) ctx.channel().remoteAddress()).getPort()); cMsg.setProperty(Constants.HOST, ((InetSocketAddress) ctx.channel().remoteAddress()).getHostName()); cMsg.setProperty(org.wso2.carbon.messaging.Constants.DIRECTION, org.wso2.carbon.messaging.Constants.DIRECTION_RESPONSE); cMsg.setProperty(org.wso2.carbon.messaging.Constants.CALL_BACK, callback); HttpResponse httpResponse = (HttpResponse) msg; cMsg.setProperty(Constants.HTTP_STATUS_CODE, httpResponse.getStatus().code()); cMsg.setHeaders(Util.getHeaders(httpResponse).getAll()); //copy required properties for service chaining from incoming carbon message to the response carbon message //copy shared worker pool cMsg.setProperty(Constants.EXECUTOR_WORKER_POOL, incomingMsg.getProperty(Constants.EXECUTOR_WORKER_POOL)); //TODO copy mandatory properties from previous message if needed return cMsg; } protected CarbonMessage createErrorMessage(String payload) { DefaultCarbonMessage response = new DefaultCarbonMessage(); response.setStringMessageBody(payload); byte[] errorMessageBytes = payload.getBytes(Charset.defaultCharset()); Map<String, String> transportHeaders = new HashMap<>(); transportHeaders.put(Constants.HTTP_CONTENT_TYPE, Constants.TEXT_XML); transportHeaders.put(Constants.HTTP_CONTENT_LENGTH, (String.valueOf(errorMessageBytes.length))); response.setHeaders(transportHeaders); response.setProperty(Constants.HTTP_STATUS_CODE, 504); response.setProperty(org.wso2.carbon.messaging.Constants.DIRECTION, org.wso2.carbon.messaging.Constants.DIRECTION_RESPONSE); response.setProperty(org.wso2.carbon.messaging.Constants.CALL_BACK, callback); MessagingException messagingException = new MessagingException("Read Timeout", 101504); response.setMessagingException(messagingException); return response; } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { if (ctx != null && ctx.channel().isActive()) { ctx.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE); } } }