/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF 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 com.alipay.sofa.rpc.transport.http; import com.alipay.sofa.rpc.common.utils.NetUtils; import com.alipay.sofa.rpc.core.exception.RpcErrorType; import com.alipay.sofa.rpc.core.exception.SofaRpcException; import com.alipay.sofa.rpc.log.LogCodes; import com.alipay.sofa.rpc.log.Logger; import com.alipay.sofa.rpc.log.LoggerFactory; import com.alipay.sofa.rpc.transport.netty.NettyHelper; import io.netty.channel.Channel; 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.http.HttpHeaders; import io.netty.handler.codec.http2.HttpConversionUtil; import io.netty.util.internal.PlatformDependent; import java.util.AbstractMap.SimpleEntry; import java.util.Iterator; import java.util.Map; import java.util.Map.Entry; /** * Process {@link FullHttpResponse} translated from HTTP/2 frames * * @author <a href="mailto:[email protected]">GengZhang</a> * @since 5.4.0 */ public class Http2ClientChannelHandler extends SimpleChannelInboundHandler<FullHttpResponse> { /** * Logger for HttpClientChannelHandler **/ private static final Logger LOGGER = LoggerFactory .getLogger(Http2ClientChannelHandler.class); /** * */ private final Map<Integer, Entry<ChannelFuture, AbstractHttpClientHandler>> streamIdPromiseMap; public Http2ClientChannelHandler() { // Use a concurrent map because we add and iterate from the main thread (just for the purposes of the example), // but Netty also does a get on the map when messages are received in a EventLoop thread. streamIdPromiseMap = PlatformDependent.newConcurrentHashMap(); } /** * Create an association between an anticipated response stream id and a {@link 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, AbstractHttpClientHandler> put(int streamId, ChannelFuture writeFuture, AbstractHttpClientHandler promise) { return streamIdPromiseMap.put(streamId, new SimpleEntry<ChannelFuture, AbstractHttpClientHandler>( writeFuture, promise)); } @Override protected void channelRead0(ChannelHandlerContext ctx, FullHttpResponse msg) throws Exception { HttpHeaders headers = msg.headers(); Integer streamId = headers.getInt(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text()); if (streamId == null) { if (LOGGER.isWarnEnabled()) { LOGGER.warn("HttpResponseHandler unexpected message received: {}, data is {}", msg.toString(), NettyHelper.toString(msg.content())); } return; } Entry<ChannelFuture, AbstractHttpClientHandler> entry = removePromise(streamId); if (entry == null) { if (LOGGER.isWarnEnabled()) { LOGGER.warn("Message received for unknown stream id {}, msg is {}, data is {}", streamId, msg.toString(), NettyHelper.toString(msg.content())); } } else { final AbstractHttpClientHandler callback = entry.getValue(); callback.receiveHttpResponse(msg); } } @Override public void channelInactive(final ChannelHandlerContext ctx) throws Exception { Channel channel = ctx.channel(); if (LOGGER.isInfoEnabled()) { LOGGER.info("Channel inactive: {}", channel); } final Exception e = new SofaRpcException(RpcErrorType.CLIENT_NETWORK, "Channel " + NetUtils.channelToString(channel.localAddress(), channel.remoteAddress()) + " has been closed, remove future when channel inactive."); Iterator<Entry<Integer, Entry<ChannelFuture, AbstractHttpClientHandler>>> it = streamIdPromiseMap.entrySet().iterator(); while (it.hasNext()) { Map.Entry<Integer, Entry<ChannelFuture, AbstractHttpClientHandler>> mapEntry = it.next(); it.remove(); Entry<ChannelFuture, AbstractHttpClientHandler> entry = mapEntry.getValue(); entry.getValue().onException(e); } } public Entry<ChannelFuture, AbstractHttpClientHandler> removePromise(int streamId) { return streamIdPromiseMap.remove(streamId); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { LOGGER.error(LogCodes.getLog(LogCodes.ERROR_CATCH_EXCEPTION), cause); } }