/* * 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.ChannelFutureListener; import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.http.DefaultLastHttpContent; import io.netty.handler.codec.http.HttpServerUpgradeHandler; import io.netty.handler.codec.http2.DefaultHttp2Headers; import io.netty.handler.codec.http2.Http2ConnectionDecoder; import io.netty.handler.codec.http2.Http2ConnectionEncoder; import io.netty.handler.codec.http2.Http2ConnectionHandler; import io.netty.handler.codec.http2.Http2Exception; import io.netty.handler.codec.http2.Http2Flags; import io.netty.handler.codec.http2.Http2FrameListener; import io.netty.handler.codec.http2.Http2Headers; import io.netty.handler.codec.http2.Http2Settings; import io.netty.util.AsciiString; import io.netty.util.CharsetUtil; import io.netty.util.internal.PlatformDependent; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.wso2.carbon.messaging.CarbonCallback; import org.wso2.carbon.messaging.CarbonMessageProcessor; 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.internal.HTTPTransportContextHolder; import org.wso2.carbon.transport.http.netty.message.HTTPCarbonMessage; import org.wso2.carbon.transport.http.netty.sender.channel.pool.ConnectionManager; import java.net.InetSocketAddress; import java.util.Map; import static io.netty.handler.codec.http.HttpResponseStatus.OK; /** * Class {@code HTTP2SourceHandler} will read the Http2 binary frames sent from client through the channel * and build carbon messages and sent to message processor. */ public final class HTTP2SourceHandler extends Http2ConnectionHandler implements Http2FrameListener { private static final Logger log = LoggerFactory.getLogger(HTTP2SourceHandler.class); /** * streamIdRequestMap contains mapping of http carbon messages to streamid to support multiplexing capability of * http2 tcp connections */ private Map<Integer, HTTPCarbonMessage> streamIdRequestMap = PlatformDependent.newConcurrentHashMap(); private ConnectionManager connectionManager; private ListenerConfiguration listenerConfiguration; private ChannelHandlerContext ctx; HTTP2SourceHandler(Http2ConnectionDecoder decoder, Http2ConnectionEncoder encoder, Http2Settings initialSettings, ConnectionManager connectionManager, ListenerConfiguration listenerConfiguration) { super(decoder, encoder, initialSettings); this.listenerConfiguration = listenerConfiguration; this.connectionManager = connectionManager; } @Override public void handlerAdded(ChannelHandlerContext ctx) throws Exception { super.handlerAdded(ctx); this.ctx = ctx; } /** * This method handles the cleartext HTTP upgrade event. If an upgrade occurred, sends a simple response via HTTP/2 * on stream 1 (the stream specifically reserved for cleartext HTTP upgrade). * * @param ctx Channel context * @param evt Event * @throws Exception Throws when user event trigger has an error */ @Override public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { if (evt instanceof HttpServerUpgradeHandler.UpgradeEvent) { // Write an HTTP/2 response to the upgrade request Http2Headers headers = new DefaultHttp2Headers().status(OK.codeAsText()) .set(new AsciiString(Constants.UPGRADE_RESPONSE_HEADER), new AsciiString("true")); encoder().writeHeaders(ctx, 1, headers, 0, true, ctx.newPromise()); } super.userEventTriggered(ctx, evt); } @Override public int onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding, boolean endOfStream) throws Http2Exception { HTTPCarbonMessage cMsg = streamIdRequestMap.get(streamId); if (cMsg != null) { cMsg.addHttpContent(new DefaultLastHttpContent(data.retain())); if (endOfStream) { cMsg.setEndOfMsgAdded(true); if (HTTPTransportContextHolder.getInstance().getHandlerExecutor() != null) { HTTPTransportContextHolder.getInstance().getHandlerExecutor().executeAtSourceRequestSending(cMsg); } } } return data.readableBytes() + padding; } public void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers, int padding, boolean endOfStream) throws Http2Exception { HTTPCarbonMessage cMsg = publishToMessageProcessor(streamId, headers); if (endOfStream) { cMsg.setEndOfMsgAdded(true); if (HTTPTransportContextHolder.getInstance().getHandlerExecutor() != null) { HTTPTransportContextHolder.getInstance().getHandlerExecutor().executeAtSourceRequestSending(cMsg); } } } @Override public void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers, int streamDependency, short weight, boolean exclusive, int padding, boolean endOfStream) throws Http2Exception { onHeadersRead(ctx, streamId, headers, padding, endOfStream); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { if (ctx != null && ctx.channel().isActive()) { ctx.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE); } } @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(); } /** * Carbon Message is published to registered message processor and Message Processor should return transport * thread immediately * * @param streamId Stream id of HTTP2 request received * @param headers HTTP2 Headers * @return HTTPCarbonMessage */ private HTTPCarbonMessage publishToMessageProcessor(int streamId, Http2Headers headers) { HTTPCarbonMessage cMsg = setupCarbonMessage(streamId, headers); 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 HTTP2ResponseCallback(ctx, streamId)); } catch (Exception e) { log.error("Error while submitting CarbonMessage to CarbonMessageProcessor", e); } } else { log.error("Cannot find registered MessageProcessor for forward the message"); } } return cMsg; } /** * Setup carbon message for HTTP2 request * * @param streamId Stream id of HTTP2 request received * @param headers HTTP2 Headers * @return HTTPCarbonMessage */ protected HTTPCarbonMessage setupCarbonMessage(int streamId, Http2Headers headers) { // Construct new HTTP carbon message and put into stream id request map HTTPCarbonMessage cMsg = new HTTPCarbonMessage(); cMsg.setProperty(Constants.PORT, ((InetSocketAddress) ctx.channel().remoteAddress()).getPort()); cMsg.setProperty(Constants.HOST, ((InetSocketAddress) ctx.channel().remoteAddress()).getHostName()); cMsg.setProperty(Constants.SCHEME, listenerConfiguration.getScheme()); cMsg.setProperty(Constants.HTTP_VERSION, Constants.HTTP2_VERSION); 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) { cMsg.setProperty(Constants.IS_SECURED_CONNECTION, true); } else { cMsg.setProperty(Constants.IS_SECURED_CONNECTION, false); } 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()); ChannelHandler handler = ctx.handler(); cMsg.setProperty(Constants.CHANNEL_ID, ((HTTP2SourceHandler) handler).getListenerConfiguration().getId()); cMsg.setProperty(Constants.STREAM_ID, streamId); if (headers.path() != null) { String path = headers.getAndRemove(Constants.HTTP2_PATH).toString(); cMsg.setProperty(Constants.TO, path); cMsg.setProperty(Constants.REQUEST_URL, path); } if (headers.method() != null) { String method = headers.getAndRemove(Constants.HTTP2_METHOD).toString(); cMsg.setProperty(Constants.HTTP_METHOD, method); } // Remove PseudoHeaderNames from headers headers.getAndRemove(Constants.HTTP2_AUTHORITY); headers.getAndRemove(Constants.HTTP2_SCHEME); // Copy Http2 headers to carbon message headers.forEach(k -> cMsg.setHeader(k.getKey().toString(), k.getValue().toString())); streamIdRequestMap.put(streamId, cMsg); return cMsg; } @Override public void onPriorityRead(ChannelHandlerContext ctx, int streamId, int streamDependency, short weight, boolean exclusive) { } @Override public void onRstStreamRead(ChannelHandlerContext ctx, int streamId, long errorCode) { } @Override public void onSettingsAckRead(ChannelHandlerContext ctx) { } @Override public void onSettingsRead(ChannelHandlerContext ctx, Http2Settings settings) { } @Override public void onPingRead(ChannelHandlerContext ctx, ByteBuf data) { } @Override public void onPingAckRead(ChannelHandlerContext ctx, ByteBuf data) { } @Override public void onPushPromiseRead(ChannelHandlerContext ctx, int streamId, int promisedStreamId, Http2Headers headers, int padding) { } @Override public void onGoAwayRead(ChannelHandlerContext ctx, int lastStreamId, long errorCode, ByteBuf debugData) { if (log.isDebugEnabled()) { if (errorCode != 0 && debugData.isReadable()) { int contentLength = debugData.readableBytes(); byte[] arr = new byte[contentLength]; debugData.readBytes(arr); log.debug("Error occurred while closing the client connection " + new String(arr, 0, contentLength, CharsetUtil.UTF_8)); } } } @Override public void onWindowUpdateRead(ChannelHandlerContext ctx, int streamId, int windowSizeIncrement) { } @Override public void onUnknownFrame(ChannelHandlerContext ctx, byte frameType, int streamId, Http2Flags flags, ByteBuf payload) { } public ListenerConfiguration getListenerConfiguration() { return listenerConfiguration; } }