/* * 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.http2; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.http.HttpContent; import io.netty.handler.codec.http.LastHttpContent; import io.netty.handler.codec.http2.DefaultHttp2Headers; import io.netty.handler.codec.http2.Http2Exception; import io.netty.handler.codec.http2.Http2Headers; 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.DefaultCarbonMessage; import org.wso2.carbon.messaging.MessageDataSource; 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 java.net.InetSocketAddress; import java.nio.ByteBuffer; /** * {@code HTTP2ResponseCallback} is the class implements {@link CarbonCallback} interface to process http2 message * responses coming from message processor */ public class HTTP2ResponseCallback implements CarbonCallback { private ChannelHandlerContext ctx; // Stream id of the channel of initial request private int streamId; private static final Logger logger = LoggerFactory.getLogger(HTTP2ResponseCallback.class); private static final String DEFAULT_HTTP_METHOD_POST = "POST"; /** * Construct a new {@link HTTP2ResponseCallback} to process HTTP2 responses * * @param channelHandlerContext Channel context * @param streamId Stream Id */ public HTTP2ResponseCallback(ChannelHandlerContext channelHandlerContext, int streamId) { this.ctx = channelHandlerContext; this.streamId = streamId; } public void done(CarbonMessage cMsg) { handleResponsesWithoutContentLength(cMsg); if (HTTPTransportContextHolder.getInstance().getHandlerExecutor() != null) { HTTPTransportContextHolder.getInstance().getHandlerExecutor().executeAtSourceResponseReceiving(cMsg); } Http2Headers http2Headers = createHttp2Headers(cMsg); if (ctx.handler() instanceof HTTP2SourceHandler) { HTTP2SourceHandler http2SourceHandler = (HTTP2SourceHandler) ctx.handler(); http2SourceHandler.encoder().writeHeaders(ctx, streamId, http2Headers, 0, false, ctx.newPromise()); try { if (cMsg instanceof HTTPCarbonMessage) { HTTPCarbonMessage nettyCMsg = (HTTPCarbonMessage) cMsg; while (true) { if (nettyCMsg.isEndOfMsgAdded() && nettyCMsg.isEmpty()) { http2SourceHandler.encoder().writeData(ctx, streamId, Unpooled.EMPTY_BUFFER, 0, true, ctx.newPromise()); http2SourceHandler.flush(ctx); break; } HttpContent httpContent = nettyCMsg.getHttpContent(); if (httpContent instanceof LastHttpContent) { http2SourceHandler.encoder().writeData(ctx, streamId, httpContent.content(), 0, true, ctx.newPromise()); http2SourceHandler.flush(ctx); if (HTTPTransportContextHolder.getInstance().getHandlerExecutor() != null) { HTTPTransportContextHolder.getInstance().getHandlerExecutor(). executeAtSourceResponseSending(cMsg); } break; } http2SourceHandler.encoder().writeData(ctx, streamId, httpContent.content(), 0, false, ctx.newPromise()); } } else if (cMsg instanceof DefaultCarbonMessage) { DefaultCarbonMessage defaultCMsg = (DefaultCarbonMessage) cMsg; if (defaultCMsg.isEndOfMsgAdded() && defaultCMsg.isEmpty()) { http2SourceHandler.encoder().writeData(ctx, streamId, Unpooled.EMPTY_BUFFER, 0, true, ctx.newPromise()); http2SourceHandler.flush(ctx); return; } while (true) { ByteBuffer byteBuffer = defaultCMsg.getMessageBody(); ByteBuf bbuf = Unpooled.wrappedBuffer(byteBuffer); http2SourceHandler.encoder().writeData(ctx, streamId, bbuf, 0, false, ctx .newPromise()); if (defaultCMsg.isEndOfMsgAdded() && defaultCMsg.isEmpty()) { ChannelFuture future = http2SourceHandler.encoder().writeData(ctx, streamId, Unpooled .EMPTY_BUFFER, 0, true, ctx.newPromise()); http2SourceHandler.flush(ctx); if (HTTPTransportContextHolder.getInstance().getHandlerExecutor() != null) { HTTPTransportContextHolder.getInstance().getHandlerExecutor(). executeAtSourceResponseSending(cMsg); } String connection = cMsg.getHeader(Constants.HTTP_CONNECTION); if (connection != null && Constants.HTTP_CONNECTION_CLOSE.equalsIgnoreCase(connection)) { future.addListener(ChannelFutureListener.CLOSE); } break; } } } else { } } catch (Http2Exception e) { logger.error("Error occurred while sending response to client", e); } } } /** * Handles the response without content length and set or remove headers based on carbon message * * @param cMsg Carbon Message */ private void handleResponsesWithoutContentLength(CarbonMessage cMsg) { if (cMsg.isAlreadyRead()) { MessageDataSource messageDataSource = cMsg.getMessageDataSource(); if (messageDataSource != null) { messageDataSource.serializeData(); cMsg.setEndOfMsgAdded(true); cMsg.getHeaders().remove(Constants.HTTP_CONTENT_LENGTH); } else { logger.error("Message is already built but cannot find the MessageDataSource"); } } if (cMsg.getHeader(Constants.HTTP_TRANSFER_ENCODING) == null && cMsg.getHeader(Constants.HTTP_CONTENT_LENGTH) == null) { cMsg.setHeader(Constants.HTTP_CONTENT_LENGTH, String.valueOf(cMsg.getFullMessageLength())); } } /** * Create HTTP/2 Headers for response * * @param msg Carbon Message * @return HTTP/2 Headers */ private Http2Headers createHttp2Headers(CarbonMessage msg) { Http2Headers http2Headers = new DefaultHttp2Headers() .status(String.valueOf(Util.getIntValue(msg, Constants.HTTP_STATUS_CODE, 200))) .method(Util.getStringValue(msg, Constants.HTTP_METHOD, DEFAULT_HTTP_METHOD_POST)) .path(msg.getProperty(Constants.TO) != null ? msg.getProperty(Constants.TO).toString() : "/") .scheme(msg.getProperty(Constants.SCHEME) != null ? msg.getProperty(Constants.SCHEME).toString() : Constants.PROTOCOL_NAME); //set Authority Header if (msg.getProperty(Constants.AUTHORITY) != null) { http2Headers.authority(Constants.AUTHORITY); } else { http2Headers.authority(((InetSocketAddress) ctx.channel().remoteAddress()).getHostName()); } msg.getHeaders().getAll().forEach(k -> http2Headers.add(k.getName().toLowerCase(), k.getValue())); return http2Headers; } }