/* * Copyright (c) 2014 AsyncHttpClient Project. All rights reserved. * * This program is licensed to you under the Apache License Version 2.0, * and you may not use this file except in compliance with the Apache License Version 2.0. * You may obtain a copy of the Apache License Version 2.0 at * http://www.apache.org/licenses/LICENSE-2.0. * * Unless required by applicable law or agreed to in writing, * software distributed under the Apache License Version 2.0 is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. */ package org.asynchttpclient.netty.handler; import static org.asynchttpclient.util.MiscUtils.getCause; import io.netty.buffer.ByteBuf; import io.netty.channel.Channel; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.handler.codec.PrematureChannelClosureException; import io.netty.handler.codec.http.HttpContent; import io.netty.handler.codec.http.LastHttpContent; import io.netty.util.ReferenceCountUtil; import java.io.IOException; import java.nio.channels.ClosedChannelException; import org.asynchttpclient.AsyncHttpClientConfig; import org.asynchttpclient.HttpResponseBodyPart; import org.asynchttpclient.exception.ChannelClosedException; import org.asynchttpclient.netty.OnLastHttpContentCallback; import org.asynchttpclient.netty.DiscardEvent; import org.asynchttpclient.netty.NettyResponseFuture; import org.asynchttpclient.netty.channel.ChannelManager; import org.asynchttpclient.netty.channel.Channels; import org.asynchttpclient.netty.future.StackTraceInspector; import org.asynchttpclient.netty.handler.intercept.Interceptors; import org.asynchttpclient.netty.request.NettyRequestSender; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public abstract class AsyncHttpClientHandler extends ChannelInboundHandlerAdapter { protected final Logger logger = LoggerFactory.getLogger(getClass()); protected final AsyncHttpClientConfig config; protected final ChannelManager channelManager; protected final NettyRequestSender requestSender; protected final Interceptors interceptors; protected final boolean hasIOExceptionFilters; public AsyncHttpClientHandler(AsyncHttpClientConfig config,// ChannelManager channelManager,// NettyRequestSender requestSender) { this.config = config; this.channelManager = channelManager; this.requestSender = requestSender; interceptors = new Interceptors(config, channelManager, requestSender); hasIOExceptionFilters = !config.getIoExceptionFilters().isEmpty(); } @Override public void channelRead(final ChannelHandlerContext ctx, Object msg) throws Exception { Channel channel = ctx.channel(); Object attribute = Channels.getAttribute(channel); try { if (attribute instanceof OnLastHttpContentCallback && msg instanceof LastHttpContent) { ((OnLastHttpContentCallback) attribute).call(); } else if (attribute instanceof NettyResponseFuture) { NettyResponseFuture<?> future = (NettyResponseFuture<?>) attribute; future.touch(); handleRead(channel, future, msg); } else if (attribute instanceof StreamedResponsePublisher) { StreamedResponsePublisher publisher = (StreamedResponsePublisher) attribute; publisher.future().touch(); if (msg instanceof HttpContent) { ByteBuf content = ((HttpContent) msg).content(); // Republish as a HttpResponseBodyPart if (content.readableBytes() > 0) { HttpResponseBodyPart part = config.getResponseBodyPartFactory().newResponseBodyPart(content, false); ctx.fireChannelRead(part); } if (msg instanceof LastHttpContent) { // Remove the handler from the pipeline, this will trigger // it to finish ctx.pipeline().remove(publisher); // Trigger a read, just in case the last read complete // triggered no new read ctx.read(); // Send the last content on to the protocol, so that it can // conclude the cleanup handleRead(channel, publisher.future(), msg); } } else { logger.info("Received unexpected message while expecting a chunk: " + msg); ctx.pipeline().remove(publisher); Channels.setDiscard(channel); } } else if (attribute != DiscardEvent.INSTANCE) { // unhandled message logger.debug("Orphan channel {} with attribute {} received message {}, closing", channel, attribute, msg); Channels.silentlyCloseChannel(channel); } } finally { ReferenceCountUtil.release(msg); } } public void channelInactive(ChannelHandlerContext ctx) throws Exception { if (requestSender.isClosed()) return; Channel channel = ctx.channel(); channelManager.removeAll(channel); Object attribute = Channels.getAttribute(channel); logger.debug("Channel Closed: {} with attribute {}", channel, attribute); if (attribute instanceof StreamedResponsePublisher) { // setting `attribute` to be the underlying future so that the retry // logic can kick-in attribute = ((StreamedResponsePublisher) attribute).future(); } if (attribute instanceof OnLastHttpContentCallback) { OnLastHttpContentCallback callback = (OnLastHttpContentCallback) attribute; Channels.setAttribute(channel, callback.future()); callback.call(); } else if (attribute instanceof NettyResponseFuture<?>) { NettyResponseFuture<?> future = (NettyResponseFuture<?>) attribute; future.touch(); if (hasIOExceptionFilters && requestSender.applyIoExceptionFiltersAndReplayRequest(future, ChannelClosedException.INSTANCE, channel)) return; handleChannelInactive(future); requestSender.handleUnexpectedClosedChannel(channel, future); } } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable e) throws Exception { Throwable cause = getCause(e); if (cause instanceof PrematureChannelClosureException || cause instanceof ClosedChannelException) return; Channel channel = ctx.channel(); NettyResponseFuture<?> future = null; logger.debug("Unexpected I/O exception on channel {}", channel, cause); try { Object attribute = Channels.getAttribute(channel); if (attribute instanceof StreamedResponsePublisher) { ctx.fireExceptionCaught(e); // setting `attribute` to be the underlying future so that the // retry logic can kick-in attribute = ((StreamedResponsePublisher) attribute).future(); } if (attribute instanceof NettyResponseFuture<?>) { future = (NettyResponseFuture<?>) attribute; future.attachChannel(null, false); future.touch(); if (cause instanceof IOException) { // FIXME why drop the original exception and throw a new one? if (hasIOExceptionFilters) { if (!requestSender.applyIoExceptionFiltersAndReplayRequest(future, ChannelClosedException.INSTANCE, channel)) { // Close the channel so the recovering can occurs. Channels.silentlyCloseChannel(channel); } return; } } if (StackTraceInspector.recoverOnReadOrWriteException(cause)) { logger.debug("Trying to recover from dead Channel: {}", channel); future.pendingException = cause; return; } } else if (attribute instanceof OnLastHttpContentCallback) { future = OnLastHttpContentCallback.class.cast(attribute).future(); } } catch (Throwable t) { cause = t; } if (future != null) try { logger.debug("Was unable to recover Future: {}", future); requestSender.abort(channel, future, cause); handleException(future, e); } catch (Throwable t) { logger.error(t.getMessage(), t); } channelManager.closeChannel(channel); // FIXME not really sure // ctx.fireChannelRead(e); Channels.silentlyCloseChannel(channel); } @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { ctx.read(); } @Override public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { if (!isHandledByReactiveStreams(ctx)) { ctx.read(); } else { ctx.fireChannelReadComplete(); } } private boolean isHandledByReactiveStreams(ChannelHandlerContext ctx) { return Channels.getAttribute(ctx.channel()) instanceof StreamedResponsePublisher; } protected void finishUpdate(NettyResponseFuture<?> future, Channel channel, boolean keepAlive, boolean expectOtherChunks) throws IOException { future.cancelTimeouts(); if (!keepAlive) { channelManager.closeChannel(channel); } else if (expectOtherChunks) { channelManager.drainChannelAndOffer(channel, future); } else { channelManager.tryToOfferChannelToPool(channel, future.getAsyncHandler(), keepAlive, future.getPartitionKey()); } try { future.done(); } catch (Exception t) { // Never propagate exception once we know we are done. logger.debug(t.getMessage(), t); } } public abstract void handleRead(Channel channel, NettyResponseFuture<?> future, Object message) throws Exception; public abstract void handleException(NettyResponseFuture<?> future, Throwable error); public abstract void handleChannelInactive(NettyResponseFuture<?> future); }