/** * 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.HttpChunkTrailer; import org.jboss.netty.handler.codec.http.HttpHeaders; 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; if (httpChunk instanceof HttpChunkTrailer) { HttpHeaders trailingHeaders = ((HttpChunkTrailer) httpChunk).trailingHeaders(); httpClientChannel.getConfig().getReadTrailers().set(trailingHeaders); } this.httpClientChannel = null; fireInputShutdown(httpClientChannel); if (httpClientChannel.setClosed()) { fireChannelDisconnected(httpClientChannel); fireChannelUnbound(httpClientChannel); 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(); }