/* * 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.util.client.http2; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelPromise; import io.netty.channel.SimpleChannelInboundHandler; import io.netty.handler.codec.http.FullHttpResponse; import io.netty.handler.codec.http2.HttpConversionUtil; import io.netty.util.CharsetUtil; import io.netty.util.internal.PlatformDependent; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.wso2.carbon.transport.http.netty.util.TestUtil; import java.util.AbstractMap.SimpleEntry; import java.util.Map; import java.util.Map.Entry; /** * Process {@link io.netty.handler.codec.http.FullHttpResponse} translated from HTTP/2 frames */ public class HTTP2ResponseHandler extends SimpleChannelInboundHandler<FullHttpResponse> { private static final Logger log = LoggerFactory.getLogger(HTTP2ResponseHandler.class); private Map<Integer, Entry<ChannelFuture, ChannelPromise>> streamIdPromiseMap; private Map<Integer, String> streamIdResponseMap; public HTTP2ResponseHandler() { // Use a concurrent map because we add and iterate from the main thread streamIdPromiseMap = PlatformDependent.newConcurrentHashMap(); streamIdResponseMap = PlatformDependent.newConcurrentHashMap(); } /** * Create an association between an anticipated response stream id and a {@link io.netty.channel.ChannelPromise} * * @param streamId The stream for which a response is expected * @param writeFuture A future that represent the request write operation * @param promise The promise object that will be used to wait/notify events * @return The previous object associated with {@code streamId} */ public Entry<ChannelFuture, ChannelPromise> put(int streamId, ChannelFuture writeFuture, ChannelPromise promise) { return streamIdPromiseMap.put(streamId, new SimpleEntry<ChannelFuture, ChannelPromise>(writeFuture, promise)); } @Override protected void channelRead0(ChannelHandlerContext ctx, FullHttpResponse msg) throws Exception { Integer streamId = msg.headers().getInt(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text()); if (streamId == null) { log.error("HTTP2ResponseHandler unexpected message received: " + msg); return; } Entry<ChannelFuture, ChannelPromise> entry = streamIdPromiseMap.get(streamId); if (entry == null) { if (streamId == 1) { log.error("HTTP2 Upgrade request has received from stream : " + streamId); } } else { ByteBuf content = msg.content(); if (content.isReadable()) { int contentLength = content.readableBytes(); byte[] arr = new byte[contentLength]; content.readBytes(arr); streamIdResponseMap.put(streamId, new String(arr, 0, contentLength, CharsetUtil.UTF_8)); } entry.getValue().setSuccess(); } } /** * Provide asynchronous response to HTTP2 request * * @param streamId StreamID * @return Response string */ public String getResponse(int streamId) { String message = streamIdResponseMap.get(streamId); if (message != null) { return message; } else { Entry<ChannelFuture, ChannelPromise> channelFutureChannelPromiseEntry = streamIdPromiseMap.get(streamId); if (channelFutureChannelPromiseEntry != null) { ChannelFuture writeFuture = channelFutureChannelPromiseEntry.getKey(); if (!writeFuture.awaitUninterruptibly(TestUtil.HTTP2_RESPONSE_TIME_OUT, TestUtil .HTTP2_RESPONSE_TIME_UNIT)) { streamIdPromiseMap.remove(streamId); throw new IllegalStateException("Timed out waiting to write for stream id " + streamId); } if (!writeFuture.isSuccess()) { streamIdPromiseMap.remove(streamId); throw new RuntimeException(writeFuture.cause()); } ChannelPromise promise = channelFutureChannelPromiseEntry.getValue(); if (!promise.awaitUninterruptibly(TestUtil.HTTP2_RESPONSE_TIME_OUT, TestUtil .HTTP2_RESPONSE_TIME_UNIT)) { streamIdPromiseMap.remove(streamId); throw new IllegalStateException("Timed out waiting for response on stream id " + streamId); } if (!promise.isSuccess()) { streamIdPromiseMap.remove(streamId); throw new RuntimeException(promise.cause()); } } } return streamIdResponseMap.get(streamId); } }