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 }