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.channel.ChannelFutureListener;
20  import io.netty.channel.ChannelHandlerContext;
21  import io.netty.channel.ChannelPromise;
22  import io.netty.channel.CombinedChannelDuplexHandler;
23  import io.netty.util.ReferenceCountUtil;
24  import io.netty.util.internal.ObjectUtil;
25  
26  import java.util.ArrayDeque;
27  import java.util.List;
28  import java.util.Queue;
29  
30  import static io.netty.handler.codec.http.HttpObjectDecoder.DEFAULT_MAX_CHUNK_SIZE;
31  import static io.netty.handler.codec.http.HttpObjectDecoder.DEFAULT_MAX_HEADER_SIZE;
32  import static io.netty.handler.codec.http.HttpObjectDecoder.DEFAULT_MAX_INITIAL_LINE_LENGTH;
33  
34  /**
35   * A combination of {@link HttpRequestDecoder} and {@link HttpResponseEncoder}
36   * which enables easier server side HTTP implementation.
37   *
38   * <h3>Header Validation</h3>
39   *
40   * It is recommended to always enable header validation.
41   * <p>
42   * Without header validation, your system can become vulnerable to
43   * <a href="https://cwe.mitre.org/data/definitions/113.html">
44   *     CWE-113: Improper Neutralization of CRLF Sequences in HTTP Headers ('HTTP Response Splitting')
45   * </a>.
46   * <p>
47   * This recommendation stands even when both peers in the HTTP exchange are trusted,
48   * as it helps with defence-in-depth.
49   *
50   * @see HttpClientCodec
51   */
52  public final class HttpServerCodec extends CombinedChannelDuplexHandler<HttpRequestDecoder, HttpResponseEncoder>
53          implements HttpServerUpgradeHandler.SourceCodec {
54  
55      /**
56       * The maximum number of pipelined requests we allow to be awaiting a response by default, before
57       * decoding of further requests is rejected. This bounds the memory a single connection can force us
58       * to hold onto if the peer pipelines requests without reading the corresponding responses.
59       */
60      static final int DEFAULT_MAX_PIPELINE_DEPTH = 128;
61  
62      private static final byte METHOD_FLAG_HEAD = 1;
63      private static final byte METHOD_FLAG_CONNECT = 2;
64      private static final byte METHOD_FLAG_OTHER = 3;
65  
66      // We only need 2 bits per request because we distinguish:
67      // 01 = HEAD, 10 = CONNECT, 11 = other
68      private static final int METHOD_FLAG_BITS = 2;
69      private static final int INLINE_QUEUE_CAPACITY = Long.SIZE / METHOD_FLAG_BITS; // 32
70  
71      /**
72       * FIFO of request method flags.
73       *
74       * The oldest entry is stored in the least-significant bits so poll is just a mask + unsigned shift.
75       * This avoids allocation for the common case of <= 32 outstanding requests.
76       *
77       * Once more than {@link #INLINE_QUEUE_CAPACITY} requests are queued, additional entries are appended
78       * to {@link #methodOverflowQueue}. Order is preserved by always draining the inline queue first.
79       */
80      private long methodQueue;
81      private int methodQueueSize;
82      private Queue<Byte> methodOverflowQueue;
83      private final int maxPipelineDepth;
84  
85      /**
86       * When set, the connection will be closed after the next response is written.
87       */
88      private boolean mustCloseAfterResponse;
89  
90      /**
91       * Creates a new instance with the default decoder options
92       * ({@code maxInitialLineLength (4096)}, {@code maxHeaderSize (8192)}, and
93       * {@code maxChunkSize (8192)}).
94       */
95      public HttpServerCodec() {
96          this(DEFAULT_MAX_INITIAL_LINE_LENGTH, DEFAULT_MAX_HEADER_SIZE, DEFAULT_MAX_CHUNK_SIZE);
97      }
98  
99      /**
100      * Creates a new instance with the specified decoder options.
101      */
102     public HttpServerCodec(int maxInitialLineLength, int maxHeaderSize, int maxChunkSize) {
103         this(new HttpDecoderConfig()
104                 .setMaxInitialLineLength(maxInitialLineLength)
105                 .setMaxHeaderSize(maxHeaderSize)
106                 .setMaxChunkSize(maxChunkSize));
107     }
108 
109     /**
110      * Creates a new instance with the specified decoder options.
111      *
112      * @deprecated Prefer the {@link #HttpServerCodec(HttpDecoderConfig)} constructor,
113      * to always enable header validation.
114      */
115     @Deprecated
116     public HttpServerCodec(int maxInitialLineLength, int maxHeaderSize, int maxChunkSize, boolean validateHeaders) {
117         this(new HttpDecoderConfig()
118                 .setMaxInitialLineLength(maxInitialLineLength)
119                 .setMaxHeaderSize(maxHeaderSize)
120                 .setMaxChunkSize(maxChunkSize)
121                 .setValidateHeaders(validateHeaders));
122     }
123 
124     /**
125      * Creates a new instance with the specified decoder options.
126      *
127      * @deprecated Prefer the {@link #HttpServerCodec(HttpDecoderConfig)} constructor, to always enable header
128      * validation.
129      */
130     @Deprecated
131     public HttpServerCodec(int maxInitialLineLength, int maxHeaderSize, int maxChunkSize, boolean validateHeaders,
132                            int initialBufferSize) {
133         this(new HttpDecoderConfig()
134                 .setMaxInitialLineLength(maxInitialLineLength)
135                 .setMaxHeaderSize(maxHeaderSize)
136                 .setMaxChunkSize(maxChunkSize)
137                 .setValidateHeaders(validateHeaders)
138                 .setInitialBufferSize(initialBufferSize));
139     }
140 
141     /**
142      * Creates a new instance with the specified decoder options.
143      *
144      * @deprecated Prefer the {@link #HttpServerCodec(HttpDecoderConfig)} constructor,
145      * to always enable header validation.
146      */
147     @Deprecated
148     public HttpServerCodec(int maxInitialLineLength, int maxHeaderSize, int maxChunkSize, boolean validateHeaders,
149                            int initialBufferSize, boolean allowDuplicateContentLengths) {
150         this(new HttpDecoderConfig()
151                 .setMaxInitialLineLength(maxInitialLineLength)
152                 .setMaxHeaderSize(maxHeaderSize)
153                 .setMaxChunkSize(maxChunkSize)
154                 .setValidateHeaders(validateHeaders)
155                 .setInitialBufferSize(initialBufferSize)
156                 .setAllowDuplicateContentLengths(allowDuplicateContentLengths));
157     }
158 
159     /**
160      * Creates a new instance with the specified decoder options.
161      *
162      * @deprecated Prefer the {@link #HttpServerCodec(HttpDecoderConfig)} constructor,
163      * to always enable header validation.
164      */
165     @Deprecated
166     public HttpServerCodec(int maxInitialLineLength, int maxHeaderSize, int maxChunkSize, boolean validateHeaders,
167                            int initialBufferSize, boolean allowDuplicateContentLengths, boolean allowPartialChunks) {
168         this(new HttpDecoderConfig()
169                 .setMaxInitialLineLength(maxInitialLineLength)
170                 .setMaxHeaderSize(maxHeaderSize)
171                 .setMaxChunkSize(maxChunkSize)
172                 .setValidateHeaders(validateHeaders)
173                 .setInitialBufferSize(initialBufferSize)
174                 .setAllowDuplicateContentLengths(allowDuplicateContentLengths)
175                 .setAllowPartialChunks(allowPartialChunks));
176     }
177 
178     /**
179      * Creates a new instance with the specified decoder configuration.
180      */
181     public HttpServerCodec(HttpDecoderConfig config) {
182         this(config, DEFAULT_MAX_PIPELINE_DEPTH);
183     }
184 
185     /**
186      * Creates a new instance with the specified decoder configuration.
187      *
188      * @param config the decoder configuration.
189      * @param maxPipelineDepth the maximum number of requests that may be decoded while awaiting the
190      *                         corresponding responses to be written, before decoding of further requests
191      *                         is rejected with an {@link IllegalStateException}.
192      */
193     public HttpServerCodec(HttpDecoderConfig config, int maxPipelineDepth) {
194         this.maxPipelineDepth = ObjectUtil.checkPositive(maxPipelineDepth, "maxPipelineDepth");
195         init(new HttpServerRequestDecoder(config), new HttpServerResponseEncoder());
196     }
197 
198     /**
199      * Upgrades to another protocol from HTTP. Removes the {@link HttpRequestDecoder} and
200      * {@link HttpResponseEncoder} from the pipeline.
201      */
202     @Override
203     public void upgradeFrom(ChannelHandlerContext ctx) {
204         ctx.pipeline().remove(this);
205     }
206 
207     private boolean enqueueMethod(HttpMethod method) {
208         Queue<Byte> overflowQueue = methodOverflowQueue;
209         int currentDepth = methodQueueSize + (overflowQueue != null ? overflowQueue.size() : 0);
210         if (currentDepth >= maxPipelineDepth) {
211             return false;
212         }
213 
214         final byte flag;
215         if (HttpMethod.HEAD.equals(method)) {
216             flag = METHOD_FLAG_HEAD;
217         } else if (HttpMethod.CONNECT.equals(method)) {
218             flag = METHOD_FLAG_CONNECT;
219         } else {
220             flag = METHOD_FLAG_OTHER;
221         }
222 
223         // Once we have overflow, always append there until it drains completely.
224         if (overflowQueue != null) {
225             overflowQueue.add(flag);
226             return true;
227         }
228 
229         if (methodQueueSize < INLINE_QUEUE_CAPACITY) {
230             methodQueue |= (long) flag << (methodQueueSize << 1);
231             methodQueueSize++;
232         } else {
233             overflowQueue = new ArrayDeque<>(4);
234             overflowQueue.add(flag);
235             methodOverflowQueue = overflowQueue;
236         }
237         return true;
238     }
239 
240     private byte pollMethod() {
241         if (methodQueueSize != 0) {
242             //(methodQueue & ((1L << METHOD_FLAG_BITS) - 1))
243             byte flag = (byte) (methodQueue & 0x3L);
244             methodQueue >>>= METHOD_FLAG_BITS;
245             methodQueueSize--;
246             return flag;
247         }
248 
249         Queue<Byte> overflowQueue = methodOverflowQueue;
250         if (overflowQueue != null) {
251             Byte flag = overflowQueue.poll();
252             if (overflowQueue.isEmpty()) {
253                 methodOverflowQueue = null;
254             }
255             return flag != null ? flag : METHOD_FLAG_OTHER;
256         }
257 
258         return METHOD_FLAG_OTHER;
259     }
260 
261     private final class HttpServerRequestDecoder extends HttpRequestDecoder {
262         private boolean discard;
263 
264         HttpServerRequestDecoder(HttpDecoderConfig config) {
265             super(config);
266         }
267 
268         @Override
269         protected void decode(ChannelHandlerContext ctx, ByteBuf buffer, List<Object> out) throws Exception {
270             if (discard) {
271                 buffer.skipBytes(buffer.readableBytes());
272                 return;
273             }
274             int oldSize = out.size();
275             super.decode(ctx, buffer, out);
276             int size = out.size();
277             for (int i = oldSize; i < size; i++) {
278                 Object obj = out.get(i);
279                 if (obj instanceof HttpRequest) {
280                     if (!enqueueMethod(((HttpRequest) obj).method())) {
281                         // We hit the limit, let's discard everything and release everything and also ensure
282                         // we close the connection once the first response is written back.
283                         mustCloseAfterResponse = true;
284                         discard = true;
285                         ReferenceCountUtil.release(obj);
286                         while (++i < size) {
287                             ReferenceCountUtil.release(out.get(i));
288                         }
289                         out.clear();
290                         throw new IllegalStateException("maxPipelineDepth exceeded: " + maxPipelineDepth);
291                     }
292                 }
293             }
294         }
295 
296         @Override
297         protected void handleTransferEncodingChunkedWithContentLength(HttpMessage message) {
298             super.handleTransferEncodingChunkedWithContentLength(message);
299             mustCloseAfterResponse = true;
300         }
301     }
302 
303     private final class HttpServerResponseEncoder extends HttpResponseEncoder {
304 
305         private byte methodFlag;
306 
307         @Override
308         public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
309             if (mustCloseAfterResponse && msg instanceof LastHttpContent) {
310                 mustCloseAfterResponse = false;
311                 promise = promise.unvoid().addListener(ChannelFutureListener.CLOSE);
312             }
313             super.write(ctx, msg, promise);
314         }
315 
316         @Override
317         protected void sanitizeHeadersBeforeEncode(HttpResponse msg, boolean isAlwaysEmpty) {
318             if (!isAlwaysEmpty && methodFlag == METHOD_FLAG_CONNECT
319                     && msg.status().codeClass() == HttpStatusClass.SUCCESS) {
320                 // Stripping Transfer-Encoding:
321                 // See https://tools.ietf.org/html/rfc7230#section-3.3.1
322                 msg.headers().remove(HttpHeaderNames.TRANSFER_ENCODING);
323                 return;
324             }
325 
326             super.sanitizeHeadersBeforeEncode(msg, isAlwaysEmpty);
327         }
328 
329         @Override
330         protected boolean isContentAlwaysEmpty(HttpResponse msg) {
331             if (msg.status().codeClass() == HttpStatusClass.INFORMATIONAL) {
332                 // An informational response should be excluded from paired comparison. This covers 101 as well:
333                 // once the protocol is switched this handler is removed from the pipeline, so the entry that is
334                 // left behind goes away with it. Just delegate to super method which has all the needed handling.
335                 return super.isContentAlwaysEmpty(msg);
336             }
337             methodFlag = pollMethod();
338             return methodFlag == METHOD_FLAG_HEAD || super.isContentAlwaysEmpty(msg);
339         }
340     }
341 }