View Javadoc
1   /*
2    * Copyright 2012 The Netty Project
3    *
4    * The Netty Project licenses this file to you under the Apache License,
5    * version 2.0 (the "License"); you may not use this file except in compliance
6    * with the 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 io.netty.handler.codec.http;
17  
18  import io.netty.buffer.ByteBuf;
19  import io.netty.buffer.DefaultByteBufHolder;
20  import io.netty.buffer.Unpooled;
21  import io.netty.channel.ChannelFuture;
22  import io.netty.channel.ChannelFutureListener;
23  import io.netty.channel.ChannelHandler;
24  import io.netty.channel.ChannelHandlerContext;
25  import io.netty.channel.ChannelPipeline;
26  import io.netty.handler.codec.DecoderResult;
27  import io.netty.handler.codec.MessageAggregator;
28  import io.netty.handler.codec.TooLongFrameException;
29  import io.netty.util.internal.logging.InternalLogger;
30  import io.netty.util.internal.logging.InternalLoggerFactory;
31  
32  /**
33   * A {@link ChannelHandler} that aggregates an {@link HttpMessage}
34   * and its following {@link HttpContent}s into a single {@link FullHttpRequest}
35   * or {@link FullHttpResponse} (depending on if it used to handle requests or responses)
36   * with no following {@link HttpContent}s.  It is useful when you don't want to take
37   * care of HTTP messages whose transfer encoding is 'chunked'.  Insert this
38   * handler after {@link HttpObjectDecoder} in the {@link ChannelPipeline}:
39   * <pre>
40   * {@link ChannelPipeline} p = ...;
41   * ...
42   * p.addLast("encoder", new {@link HttpResponseEncoder}());
43   * p.addLast("decoder", new {@link HttpRequestDecoder}());
44   * p.addLast("aggregator", <b>new {@link HttpObjectAggregator}(1048576)</b>);
45   * ...
46   * p.addLast("handler", new HttpRequestHandler());
47   * </pre>
48   * Be aware that you need to have the {@link HttpResponseEncoder} or {@link HttpRequestEncoder}
49   * before the {@link HttpObjectAggregator} in the {@link ChannelPipeline}.
50   */
51  public class HttpObjectAggregator
52          extends MessageAggregator<HttpObject, HttpMessage, HttpContent, FullHttpMessage> {
53  
54      private static final InternalLogger logger = InternalLoggerFactory.getInstance(HttpObjectAggregator.class);
55      private static final FullHttpResponse CONTINUE = new DefaultFullHttpResponse(
56              HttpVersion.HTTP_1_1, HttpResponseStatus.CONTINUE, Unpooled.EMPTY_BUFFER);
57      private static final FullHttpResponse TOO_LARGE = new DefaultFullHttpResponse(
58              HttpVersion.HTTP_1_1, HttpResponseStatus.REQUEST_ENTITY_TOO_LARGE, Unpooled.EMPTY_BUFFER);
59  
60      static {
61          TOO_LARGE.headers().setInt(HttpHeaderNames.CONTENT_LENGTH, 0);
62      }
63  
64      /**
65       * Creates a new instance.
66       *
67       * @param maxContentLength
68       *        the maximum length of the aggregated content.
69       *        If the length of the aggregated content exceeds this value,
70       *        {@link #handleOversizedMessage(ChannelHandlerContext, HttpMessage)}
71       *        will be called.
72       */
73      public HttpObjectAggregator(int maxContentLength) {
74          super(maxContentLength);
75      }
76  
77      @Override
78      protected boolean isStartMessage(HttpObject msg) throws Exception {
79          return msg instanceof HttpMessage;
80      }
81  
82      @Override
83      protected boolean isContentMessage(HttpObject msg) throws Exception {
84          return msg instanceof HttpContent;
85      }
86  
87      @Override
88      protected boolean isLastContentMessage(HttpContent msg) throws Exception {
89          return msg instanceof LastHttpContent;
90      }
91  
92      @Override
93      protected boolean isAggregated(HttpObject msg) throws Exception {
94          return msg instanceof FullHttpMessage;
95      }
96  
97      @Override
98      protected boolean hasContentLength(HttpMessage start) throws Exception {
99          return HttpHeaderUtil.isContentLengthSet(start);
100     }
101 
102     @Override
103     protected long contentLength(HttpMessage start) throws Exception {
104         return HttpHeaderUtil.getContentLength(start);
105     }
106 
107     @Override
108     protected Object newContinueResponse(HttpMessage start) throws Exception {
109         if (HttpHeaderUtil.is100ContinueExpected(start)) {
110             return CONTINUE;
111         } else {
112             return null;
113         }
114     }
115 
116     @Override
117     protected FullHttpMessage beginAggregation(HttpMessage start, ByteBuf content) throws Exception {
118         assert !(start instanceof FullHttpMessage);
119 
120         HttpHeaderUtil.setTransferEncodingChunked(start, false);
121 
122         AggregatedFullHttpMessage ret;
123         if (start instanceof HttpRequest) {
124             ret = new AggregatedFullHttpRequest((HttpRequest) start, content, null);
125         } else if (start instanceof HttpResponse) {
126             ret = new AggregatedFullHttpResponse((HttpResponse) start, content, null);
127         } else {
128             throw new Error();
129         }
130         return ret;
131     }
132 
133     @Override
134     protected void aggregate(FullHttpMessage aggregated, HttpContent content) throws Exception {
135         if (content instanceof LastHttpContent) {
136             // Merge trailing headers into the message.
137             ((AggregatedFullHttpMessage) aggregated).setTrailingHeaders(((LastHttpContent) content).trailingHeaders());
138         }
139     }
140 
141     @Override
142     protected void finishAggregation(FullHttpMessage aggregated) throws Exception {
143         // Set the 'Content-Length' header. If one isn't already set.
144         // This is important as HEAD responses will use a 'Content-Length' header which
145         // does not match the actual body, but the number of bytes that would be
146         // transmitted if a GET would have been used.
147         //
148         // See rfc2616 14.13 Content-Length
149         if (!HttpHeaderUtil.isContentLengthSet(aggregated)) {
150             aggregated.headers().set(
151                     HttpHeaderNames.CONTENT_LENGTH,
152                     String.valueOf(aggregated.content().readableBytes()));
153         }
154     }
155 
156     @Override
157     protected void handleOversizedMessage(final ChannelHandlerContext ctx, HttpMessage oversized) throws Exception {
158         if (oversized instanceof HttpRequest) {
159             // send back a 413 and close the connection
160             ChannelFuture future = ctx.writeAndFlush(TOO_LARGE).addListener(new ChannelFutureListener() {
161                 @Override
162                 public void operationComplete(ChannelFuture future) throws Exception {
163                     if (!future.isSuccess()) {
164                         logger.debug("Failed to send a 413 Request Entity Too Large.", future.cause());
165                         ctx.close();
166                     }
167                 }
168             });
169 
170             // If the client started to send data already, close because it's impossible to recover.
171             // If keep-alive is off and 'Expect: 100-continue' is missing, no need to leave the connection open.
172             if (oversized instanceof FullHttpMessage ||
173                 !HttpHeaderUtil.is100ContinueExpected(oversized) && !HttpHeaderUtil.isKeepAlive(oversized)) {
174                 future.addListener(ChannelFutureListener.CLOSE);
175             }
176 
177             // If an oversized request was handled properly and the connection is still alive
178             // (i.e. rejected 100-continue). the decoder should prepare to handle a new message.
179             HttpObjectDecoder decoder = ctx.pipeline().get(HttpObjectDecoder.class);
180             if (decoder != null) {
181                 decoder.reset();
182             }
183         } else if (oversized instanceof HttpResponse) {
184             ctx.close();
185             throw new TooLongFrameException("Response entity too large: " + oversized);
186         } else {
187             throw new IllegalStateException();
188         }
189     }
190 
191     private abstract static class AggregatedFullHttpMessage extends DefaultByteBufHolder implements FullHttpMessage {
192         protected final HttpMessage message;
193         private HttpHeaders trailingHeaders;
194 
195         AggregatedFullHttpMessage(HttpMessage message, ByteBuf content, HttpHeaders trailingHeaders) {
196             super(content);
197             this.message = message;
198             this.trailingHeaders = trailingHeaders;
199         }
200 
201         @Override
202         public HttpHeaders trailingHeaders() {
203             HttpHeaders trailingHeaders = this.trailingHeaders;
204             if (trailingHeaders == null) {
205                 return EmptyHttpHeaders.INSTANCE;
206             } else {
207                 return trailingHeaders;
208             }
209         }
210 
211         void setTrailingHeaders(HttpHeaders trailingHeaders) {
212             this.trailingHeaders = trailingHeaders;
213         }
214 
215         @Override
216         public HttpVersion protocolVersion() {
217             return message.protocolVersion();
218         }
219 
220         @Override
221         public FullHttpMessage setProtocolVersion(HttpVersion version) {
222             message.setProtocolVersion(version);
223             return this;
224         }
225 
226         @Override
227         public HttpHeaders headers() {
228             return message.headers();
229         }
230 
231         @Override
232         public DecoderResult decoderResult() {
233             return message.decoderResult();
234         }
235 
236         @Override
237         public void setDecoderResult(DecoderResult result) {
238             message.setDecoderResult(result);
239         }
240 
241         @Override
242         public FullHttpMessage retain(int increment) {
243             super.retain(increment);
244             return this;
245         }
246 
247         @Override
248         public FullHttpMessage retain() {
249             super.retain();
250             return this;
251         }
252 
253         @Override
254         public FullHttpMessage touch(Object hint) {
255             super.touch(hint);
256             return this;
257         }
258 
259         @Override
260         public FullHttpMessage touch() {
261             super.touch();
262             return this;
263         }
264 
265         @Override
266         public abstract FullHttpMessage copy();
267 
268         @Override
269         public abstract FullHttpMessage duplicate();
270     }
271 
272     private static final class AggregatedFullHttpRequest extends AggregatedFullHttpMessage implements FullHttpRequest {
273 
274         AggregatedFullHttpRequest(HttpRequest request, ByteBuf content, HttpHeaders trailingHeaders) {
275             super(request, content, trailingHeaders);
276         }
277 
278         /**
279          * Copy this object
280          *
281          * @param copyContent
282          * <ul>
283          * <li>{@code true} if this object's {@link #content()} should be used to copy.</li>
284          * <li>{@code false} if {@code newContent} should be used instead.</li>
285          * </ul>
286          * @param newContent
287          * <ul>
288          * <li>if {@code copyContent} is false then this will be used in the copy's content.</li>
289          * <li>if {@code null} then a default buffer of 0 size will be selected</li>
290          * </ul>
291          * @return A copy of this object
292          */
293         private FullHttpRequest copy(boolean copyContent, ByteBuf newContent) {
294             DefaultFullHttpRequest copy = new DefaultFullHttpRequest(
295                     protocolVersion(), method(), uri(),
296                     copyContent ? content().copy() :
297                         newContent == null ? Unpooled.buffer(0) : newContent);
298             copy.headers().set(headers());
299             copy.trailingHeaders().set(trailingHeaders());
300             return copy;
301         }
302 
303         @Override
304         public FullHttpRequest copy(ByteBuf newContent) {
305             return copy(false, newContent);
306         }
307 
308         @Override
309         public FullHttpRequest copy() {
310             return copy(true, null);
311         }
312 
313         @Override
314         public FullHttpRequest duplicate() {
315             DefaultFullHttpRequest duplicate = new DefaultFullHttpRequest(
316                     protocolVersion(), method(), uri(), content().duplicate());
317             duplicate.headers().set(headers());
318             duplicate.trailingHeaders().set(trailingHeaders());
319             return duplicate;
320         }
321 
322         @Override
323         public FullHttpRequest retain(int increment) {
324             super.retain(increment);
325             return this;
326         }
327 
328         @Override
329         public FullHttpRequest retain() {
330             super.retain();
331             return this;
332         }
333 
334         @Override
335         public FullHttpRequest touch() {
336             super.touch();
337             return this;
338         }
339 
340         @Override
341         public FullHttpRequest touch(Object hint) {
342             super.touch(hint);
343             return this;
344         }
345 
346         @Override
347         public FullHttpRequest setMethod(HttpMethod method) {
348             ((HttpRequest) message).setMethod(method);
349             return this;
350         }
351 
352         @Override
353         public FullHttpRequest setUri(String uri) {
354             ((HttpRequest) message).setUri(uri);
355             return this;
356         }
357 
358         @Override
359         public HttpMethod method() {
360             return ((HttpRequest) message).method();
361         }
362 
363         @Override
364         public String uri() {
365             return ((HttpRequest) message).uri();
366         }
367 
368         @Override
369         public FullHttpRequest setProtocolVersion(HttpVersion version) {
370             super.setProtocolVersion(version);
371             return this;
372         }
373 
374         @Override
375         public String toString() {
376             return HttpMessageUtil.appendFullRequest(new StringBuilder(256), this).toString();
377         }
378     }
379 
380     private static final class AggregatedFullHttpResponse extends AggregatedFullHttpMessage
381             implements FullHttpResponse {
382 
383         AggregatedFullHttpResponse(HttpResponse message, ByteBuf content, HttpHeaders trailingHeaders) {
384             super(message, content, trailingHeaders);
385         }
386 
387         /**
388          * Copy this object
389          *
390          * @param copyContent
391          * <ul>
392          * <li>{@code true} if this object's {@link #content()} should be used to copy.</li>
393          * <li>{@code false} if {@code newContent} should be used instead.</li>
394          * </ul>
395          * @param newContent
396          * <ul>
397          * <li>if {@code copyContent} is false then this will be used in the copy's content.</li>
398          * <li>if {@code null} then a default buffer of 0 size will be selected</li>
399          * </ul>
400          * @return A copy of this object
401          */
402         private FullHttpResponse copy(boolean copyContent, ByteBuf newContent) {
403             DefaultFullHttpResponse copy = new DefaultFullHttpResponse(
404                     protocolVersion(), status(),
405                     copyContent ? content().copy() :
406                         newContent == null ? Unpooled.buffer(0) : newContent);
407             copy.headers().set(headers());
408             copy.trailingHeaders().set(trailingHeaders());
409             return copy;
410         }
411 
412         @Override
413         public FullHttpResponse copy(ByteBuf newContent) {
414             return copy(false, newContent);
415         }
416 
417         @Override
418         public FullHttpResponse copy() {
419             return copy(true, null);
420         }
421 
422         @Override
423         public FullHttpResponse duplicate() {
424             DefaultFullHttpResponse duplicate = new DefaultFullHttpResponse(protocolVersion(), status(),
425                     content().duplicate());
426             duplicate.headers().set(headers());
427             duplicate.trailingHeaders().set(trailingHeaders());
428             return duplicate;
429         }
430 
431         @Override
432         public FullHttpResponse setStatus(HttpResponseStatus status) {
433             ((HttpResponse) message).setStatus(status);
434             return this;
435         }
436 
437         @Override
438         public HttpResponseStatus status() {
439             return ((HttpResponse) message).status();
440         }
441 
442         @Override
443         public FullHttpResponse setProtocolVersion(HttpVersion version) {
444             super.setProtocolVersion(version);
445             return this;
446         }
447 
448         @Override
449         public FullHttpResponse retain(int increment) {
450             super.retain(increment);
451             return this;
452         }
453 
454         @Override
455         public FullHttpResponse retain() {
456             super.retain();
457             return this;
458         }
459 
460         @Override
461         public FullHttpResponse touch(Object hint) {
462             super.touch(hint);
463             return this;
464         }
465 
466         @Override
467         public FullHttpResponse touch() {
468             super.touch();
469             return this;
470         }
471 
472         @Override
473         public String toString() {
474             return HttpMessageUtil.appendFullResponse(new StringBuilder(256), this).toString();
475         }
476     }
477 }