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