1 /*
2 * Copyright 2009 Red Hat, Inc.
3 *
4 * Red Hat 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
6 * License. You may obtain a copy of the License at:
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 * License for the specific language governing permissions and limitations
14 * under the License.
15 */
16 package org.jboss.netty.handler.codec.http2;
17
18 import static org.jboss.netty.channel.Channels.*;
19 import static org.jboss.netty.handler.codec.http2.HttpHeaders.*;
20
21 import java.util.List;
22 import java.util.Map.Entry;
23
24 import org.jboss.netty.buffer.ChannelBuffer;
25 import org.jboss.netty.buffer.ChannelBuffers;
26 import org.jboss.netty.channel.ChannelHandler;
27 import org.jboss.netty.channel.ChannelHandlerContext;
28 import org.jboss.netty.channel.ChannelPipeline;
29 import org.jboss.netty.channel.Channels;
30 import org.jboss.netty.channel.MessageEvent;
31 import org.jboss.netty.channel.SimpleChannelUpstreamHandler;
32 import org.jboss.netty.handler.codec.frame.TooLongFrameException;
33 import org.jboss.netty.util.CharsetUtil;
34
35 /**
36 * A {@link ChannelHandler} that aggregates an {@link HttpMessage}
37 * and its following {@link HttpChunk}s into a single {@link HttpMessage} with
38 * no following {@link HttpChunk}s. It is useful when you don't want to take
39 * care of HTTP messages whose transfer encoding is 'chunked'. Insert this
40 * handler after {@link HttpMessageDecoder} in the {@link ChannelPipeline}:
41 * <pre>
42 * {@link ChannelPipeline} p = ...;
43 * ...
44 * p.addLast("decoder", new {@link HttpRequestDecoder}());
45 * p.addLast("aggregator", <b>new {@link HttpChunkAggregator}(1048576)</b>);
46 * ...
47 * p.addLast("encoder", new {@link HttpResponseEncoder}());
48 * p.addLast("handler", new HttpRequestHandler());
49 * </pre>
50 *
51 * @author <a href="http://www.jboss.org/netty/">The Netty Project</a>
52 * @author <a href="http://gleamynode.net/">Trustin Lee</a>
53 * @version $Rev: 1107 $, $Date: 2012-04-15 19:00:57 +0200 (dim., 15 avr. 2012) $
54 *
55 * @apiviz.landmark
56 * @apiviz.has org.jboss.netty.handler.codec.http2.HttpChunk oneway - - filters out
57 */
58 public class HttpChunkAggregator extends SimpleChannelUpstreamHandler {
59
60 private static final ChannelBuffer CONTINUE = ChannelBuffers.copiedBuffer(
61 "HTTP/1.1 100 Continue\r\n\r\n", CharsetUtil.US_ASCII);
62
63 private final int maxContentLength;
64 private HttpMessage currentMessage;
65
66 /**
67 * Creates a new instance.
68 *
69 * @param maxContentLength
70 * the maximum length of the aggregated content.
71 * If the length of the aggregated content exceeds this value,
72 * a {@link TooLongFrameException} will be raised.
73 */
74 public HttpChunkAggregator(int maxContentLength) {
75 if (maxContentLength <= 0) {
76 throw new IllegalArgumentException(
77 "maxContentLength must be a positive integer: " +
78 maxContentLength);
79 }
80 this.maxContentLength = maxContentLength;
81 }
82
83 @Override
84 public void messageReceived(ChannelHandlerContext ctx, MessageEvent e)
85 throws Exception {
86
87 Object msg = e.getMessage();
88 HttpMessage currentMessage = this.currentMessage;
89
90 if (msg instanceof HttpMessage) {
91 HttpMessage m = (HttpMessage) msg;
92
93 // Handle the 'Expect: 100-continue' header if necessary.
94 // TODO: Respond with 413 Request Entity Too Large
95 // and discard the traffic or close the connection.
96 // No need to notify the upstream handlers - just log.
97 // If decoding a response, just throw an exception.
98 if (is100ContinueExpected(m)) {
99 write(ctx, succeededFuture(ctx.getChannel()),
100 CONTINUE.duplicate());
101 }
102
103 if (m.isChunked()) {
104 // A chunked message - remove 'Transfer-Encoding' header,
105 // initialize the cumulative buffer, and wait for incoming chunks.
106 List<String> encodings = m
107 .getHeaders(HttpHeaders.Names.TRANSFER_ENCODING);
108 encodings.remove(HttpHeaders.Values.CHUNKED);
109 if (encodings.isEmpty()) {
110 m.removeHeader(HttpHeaders.Names.TRANSFER_ENCODING);
111 }
112 m.setChunked(false);
113 m.setContent(ChannelBuffers.dynamicBuffer(e.getChannel()
114 .getConfig().getBufferFactory()));
115 this.currentMessage = m;
116 } else {
117 // Not a chunked message - pass through.
118 this.currentMessage = null;
119 ctx.sendUpstream(e);
120 }
121 } else if (msg instanceof HttpChunk) {
122 // Sanity check
123 if (currentMessage == null) {
124 throw new IllegalStateException("received " +
125 HttpChunk.class.getSimpleName() + " without " +
126 HttpMessage.class.getSimpleName());
127 }
128
129 // Merge the received chunk into the content of the current message.
130 HttpChunk chunk = (HttpChunk) msg;
131 ChannelBuffer content = currentMessage.getContent();
132
133 if (content.readableBytes() > maxContentLength -
134 chunk.getContent().readableBytes()) {
135 // TODO: Respond with 413 Request Entity Too Large
136 // and discard the traffic or close the connection.
137 // No need to notify the upstream handlers - just log.
138 // If decoding a response, just throw an exception.
139 throw new TooLongFrameException(
140 "HTTP content length exceeded " + maxContentLength +
141 " bytes.");
142 }
143
144 content.writeBytes(chunk.getContent());
145 if (chunk.isLast()) {
146 this.currentMessage = null;
147
148 // Merge trailing headers into the message.
149 if (chunk instanceof HttpChunkTrailer) {
150 HttpChunkTrailer trailer = (HttpChunkTrailer) chunk;
151 for (Entry<String, String> header: trailer.getHeaders()) {
152 currentMessage.setHeader(header.getKey(),
153 header.getValue());
154 }
155 }
156
157 // Set the 'Content-Length' header.
158 currentMessage.setHeader(HttpHeaders.Names.CONTENT_LENGTH,
159 String.valueOf(content.readableBytes()));
160
161 // All done - generate the event.
162 Channels.fireMessageReceived(ctx, currentMessage,
163 e.getRemoteAddress());
164 }
165 } else {
166 // Neither HttpMessage or HttpChunk
167 ctx.sendUpstream(e);
168 }
169 }
170
171 }