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