View Javadoc
1   /*
2    * Copyright 2014 The Netty Project
3    *
4    * The Netty Project licenses this file to you under the Apache License, version 2.0 (the
5    * "License"); you may not use this file except in compliance with the License. You may obtain a
6    * 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 distributed under the License
11   * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
12   * or implied. See the License for the specific language governing permissions and limitations under
13   * the License.
14   */
15  
16  package io.netty.handler.codec.http2;
17  
18  import io.netty.buffer.ByteBuf;
19  import io.netty.util.internal.ObjectUtil;
20  import io.netty.util.internal.UnstableApi;
21  
22  import static io.netty.handler.codec.http2.Http2CodecUtil.DEFAULT_HEADER_LIST_SIZE;
23  import static io.netty.handler.codec.http2.Http2CodecUtil.DEFAULT_INITIAL_HUFFMAN_DECODE_CAPACITY;
24  import static io.netty.handler.codec.http2.Http2Error.COMPRESSION_ERROR;
25  import static io.netty.handler.codec.http2.Http2Error.INTERNAL_ERROR;
26  import static io.netty.handler.codec.http2.Http2Exception.connectionError;
27  
28  @UnstableApi
29  public class DefaultHttp2HeadersDecoder implements Http2HeadersDecoder, Http2HeadersDecoder.Configuration {
30      private static final float HEADERS_COUNT_WEIGHT_NEW = 1 / 5f;
31      private static final float HEADERS_COUNT_WEIGHT_HISTORICAL = 1 - HEADERS_COUNT_WEIGHT_NEW;
32  
33      private final HpackDecoder hpackDecoder;
34      private final boolean validateHeaders;
35      private long maxHeaderListSizeGoAway;
36  
37      /**
38       * Used to calculate an exponential moving average of header sizes to get an estimate of how large the data
39       * structure for storing headers should be.
40       */
41      private float headerArraySizeAccumulator = 8;
42  
43      public DefaultHttp2HeadersDecoder() {
44          this(true);
45      }
46  
47      public DefaultHttp2HeadersDecoder(boolean validateHeaders) {
48          this(validateHeaders, DEFAULT_HEADER_LIST_SIZE);
49      }
50  
51      /**
52       * Create a new instance.
53       * @param validateHeaders {@code true} to validate headers are valid according to the RFC.
54       * @param maxHeaderListSize This is the only setting that can be configured before notifying the peer.
55       *  This is because <a href="https://tools.ietf.org/html/rfc7540#section-6.5.1">SETTINGS_MAX_HEADER_LIST_SIZE</a>
56       *  allows a lower than advertised limit from being enforced, and the default limit is unlimited
57       *  (which is dangerous).
58       */
59      public DefaultHttp2HeadersDecoder(boolean validateHeaders, long maxHeaderListSize) {
60          this(validateHeaders, maxHeaderListSize, DEFAULT_INITIAL_HUFFMAN_DECODE_CAPACITY);
61      }
62  
63      /**
64       * Create a new instance.
65       * @param validateHeaders {@code true} to validate headers are valid according to the RFC.
66       * @param maxHeaderListSize This is the only setting that can be configured before notifying the peer.
67       *  This is because <a href="https://tools.ietf.org/html/rfc7540#section-6.5.1">SETTINGS_MAX_HEADER_LIST_SIZE</a>
68       *  allows a lower than advertised limit from being enforced, and the default limit is unlimited
69       *  (which is dangerous).
70       * @param initialHuffmanDecodeCapacity Size of an intermediate buffer used during huffman decode.
71       */
72      public DefaultHttp2HeadersDecoder(boolean validateHeaders, long maxHeaderListSize,
73                                        int initialHuffmanDecodeCapacity) {
74          this(validateHeaders, new HpackDecoder(maxHeaderListSize, initialHuffmanDecodeCapacity));
75      }
76  
77      /**
78       * Exposed Used for testing only! Default values used in the initial settings frame are overridden intentionally
79       * for testing but violate the RFC if used outside the scope of testing.
80       */
81      DefaultHttp2HeadersDecoder(boolean validateHeaders, HpackDecoder hpackDecoder) {
82          this.hpackDecoder = ObjectUtil.checkNotNull(hpackDecoder, "hpackDecoder");
83          this.validateHeaders = validateHeaders;
84          this.maxHeaderListSizeGoAway =
85                  Http2CodecUtil.calculateMaxHeaderListSizeGoAway(hpackDecoder.getMaxHeaderListSize());
86      }
87  
88      @Override
89      public void maxHeaderTableSize(long max) throws Http2Exception {
90          hpackDecoder.setMaxHeaderTableSize(max);
91      }
92  
93      @Override
94      public long maxHeaderTableSize() {
95          return hpackDecoder.getMaxHeaderTableSize();
96      }
97  
98      @Override
99      public void maxHeaderListSize(long max, long goAwayMax) throws Http2Exception {
100         if (goAwayMax < max || goAwayMax < 0) {
101             throw connectionError(INTERNAL_ERROR, "Header List Size GO_AWAY %d must be non-negative and >= %d",
102                     goAwayMax, max);
103         }
104         hpackDecoder.setMaxHeaderListSize(max);
105         this.maxHeaderListSizeGoAway = goAwayMax;
106     }
107 
108     @Override
109     public long maxHeaderListSize() {
110         return hpackDecoder.getMaxHeaderListSize();
111     }
112 
113     @Override
114     public long maxHeaderListSizeGoAway() {
115         return maxHeaderListSizeGoAway;
116     }
117 
118     @Override
119     public Configuration configuration() {
120         return this;
121     }
122 
123     @Override
124     public Http2Headers decodeHeaders(int streamId, ByteBuf headerBlock) throws Http2Exception {
125         try {
126             final Http2Headers headers = newHeaders();
127             hpackDecoder.decode(streamId, headerBlock, headers, validateHeaders);
128             headerArraySizeAccumulator = HEADERS_COUNT_WEIGHT_NEW * headers.size() +
129                                          HEADERS_COUNT_WEIGHT_HISTORICAL * headerArraySizeAccumulator;
130             return headers;
131         } catch (Http2Exception e) {
132             throw e;
133         } catch (Throwable e) {
134             // Default handler for any other types of errors that may have occurred. For example,
135             // the Header builder throws IllegalArgumentException if the key or value was invalid
136             // for any reason (e.g. the key was an invalid pseudo-header).
137             throw connectionError(COMPRESSION_ERROR, e, e.getMessage());
138         }
139     }
140 
141     /**
142      * A weighted moving average estimating how many headers are expected during the decode process.
143      * @return an estimate of how many headers are expected during the decode process.
144      */
145     protected final int numberOfHeadersGuess() {
146         return (int) headerArraySizeAccumulator;
147     }
148 
149     /**
150      * Determines if the headers should be validated as a result of the decode operation.
151      * @return {@code true} if the headers should be validated as a result of the decode operation.
152      */
153     protected final boolean validateHeaders() {
154         return validateHeaders;
155     }
156 
157     /**
158      * Create a new {@link Http2Headers} object which will store the results of the decode operation.
159      * @return a new {@link Http2Headers} object which will store the results of the decode operation.
160      */
161     protected Http2Headers newHeaders() {
162         return new DefaultHttp2Headers(validateHeaders, (int) headerArraySizeAccumulator);
163     }
164 }