/**
* 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 org.apache.camel.component.netty.http.handlers;
import java.util.Map;
import org.apache.camel.Exchange;
import org.apache.camel.Message;
import org.apache.camel.component.netty.NettyConstants;
import org.apache.camel.component.netty.handlers.ClientChannelHandler;
import org.apache.camel.component.netty.http.NettyHttpProducer;
import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.buffer.ChannelBuffers;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.MessageEvent;
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.HttpResponse;
import org.jboss.netty.handler.codec.http.HttpResponseStatus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Netty HTTP {@link org.apache.camel.component.netty.handlers.ClientChannelHandler} that handles the response combing
* back from the HTTP server, called by this client.
*
*/
public class HttpClientChannelHandler extends ClientChannelHandler {
// use NettyHttpProducer as logger to make it easier to read the logs as this is part of the producer
private static final Logger LOG = LoggerFactory.getLogger(NettyHttpProducer.class);
private final NettyHttpProducer producer;
private HttpResponse response;
private ChannelBuffer buffer;
public HttpClientChannelHandler(NettyHttpProducer producer) {
super(producer);
this.producer = producer;
}
@Override
public void messageReceived(ChannelHandlerContext ctx, MessageEvent messageEvent) throws Exception {
// store response, as this channel handler is created per pipeline
Object msg = messageEvent.getMessage();
// it may be a chunked message
if (msg instanceof HttpChunk) {
HttpChunk chunk = (HttpChunk) msg;
if (LOG.isTraceEnabled()) {
LOG.trace("HttpChunk received: {} isLast: {}", chunk, chunk.isLast());
}
if (msg instanceof HttpChunkTrailer) {
// chunk trailer only has headers
HttpChunkTrailer trailer = (HttpChunkTrailer) msg;
for (Map.Entry<String, String> entry : trailer.trailingHeaders()) {
if (LOG.isTraceEnabled()) {
LOG.trace("Adding trailing header {}={}", entry.getKey(), entry.getValue());
}
response.headers().add(entry.getKey(), entry.getValue());
}
} else {
// append chunked content
buffer.writeBytes(chunk.getContent());
if (LOG.isTraceEnabled()) {
LOG.trace("Wrote {} bytes to chunk buffer", buffer.writerIndex());
}
}
if (chunk.isLast()) {
// the content is a copy of the buffer with the actual data we wrote to it
int end = buffer.writerIndex();
ChannelBuffer copy = buffer.copy(0, end);
// the copy must not be readable when the content was chunked, so set the index to the end
copy.setIndex(end, end);
response.setContent(copy);
// we get the all the content now, so call super to process the received message
super.messageReceived(ctx, messageEvent);
}
} else if (msg instanceof HttpResponse) {
response = (HttpResponse) msg;
Exchange exchange = super.getExchange(ctx);
if (!HttpHeaders.isKeepAlive(response)) {
// just want to make sure we close the channel if the keepAlive is not true
exchange.setProperty(NettyConstants.NETTY_CLOSE_CHANNEL_WHEN_COMPLETE, true);
}
if (LOG.isTraceEnabled()) {
LOG.trace("HttpResponse received: {} chunked:", response, response.isChunked());
}
if (response.getStatus().getCode() == HttpResponseStatus.CONTINUE.getCode()) {
if (LOG.isTraceEnabled()) {
LOG.trace("HttpResponse received: {}: {}", response, response.getStatus());
}
} else if (!response.isChunked()) {
// the response is not chunked so we have all the content
super.messageReceived(ctx, messageEvent);
} else {
// the response is chunkced so use a dynamic buffer to receive the content in chunks
buffer = ChannelBuffers.dynamicBuffer();
}
} else {
// ignore not supported message
if (LOG.isTraceEnabled() && msg != null) {
LOG.trace("Ignoring non supported response message of type {} -> {}", msg.getClass(), msg);
}
}
}
@Override
protected Message getResponseMessage(Exchange exchange, MessageEvent messageEvent) throws Exception {
// use the binding
return producer.getEndpoint().getNettyHttpBinding().toCamelMessage(response, exchange, producer.getConfiguration());
}
}