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.getBodyType().equals(IcapMessageElementEnum.REQBODY) && message.getHttpRequest() != null) {
53  			buffer = message.getHttpRequest().getContent();
54  		} else if(message.getBodyType().equals(IcapMessageElementEnum.RESBODY) && message.getHttpResponse() != null) {
55  			buffer = message.getHttpResponse().getContent();
56  		} else if(message instanceof IcapResponse && message.getBodyType().equals(IcapMessageElementEnum.OPTBODY)) {
57  			IcapResponse response = (IcapResponse)message;
58  			buffer = response.getContent();
59  		}
60  		return buffer;
61  	}
62  	
63  	/**
64  	 * @param maxContentLength defines the maximum length of the body content that is allowed. 
65  	 * If the length is exceeded an exception is thrown.
66  	 */
67  	public IcapChunkAggregator(long maxContentLength) {
68  		this.maxContentLength = maxContentLength;
69  	}
70  	
71      @Override
72      public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {
73      	Object msg = e.getMessage();
74      	if(msg instanceof IcapMessage) {
75      		LOG.debug("Aggregation of message [" + msg.getClass().getName() + "] ");
76      		IcapMessage currentMessage = (IcapMessage)msg;
77      		message = new IcapMessageWrapper(currentMessage);
78      		if(!message.hasBody()) {
79      			Channels.fireMessageReceived(ctx,message.getIcapMessage(),e.getRemoteAddress());
80      			message = null;
81      			return;
82      		}
83      	} else if(msg instanceof IcapChunkTrailer) {
84      		LOG.debug("Aggregation of chunk trailer [" + msg.getClass().getName() + "] ");
85      		if(message == null) {
86      			ctx.sendUpstream(e);
87      		} else {
88      			IcapChunkTrailer trailer = (IcapChunkTrailer)msg;
89      			if(trailer.getHeaderNames().size() > 0) {		
90      				for(String name : trailer.getHeaderNames()) {
91      					message.addHeader(name,trailer.getHeader(name));
92      				}
93      			}
94      			Channels.fireMessageReceived(ctx,message.getIcapMessage(),e.getRemoteAddress());
95      		}
96      	} else if(msg instanceof IcapChunk) {
97      		LOG.debug("Aggregation of chunk [" + msg.getClass().getName() + "] ");
98      		IcapChunk chunk = (IcapChunk)msg;
99      		if(message == null) {
100     			ctx.sendUpstream(e);
101     		} else if(chunk.isLast()) {
102     			Channels.fireMessageReceived(ctx,message.getIcapMessage(),e.getRemoteAddress());
103     			message = null;
104     		} else {
105 	    		ChannelBuffer chunkBuffer = chunk.getContent();
106 	    		ChannelBuffer content = message.getContent();
107     			if(content.readableBytes() > maxContentLength - chunkBuffer.readableBytes()) {
108     				throw new TooLongFrameException("ICAP content length exceeded [" + maxContentLength + "] bytes");
109     			} else {
110     				content.writeBytes(chunkBuffer);
111     			}
112     		}
113     	} else {
114     		ctx.sendUpstream(e);
115     	}
116     }
117     
118     private final class IcapMessageWrapper {
119     	
120     	private IcapMessage message;
121     	private HttpMessage relevantHttpMessage;
122     	private IcapResponse icapResponse;
123     	private boolean messageWithBody;
124     	
125     	public IcapMessageWrapper(IcapMessage message) {
126     		this.message = message;
127     		if(message.getBodyType() != null) {
128 	    		if(message.getBodyType().equals(IcapMessageElementEnum.REQBODY)) {
129 	    			relevantHttpMessage = message.getHttpRequest();
130 	    			messageWithBody = true;
131 	    		} else if(message.getBodyType().equals(IcapMessageElementEnum.RESBODY)) {
132 	    			relevantHttpMessage = message.getHttpResponse();
133 	    			messageWithBody = true;
134 	    		} else if(message instanceof IcapResponse && message.getBodyType().equals(IcapMessageElementEnum.OPTBODY)) {
135 	    			icapResponse = (IcapResponse)message;
136 	    			messageWithBody = true;
137 	    		}
138     		}
139     		if(messageWithBody) {
140     			if(relevantHttpMessage != null) {
141 	    			if(relevantHttpMessage.getContent() == null || relevantHttpMessage.getContent().readableBytes() <= 0) {
142 	    				relevantHttpMessage.setContent(ChannelBuffers.dynamicBuffer());
143 	    			}
144     			} else if(icapResponse != null) {
145     				if(icapResponse.getContent() == null || icapResponse.getContent().readableBytes() <= 0) {
146     					icapResponse.setContent(ChannelBuffers.dynamicBuffer());
147     				}
148     			}
149     		}
150     	}
151     	
152     	public boolean hasBody() {
153     		return messageWithBody;
154     	}
155     	
156     	public IcapMessage getIcapMessage() {
157     		return message;
158     	}
159     	
160     	public void addHeader(String name, String value) {
161     		if(messageWithBody) {
162     			relevantHttpMessage.addHeader(name,value);
163     		} else {
164     			throw new IcapDecodingError("A message without body cannot carry trailing headers.");
165     		}
166     	}
167     	
168     	public ChannelBuffer getContent() {
169     		if(messageWithBody) {
170     			if(relevantHttpMessage != null) {
171     				return relevantHttpMessage.getContent();
172     			} else if(icapResponse != null) {
173     				return icapResponse.getContent();
174     			}
175     		}
176     		throw new IcapDecodingError("Message stated that there is a body but nothing found in message.");
177     	}
178     }
179 }