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