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.Unpooled;
20  import io.netty.channel.ChannelFuture;
21  import io.netty.channel.ChannelFutureListener;
22  import io.netty.channel.ChannelHandler;
23  import io.netty.channel.ChannelHandlerContext;
24  import io.netty.channel.ChannelPipeline;
25  import io.netty.handler.codec.DecoderResult;
26  import io.netty.handler.codec.MessageAggregator;
27  import io.netty.handler.codec.TooLongFrameException;
28  import io.netty.util.internal.logging.InternalLogger;
29  import io.netty.util.internal.logging.InternalLoggerFactory;
30  
31  import static io.netty.handler.codec.http.HttpHeaderNames.CONNECTION;
32  import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_LENGTH;
33  import static io.netty.handler.codec.http.HttpHeaderNames.EXPECT;
34  import static io.netty.handler.codec.http.HttpUtil.getContentLength;
35  
36  /**
37   * A {@link ChannelHandler} that aggregates an {@link HttpMessage}
38   * and its following {@link HttpContent}s into a single {@link FullHttpRequest}
39   * or {@link FullHttpResponse} (depending on if it used to handle requests or responses)
40   * with no following {@link HttpContent}s.  It is useful when you don't want to take
41   * care of HTTP messages whose transfer encoding is 'chunked'.  Insert this
42   * handler after {@link HttpResponseDecoder} in the {@link ChannelPipeline} if being used to handle
43   * responses, or after {@link HttpRequestDecoder} and {@link HttpResponseEncoder} in the
44   * {@link ChannelPipeline} if being used to handle requests.
45   * <blockquote>
46   *  <pre>
47   *  {@link ChannelPipeline} p = ...;
48   *  ...
49   *  p.addLast("decoder", <b>new {@link HttpRequestDecoder}()</b>);
50   *  p.addLast("encoder", <b>new {@link HttpResponseEncoder}()</b>);
51   *  p.addLast("aggregator", <b>new {@link HttpObjectAggregator}(1048576)</b>);
52   *  ...
53   *  p.addLast("handler", new HttpRequestHandler());
54   *  </pre>
55   * </blockquote>
56   * <p>
57   * For convenience, consider putting a {@link HttpServerCodec} before the {@link HttpObjectAggregator}
58   * as it functions as both a {@link HttpRequestDecoder} and a {@link HttpResponseEncoder}.
59   * </p>
60   * Be aware that {@link HttpObjectAggregator} may end up sending a {@link HttpResponse}:
61   * <table border summary="Possible Responses">
62   *   <tbody>
63   *     <tr>
64   *       <th>Response Status</th>
65   *       <th>Condition When Sent</th>
66   *     </tr>
67   *     <tr>
68   *       <td>100 Continue</td>
69   *       <td>A '100-continue' expectation is received and the 'content-length' doesn't exceed maxContentLength</td>
70   *     </tr>
71   *     <tr>
72   *       <td>417 Expectation Failed</td>
73   *       <td>A '100-continue' expectation is received and the 'content-length' exceeds maxContentLength</td>
74   *     </tr>
75   *     <tr>
76   *       <td>413 Request Entity Too Large</td>
77   *       <td>Either the 'content-length' or the bytes received so far exceed maxContentLength</td>
78   *     </tr>
79   *   </tbody>
80   * </table>
81   *
82   * @see FullHttpRequest
83   * @see FullHttpResponse
84   * @see HttpResponseDecoder
85   * @see HttpServerCodec
86   */
87  public class HttpObjectAggregator
88          extends MessageAggregator<HttpObject, HttpMessage, HttpContent, FullHttpMessage> {
89      private static final InternalLogger logger = InternalLoggerFactory.getInstance(HttpObjectAggregator.class);
90      private static final FullHttpResponse CONTINUE =
91              new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.CONTINUE, Unpooled.EMPTY_BUFFER);
92      private static final FullHttpResponse EXPECTATION_FAILED = new DefaultFullHttpResponse(
93              HttpVersion.HTTP_1_1, HttpResponseStatus.EXPECTATION_FAILED, Unpooled.EMPTY_BUFFER);
94      private static final FullHttpResponse TOO_LARGE_CLOSE = new DefaultFullHttpResponse(
95              HttpVersion.HTTP_1_1, HttpResponseStatus.REQUEST_ENTITY_TOO_LARGE, Unpooled.EMPTY_BUFFER);
96      private static final FullHttpResponse TOO_LARGE = new DefaultFullHttpResponse(
97          HttpVersion.HTTP_1_1, HttpResponseStatus.REQUEST_ENTITY_TOO_LARGE, Unpooled.EMPTY_BUFFER);
98  
99      static {
100         EXPECTATION_FAILED.headers().set(CONTENT_LENGTH, 0);
101         TOO_LARGE.headers().set(CONTENT_LENGTH, 0);
102 
103         TOO_LARGE_CLOSE.headers().set(CONTENT_LENGTH, 0);
104         TOO_LARGE_CLOSE.headers().set(CONNECTION, HttpHeaderValues.CLOSE);
105     }
106 
107     private final boolean closeOnExpectationFailed;
108 
109     /**
110      * Creates a new instance.
111      * @param maxContentLength the maximum length of the aggregated content in bytes.
112      * If the length of the aggregated content exceeds this value,
113      * {@link #handleOversizedMessage(ChannelHandlerContext, HttpMessage)} will be called.
114      */
115     public HttpObjectAggregator(int maxContentLength) {
116         this(maxContentLength, false);
117     }
118 
119     /**
120      * Creates a new instance.
121      * @param maxContentLength the maximum length of the aggregated content in bytes.
122      * If the length of the aggregated content exceeds this value,
123      * {@link #handleOversizedMessage(ChannelHandlerContext, HttpMessage)} will be called.
124      * @param closeOnExpectationFailed If a 100-continue response is detected but the content length is too large
125      * then {@code true} means close the connection. otherwise the connection will remain open and data will be
126      * consumed and discarded until the next request is received.
127      */
128     public HttpObjectAggregator(int maxContentLength, boolean closeOnExpectationFailed) {
129         super(maxContentLength);
130         this.closeOnExpectationFailed = closeOnExpectationFailed;
131     }
132 
133     @Override
134     protected boolean isStartMessage(HttpObject msg) throws Exception {
135         return msg instanceof HttpMessage;
136     }
137 
138     @Override
139     protected boolean isContentMessage(HttpObject msg) throws Exception {
140         return msg instanceof HttpContent;
141     }
142 
143     @Override
144     protected boolean isLastContentMessage(HttpContent msg) throws Exception {
145         return msg instanceof LastHttpContent;
146     }
147 
148     @Override
149     protected boolean isAggregated(HttpObject msg) throws Exception {
150         return msg instanceof FullHttpMessage;
151     }
152 
153     @Override
154     protected boolean isContentLengthInvalid(HttpMessage start, int maxContentLength) {
155         try {
156             return getContentLength(start, -1L) > maxContentLength;
157         } catch (final NumberFormatException e) {
158             return false;
159         }
160     }
161 
162     private static Object continueResponse(HttpMessage start, int maxContentLength, ChannelPipeline pipeline) {
163         if (HttpUtil.isUnsupportedExpectation(start)) {
164             // if the request contains an unsupported expectation, we return 417
165             pipeline.fireUserEventTriggered(HttpExpectationFailedEvent.INSTANCE);
166             return EXPECTATION_FAILED.retainedDuplicate();
167         } else if (HttpUtil.is100ContinueExpected(start)) {
168             // if the request contains 100-continue but the content-length is too large, we return 413
169             if (getContentLength(start, -1L) <= maxContentLength) {
170                 return CONTINUE.retainedDuplicate();
171             }
172             pipeline.fireUserEventTriggered(HttpExpectationFailedEvent.INSTANCE);
173             return TOO_LARGE.retainedDuplicate();
174         }
175 
176         return null;
177     }
178 
179     @Override
180     protected Object newContinueResponse(HttpMessage start, int maxContentLength, ChannelPipeline pipeline) {
181         Object response = continueResponse(start, maxContentLength, pipeline);
182         // we're going to respond based on the request expectation so there's no
183         // need to propagate the expectation further.
184         if (response != null) {
185             start.headers().remove(EXPECT);
186         }
187         return response;
188     }
189 
190     @Override
191     protected boolean closeAfterContinueResponse(Object msg) {
192         return closeOnExpectationFailed && ignoreContentAfterContinueResponse(msg);
193     }
194 
195     @Override
196     protected boolean ignoreContentAfterContinueResponse(Object msg) {
197         if (msg instanceof HttpResponse) {
198             final HttpResponse httpResponse = (HttpResponse) msg;
199             return httpResponse.status().codeClass().equals(HttpStatusClass.CLIENT_ERROR);
200         }
201         return false;
202     }
203 
204     @Override
205     protected FullHttpMessage beginAggregation(HttpMessage start, ByteBuf content) throws Exception {
206         assert !(start instanceof FullHttpMessage);
207 
208         HttpUtil.setTransferEncodingChunked(start, false);
209 
210         AggregatedFullHttpMessage ret;
211         if (start instanceof HttpRequest) {
212             ret = new AggregatedFullHttpRequest((HttpRequest) start, content, null);
213         } else if (start instanceof HttpResponse) {
214             ret = new AggregatedFullHttpResponse((HttpResponse) start, content, null);
215         } else {
216             throw new Error();
217         }
218         return ret;
219     }
220 
221     @Override
222     protected void aggregate(FullHttpMessage aggregated, HttpContent content) throws Exception {
223         if (content instanceof LastHttpContent) {
224             // Merge trailing headers into the message.
225             ((AggregatedFullHttpMessage) aggregated).setTrailingHeaders(((LastHttpContent) content).trailingHeaders());
226         }
227     }
228 
229     @Override
230     protected void finishAggregation(FullHttpMessage aggregated) throws Exception {
231         // Set the 'Content-Length' header. If one isn't already set.
232         // This is important as HEAD responses will use a 'Content-Length' header which
233         // does not match the actual body, but the number of bytes that would be
234         // transmitted if a GET would have been used.
235         //
236         // See rfc2616 14.13 Content-Length
237         if (!HttpUtil.isContentLengthSet(aggregated)) {
238             aggregated.headers().set(
239                     CONTENT_LENGTH,
240                     String.valueOf(aggregated.content().readableBytes()));
241         }
242     }
243 
244     @Override
245     protected void handleOversizedMessage(final ChannelHandlerContext ctx, HttpMessage oversized) throws Exception {
246         if (oversized instanceof HttpRequest) {
247             // send back a 413 and close the connection
248 
249             // If the client started to send data already, close because it's impossible to recover.
250             // If keep-alive is off and 'Expect: 100-continue' is missing, no need to leave the connection open.
251             if (oversized instanceof FullHttpMessage ||
252                 !HttpUtil.is100ContinueExpected(oversized) && !HttpUtil.isKeepAlive(oversized)) {
253                 ChannelFuture future = ctx.writeAndFlush(TOO_LARGE_CLOSE.retainedDuplicate());
254                 future.addListener(new ChannelFutureListener() {
255                     @Override
256                     public void operationComplete(ChannelFuture future) throws Exception {
257                         if (!future.isSuccess()) {
258                             logger.debug("Failed to send a 413 Request Entity Too Large.", future.cause());
259                         }
260                         ctx.close();
261                     }
262                 });
263             } else {
264                 ctx.writeAndFlush(TOO_LARGE.retainedDuplicate()).addListener(new ChannelFutureListener() {
265                     @Override
266                     public void operationComplete(ChannelFuture future) throws Exception {
267                         if (!future.isSuccess()) {
268                             logger.debug("Failed to send a 413 Request Entity Too Large.", future.cause());
269                             ctx.close();
270                         }
271                     }
272                 });
273             }
274 
275             // If an oversized request was handled properly and the connection is still alive
276             // (i.e. rejected 100-continue). the decoder should prepare to handle a new message.
277             HttpObjectDecoder decoder = ctx.pipeline().get(HttpObjectDecoder.class);
278             if (decoder != null) {
279                 decoder.reset();
280             }
281         } else if (oversized instanceof HttpResponse) {
282             ctx.close();
283             throw new TooLongFrameException("Response entity too large: " + oversized);
284         } else {
285             throw new IllegalStateException();
286         }
287     }
288 
289     private abstract static class AggregatedFullHttpMessage implements FullHttpMessage {
290         protected final HttpMessage message;
291         private final ByteBuf content;
292         private HttpHeaders trailingHeaders;
293 
294         AggregatedFullHttpMessage(HttpMessage message, ByteBuf content, HttpHeaders trailingHeaders) {
295             this.message = message;
296             this.content = content;
297             this.trailingHeaders = trailingHeaders;
298         }
299 
300         @Override
301         public HttpHeaders trailingHeaders() {
302             HttpHeaders trailingHeaders = this.trailingHeaders;
303             if (trailingHeaders == null) {
304                 return EmptyHttpHeaders.INSTANCE;
305             } else {
306                 return trailingHeaders;
307             }
308         }
309 
310         void setTrailingHeaders(HttpHeaders trailingHeaders) {
311             this.trailingHeaders = trailingHeaders;
312         }
313 
314         @Override
315         public HttpVersion getProtocolVersion() {
316             return message.protocolVersion();
317         }
318 
319         @Override
320         public HttpVersion protocolVersion() {
321             return message.protocolVersion();
322         }
323 
324         @Override
325         public FullHttpMessage setProtocolVersion(HttpVersion version) {
326             message.setProtocolVersion(version);
327             return this;
328         }
329 
330         @Override
331         public HttpHeaders headers() {
332             return message.headers();
333         }
334 
335         @Override
336         public DecoderResult decoderResult() {
337             return message.decoderResult();
338         }
339 
340         @Override
341         public DecoderResult getDecoderResult() {
342             return message.decoderResult();
343         }
344 
345         @Override
346         public void setDecoderResult(DecoderResult result) {
347             message.setDecoderResult(result);
348         }
349 
350         @Override
351         public ByteBuf content() {
352             return content;
353         }
354 
355         @Override
356         public int refCnt() {
357             return content.refCnt();
358         }
359 
360         @Override
361         public FullHttpMessage retain() {
362             content.retain();
363             return this;
364         }
365 
366         @Override
367         public FullHttpMessage retain(int increment) {
368             content.retain(increment);
369             return this;
370         }
371 
372         @Override
373         public FullHttpMessage touch(Object hint) {
374             content.touch(hint);
375             return this;
376         }
377 
378         @Override
379         public FullHttpMessage touch() {
380             content.touch();
381             return this;
382         }
383 
384         @Override
385         public boolean release() {
386             return content.release();
387         }
388 
389         @Override
390         public boolean release(int decrement) {
391             return content.release(decrement);
392         }
393 
394         @Override
395         public abstract FullHttpMessage copy();
396 
397         @Override
398         public abstract FullHttpMessage duplicate();
399 
400         @Override
401         public abstract FullHttpMessage retainedDuplicate();
402     }
403 
404     private static final class AggregatedFullHttpRequest extends AggregatedFullHttpMessage implements FullHttpRequest {
405 
406         AggregatedFullHttpRequest(HttpRequest request, ByteBuf content, HttpHeaders trailingHeaders) {
407             super(request, content, trailingHeaders);
408         }
409 
410         @Override
411         public FullHttpRequest copy() {
412             return replace(content().copy());
413         }
414 
415         @Override
416         public FullHttpRequest duplicate() {
417             return replace(content().duplicate());
418         }
419 
420         @Override
421         public FullHttpRequest retainedDuplicate() {
422             return replace(content().retainedDuplicate());
423         }
424 
425         @Override
426         public FullHttpRequest replace(ByteBuf content) {
427             DefaultFullHttpRequest dup = new DefaultFullHttpRequest(protocolVersion(), method(), uri(), content);
428             dup.headers().set(headers());
429             dup.trailingHeaders().set(trailingHeaders());
430             dup.setDecoderResult(decoderResult());
431             return dup;
432         }
433 
434         @Override
435         public FullHttpRequest retain(int increment) {
436             super.retain(increment);
437             return this;
438         }
439 
440         @Override
441         public FullHttpRequest retain() {
442             super.retain();
443             return this;
444         }
445 
446         @Override
447         public FullHttpRequest touch() {
448             super.touch();
449             return this;
450         }
451 
452         @Override
453         public FullHttpRequest touch(Object hint) {
454             super.touch(hint);
455             return this;
456         }
457 
458         @Override
459         public FullHttpRequest setMethod(HttpMethod method) {
460             ((HttpRequest) message).setMethod(method);
461             return this;
462         }
463 
464         @Override
465         public FullHttpRequest setUri(String uri) {
466             ((HttpRequest) message).setUri(uri);
467             return this;
468         }
469 
470         @Override
471         public HttpMethod getMethod() {
472             return ((HttpRequest) message).method();
473         }
474 
475         @Override
476         public String getUri() {
477             return ((HttpRequest) message).uri();
478         }
479 
480         @Override
481         public HttpMethod method() {
482             return getMethod();
483         }
484 
485         @Override
486         public String uri() {
487             return getUri();
488         }
489 
490         @Override
491         public FullHttpRequest setProtocolVersion(HttpVersion version) {
492             super.setProtocolVersion(version);
493             return this;
494         }
495 
496         @Override
497         public String toString() {
498             return HttpMessageUtil.appendFullRequest(new StringBuilder(256), this).toString();
499         }
500     }
501 
502     private static final class AggregatedFullHttpResponse extends AggregatedFullHttpMessage
503             implements FullHttpResponse {
504 
505         AggregatedFullHttpResponse(HttpResponse message, ByteBuf content, HttpHeaders trailingHeaders) {
506             super(message, content, trailingHeaders);
507         }
508 
509         @Override
510         public FullHttpResponse copy() {
511             return replace(content().copy());
512         }
513 
514         @Override
515         public FullHttpResponse duplicate() {
516             return replace(content().duplicate());
517         }
518 
519         @Override
520         public FullHttpResponse retainedDuplicate() {
521             return replace(content().retainedDuplicate());
522         }
523 
524         @Override
525         public FullHttpResponse replace(ByteBuf content) {
526             DefaultFullHttpResponse dup = new DefaultFullHttpResponse(getProtocolVersion(), getStatus(), content);
527             dup.headers().set(headers());
528             dup.trailingHeaders().set(trailingHeaders());
529             dup.setDecoderResult(decoderResult());
530             return dup;
531         }
532 
533         @Override
534         public FullHttpResponse setStatus(HttpResponseStatus status) {
535             ((HttpResponse) message).setStatus(status);
536             return this;
537         }
538 
539         @Override
540         public HttpResponseStatus getStatus() {
541             return ((HttpResponse) message).status();
542         }
543 
544         @Override
545         public HttpResponseStatus status() {
546             return getStatus();
547         }
548 
549         @Override
550         public FullHttpResponse setProtocolVersion(HttpVersion version) {
551             super.setProtocolVersion(version);
552             return this;
553         }
554 
555         @Override
556         public FullHttpResponse retain(int increment) {
557             super.retain(increment);
558             return this;
559         }
560 
561         @Override
562         public FullHttpResponse retain() {
563             super.retain();
564             return this;
565         }
566 
567         @Override
568         public FullHttpResponse touch(Object hint) {
569             super.touch(hint);
570             return this;
571         }
572 
573         @Override
574         public FullHttpResponse touch() {
575             super.touch();
576             return this;
577         }
578 
579         @Override
580         public String toString() {
581             return HttpMessageUtil.appendFullResponse(new StringBuilder(256), this).toString();
582         }
583     }
584 }