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