View Javadoc

    1   /*******************************************************************************
    2    * Copyright (c) 2011 Michael Mimo Moratti.
    3    *
    4    * Michael Mimo Moratti licenses this file to you under the Apache License, version 2.0
    5    * (the "License"); you may not use this file except in compliance with the License.
    6    * You may obtain a copy of the License at:
    7    *     http://www.apache.org/licenses/LICENSE-2.0
    8    * Unless required by applicable law or agreed to in writing, software
    9    * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    10   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
    11   * License for the specific language governing permissions and limitations
    12   * under the License.
    13   *******************************************************************************/
    14  package ch.mimo.netty.handler.codec.icap;
    15
    16  import org.jboss.netty.buffer.ChannelBuffer;
    17  import org.jboss.netty.buffer.ChannelBuffers;
    18  import org.jboss.netty.channel.ChannelHandlerContext;
    19  import org.jboss.netty.channel.Channels;
    20  import org.jboss.netty.channel.MessageEvent;
    21  import org.jboss.netty.channel.SimpleChannelUpstreamHandler;
    22  import org.jboss.netty.handler.codec.frame.TooLongFrameException;
    23  import org.jboss.netty.handler.codec.http.HttpMessage;
    24  import org.jboss.netty.logging.InternalLogger;
    25  import org.jboss.netty.logging.InternalLoggerFactory;
    26
    27  /**
    28   * This ICAP chunk aggregator will combine an received ICAP message with all body chunks.
    29   * the body is the to be found attached to the correct HTTP request or response instance
    30   * within the ICAP message.
    31   *
    32   * @author Michael Mimo Moratti (mimo@mimo.ch)
    33   *
    34   * @see IcapChunkSeparator
    35   *
    36   */
    37  public class IcapChunkAggregator extends SimpleChannelUpstreamHandler {
    38
    39  	private static final InternalLogger LOG = InternalLoggerFactory.getInstance(IcapChunkAggregator.class);
    40
    41  	private long maxContentLength;
    42  	private IcapMessageWrapper message;
    43
    44  	/**
    45  	 * Convenience method to retrieve a HTTP request,response or
    46  	 * an ICAP options response body from an aggregated IcapMessage.
    47  	 * @param message
    48  	 * @return null or @see {@link ChannelBuffer} if a body exists.
    49  	 */
    50  	public static ChannelBuffer extractHttpBodyContentFromIcapMessage(IcapMessage message) {
    51  		ChannelBuffer buffer = null;
    52  		if(message != null) {
    53  			if(message.getHttpRequest() != null && message.getHttpRequest().getContent().readableBytes() > 0) {
    54  				buffer = message.getHttpRequest().getContent();
    55  			} else if(message.getHttpResponse() != null && message.getHttpResponse().getContent().readableBytes() > 0) {
    56  				buffer = message.getHttpResponse().getContent();
    57  			} else if(message instanceof IcapResponse) {
    58  				if(((IcapResponse) message).getContent().readableBytes() > 0) {
    59  					buffer = ((IcapResponse) message).getContent();
    60  				}
    61  			}
    62  		}
    63  		return buffer;
    64  	}
    65
    66  	/**
    67  	 * @param maxContentLength defines the maximum length of the body content that is allowed.
    68  	 * If the length is exceeded an exception is thrown.
    69  	 */
    70  	public IcapChunkAggregator(long maxContentLength) {
    71  		this.maxContentLength = maxContentLength;
    72  	}
    73
    74      @Override
    75      public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {
    76      	Object msg = e.getMessage();
    77      	if(msg instanceof IcapMessage) {
    78      		LOG.debug("Aggregation of message [" + msg.getClass().getName() + "] ");
    79      		IcapMessage currentMessage = (IcapMessage)msg;
    80      		message = new IcapMessageWrapper(currentMessage);
    81      		if(!message.hasBody()) {
    82      			Channels.fireMessageReceived(ctx,message.getIcapMessage(),e.getRemoteAddress());
    83      			message = null;
    84      			return;
    85      		}
    86      	} else if(msg instanceof IcapChunkTrailer) {
    87      		LOG.debug("Aggregation of chunk trailer [" + msg.getClass().getName() + "] ");
    88      		if(message == null) {
    89      			ctx.sendUpstream(e);
    90      		} else {
    91      			IcapChunkTrailer trailer = (IcapChunkTrailer)msg;
    92      			if(trailer.getHeaderNames().size() > 0) {
    93      				for(String name : trailer.getHeaderNames()) {
    94      					message.addHeader(name,trailer.getHeader(name));
    95      				}
    96      			}
    97      			Channels.fireMessageReceived(ctx,message.getIcapMessage(),e.getRemoteAddress());
    98      		}
    99      	} else if(msg instanceof IcapChunk) {
    100     		LOG.debug("Aggregation of chunk [" + msg.getClass().getName() + "] ");
    101     		IcapChunk chunk = (IcapChunk)msg;
    102     		if(message == null) {
    103     			ctx.sendUpstream(e);
    104     		} else if(chunk.isLast()) {
    105     			Channels.fireMessageReceived(ctx,message.getIcapMessage(),e.getRemoteAddress());
    106     			message = null;
    107     		} else {
    108 	    		ChannelBuffer chunkBuffer = chunk.getContent();
    109 	    		ChannelBuffer content = message.getContent();
    110     			if(content.readableBytes() > maxContentLength - chunkBuffer.readableBytes()) {
    111     				throw new TooLongFrameException("ICAP content length exceeded [" + maxContentLength + "] bytes");
    112     			} else {
    113     				content.writeBytes(chunkBuffer);
    114     			}
    115     		}
    116     	} else {
    117     		ctx.sendUpstream(e);
    118     	}
    119     }
    120
    121     private final class IcapMessageWrapper {
    122
    123     	private IcapMessage message;
    124     	private HttpMessage relevantHttpMessage;
    125     	private IcapResponse icapResponse;
    126     	private boolean messageWithBody;
    127
    128     	public IcapMessageWrapper(IcapMessage message) {
    129     		this.message = message;
    130     		if(message.getBodyType() != null) {
    131 	    		if(message.getBodyType().equals(IcapMessageElementEnum.REQBODY)) {
    132 	    			relevantHttpMessage = message.getHttpRequest();
    133 	    			messageWithBody = true;
    134 	    		} else if(message.getBodyType().equals(IcapMessageElementEnum.RESBODY)) {
    135 	    			relevantHttpMessage = message.getHttpResponse();
    136 	    			messageWithBody = true;
    137 	    		} else if(message instanceof IcapResponse && message.getBodyType().equals(IcapMessageElementEnum.OPTBODY)) {
    138 	    			icapResponse = (IcapResponse)message;
    139 	    			messageWithBody = true;
    140 	    		}
    141     		}
    142     		if(messageWithBody) {
    143     			if(relevantHttpMessage != null) {
    144 	    			if(relevantHttpMessage.getContent() == null || relevantHttpMessage.getContent().readableBytes() <= 0) {
    145 	    				relevantHttpMessage.setContent(ChannelBuffers.dynamicBuffer());
    146 	    			}
    147     			} else if(icapResponse != null) {
    148     				if(icapResponse.getContent() == null || icapResponse.getContent().readableBytes() <= 0) {
    149     					icapResponse.setContent(ChannelBuffers.dynamicBuffer());
    150     				}
    151     			}
    152     		}
    153     	}
    154
    155     	public boolean hasBody() {
    156     		return messageWithBody;
    157     	}
    158
    159     	public IcapMessage getIcapMessage() {
    160     		return message;
    161     	}
    162
    163     	public void addHeader(String name, String value) {
    164     		if(messageWithBody) {
    165     			relevantHttpMessage.addHeader(name,value);
    166     		} else {
    167     			throw new IcapDecodingError("A message without body cannot carry trailing headers.");
    168     		}
    169     	}
    170
    171     	public ChannelBuffer getContent() {
    172     		if(messageWithBody) {
    173     			if(relevantHttpMessage != null) {
    174     				return relevantHttpMessage.getContent();
    175     			} else if(icapResponse != null) {
    176     				return icapResponse.getContent();
    177     			}
    178     		}
    179     		throw new IcapDecodingError("Message stated that there is a body but nothing found in message.");
    180     	}
    181     }
    182 }