/**
 * Copyright 2007-2015, Kaazing Corporation. All rights reserved.
 *
 * Licensed 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.kaazing.k3po.driver.internal.netty.bootstrap.http;

import static java.lang.String.format;
import static org.jboss.netty.channel.Channels.fireChannelClosed;
import static org.jboss.netty.channel.Channels.fireChannelDisconnected;
import static org.jboss.netty.channel.Channels.fireChannelInterestChanged;
import static org.jboss.netty.channel.Channels.fireChannelUnbound;
import static org.jboss.netty.channel.Channels.fireExceptionCaught;
import static org.jboss.netty.channel.Channels.fireMessageReceived;
import static org.jboss.netty.handler.codec.http.HttpResponseStatus.SWITCHING_PROTOCOLS;
import static org.kaazing.k3po.driver.internal.netty.channel.Channels.fireInputShutdown;

import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelException;
import org.jboss.netty.channel.ChannelHandler;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.ChannelPipeline;
import org.jboss.netty.channel.ChannelStateEvent;
import org.jboss.netty.channel.ExceptionEvent;
import org.jboss.netty.channel.MessageEvent;
import org.jboss.netty.channel.SimpleChannelHandler;
import org.jboss.netty.handler.codec.http.HttpChunk;
import org.jboss.netty.handler.codec.http.HttpRequestEncoder;
import org.jboss.netty.handler.codec.http.HttpResponse;
import org.jboss.netty.handler.codec.http.HttpResponseDecoder;
import org.jboss.netty.handler.codec.http.HttpResponseStatus;


public class HttpClientChannelSource extends HttpChannelHandler {

    private HttpClientChannel httpClientChannel;

    public void setHttpChannel(HttpClientChannel httpClientChannel) {
        assert this.httpClientChannel == null;
        this.httpClientChannel = httpClientChannel;
    }

    @Override
    protected void httpMessageReceived(ChannelHandlerContext ctx, MessageEvent e, HttpResponse httpResponse) throws Exception {
        HttpChannelConfig httpChildConfig = httpClientChannel.getConfig();
        httpChildConfig.setStatus(httpResponse.getStatus());
        httpChildConfig.setVersion(httpResponse.getProtocolVersion());
        httpChildConfig.getReadHeaders().set(httpResponse.headers());

        if (httpResponse.getStatus().getCode() == SWITCHING_PROTOCOLS.getCode()) {
            Channel transport = ctx.getChannel();
            ChannelPipeline pipeline = transport.getPipeline();
            pipeline.remove(HttpRequestEncoder.class);

            boolean readable = httpClientChannel.isReadable();
            if (!readable) {
                httpClientChannel.setReadable(true);
                fireChannelInterestChanged(httpClientChannel);
            }

            // propagate any remaining bytes out of replaying decoder (after channel is marked as readable)
            ChannelHandlerContext httpDecoderCtx = pipeline.getContext(HttpResponseDecoder.class);
            HttpResponseDecoder httpDecoder = (HttpResponseDecoder) httpDecoderCtx.getHandler();
            httpDecoder.replace(format("%s.noop", httpDecoderCtx.getName()), NOOP_HANDLER);
        }
        else {
            ChannelBuffer content = httpResponse.getContent();
            boolean readable = httpClientChannel.isReadable();
            if (!readable) {
                httpClientChannel.setReadable(true);
                fireChannelInterestChanged(httpClientChannel);
            }

            if (content.readable()) {
                fireMessageReceived(httpClientChannel, content);
            }

            if (!httpResponse.isChunked()) {
                HttpClientChannel httpClientChannel = this.httpClientChannel;
                this.httpClientChannel = null;
                fireInputShutdown(httpClientChannel);

                boolean wasConnected = httpClientChannel.isConnected();
                boolean wasBound = httpClientChannel.isBound();
                if (httpClientChannel.setClosed()) {
                    if (wasConnected) {
                        fireChannelDisconnected(httpClientChannel);
                    }
                    if (wasBound) {
                        fireChannelUnbound(httpClientChannel);
                    }
                    fireChannelClosed(httpClientChannel);
                }
            }
        }
    }

    @Override
    protected void httpMessageReceived(ChannelHandlerContext ctx, MessageEvent e, HttpChunk httpChunk) throws Exception {
        ChannelBuffer content = httpChunk.getContent();
        if (content.readable()) {
            fireMessageReceived(httpClientChannel, content);
        }

        boolean last = httpChunk.isLast();
        if (last) {
            HttpClientChannel httpClientChannel = this.httpClientChannel;
            this.httpClientChannel = null;
            fireInputShutdown(httpClientChannel);

            if (httpClientChannel.setClosed()) {
                fireChannelClosed(httpClientChannel);
            }
        }
    }

    @Override
    protected void httpMessageReceived(ChannelHandlerContext ctx, MessageEvent e, ChannelBuffer message) throws Exception {
        if (message.readable()) {
            // after 101 switching protocols
            fireMessageReceived(httpClientChannel, message);
        }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception {
        if (httpClientChannel != null) {
            HttpClientChannel httpClientChannel = this.httpClientChannel;
            this.httpClientChannel = null;
            if (httpClientChannel.setClosed()) {
                fireExceptionCaught(httpClientChannel, e.getCause());
                fireChannelClosed(httpClientChannel);
            }
        }

        Channel channel = ctx.getChannel();
        channel.close();
    }

    @Override
    public void channelClosed(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {
        if (httpClientChannel != null) {
            HttpChannelConfig httpClientConfig = httpClientChannel.getConfig();
            HttpResponseStatus httpStatus = httpClientConfig.getStatus();
            int httpStatusCode = (httpStatus != null) ? httpStatus.getCode() : 0;
            if (httpStatusCode == SWITCHING_PROTOCOLS.getCode()) {
                if (httpClientChannel.setClosed()) {
                    fireChannelDisconnected(httpClientChannel);
                    fireChannelUnbound(httpClientChannel);
                    fireChannelClosed(httpClientChannel);
                }
            }
            else {
                ChannelException exception = new ChannelException("transport closed unexpectedly");
                exception.fillInStackTrace();
                fireExceptionCaught(httpClientChannel, exception);
            }
        }
    }

    @Sharable
    private static class NoopChannelHandler extends SimpleChannelHandler {
    }

    private static final ChannelHandler NOOP_HANDLER = new NoopChannelHandler();
}

@@ -5451,32 +5451,147 @@
 .setClosed()) %7B%0A
+                fireChannelDisconnected(httpClientChannel);%0A                fireChannelUnbound(httpClientChannel);%0A
                 

Add fix to properly fire channel disconnected and unbound via Http chunking