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.util.collection.CharObjectHashMap;
19  
20  import static io.netty.handler.codec.http2.Http2CodecUtil.DEFAULT_HEADER_LIST_SIZE;
21  import static io.netty.handler.codec.http2.Http2CodecUtil.MAX_CONCURRENT_STREAMS;
22  import static io.netty.handler.codec.http2.Http2CodecUtil.MAX_FRAME_SIZE_LOWER_BOUND;
23  import static io.netty.handler.codec.http2.Http2CodecUtil.MAX_FRAME_SIZE_UPPER_BOUND;
24  import static io.netty.handler.codec.http2.Http2CodecUtil.MAX_HEADER_LIST_SIZE;
25  import static io.netty.handler.codec.http2.Http2CodecUtil.MAX_HEADER_TABLE_SIZE;
26  import static io.netty.handler.codec.http2.Http2CodecUtil.MAX_INITIAL_WINDOW_SIZE;
27  import static io.netty.handler.codec.http2.Http2CodecUtil.MAX_UNSIGNED_INT;
28  import static io.netty.handler.codec.http2.Http2CodecUtil.MIN_CONCURRENT_STREAMS;
29  import static io.netty.handler.codec.http2.Http2CodecUtil.MIN_HEADER_LIST_SIZE;
30  import static io.netty.handler.codec.http2.Http2CodecUtil.MIN_HEADER_TABLE_SIZE;
31  import static io.netty.handler.codec.http2.Http2CodecUtil.MIN_INITIAL_WINDOW_SIZE;
32  import static io.netty.handler.codec.http2.Http2CodecUtil.NUM_STANDARD_SETTINGS;
33  import static io.netty.handler.codec.http2.Http2CodecUtil.SETTINGS_ENABLE_CONNECT_PROTOCOL;
34  import static io.netty.handler.codec.http2.Http2CodecUtil.SETTINGS_ENABLE_PUSH;
35  import static io.netty.handler.codec.http2.Http2CodecUtil.SETTINGS_HEADER_TABLE_SIZE;
36  import static io.netty.handler.codec.http2.Http2CodecUtil.SETTINGS_INITIAL_WINDOW_SIZE;
37  import static io.netty.handler.codec.http2.Http2CodecUtil.SETTINGS_MAX_CONCURRENT_STREAMS;
38  import static io.netty.handler.codec.http2.Http2CodecUtil.SETTINGS_MAX_FRAME_SIZE;
39  import static io.netty.handler.codec.http2.Http2CodecUtil.SETTINGS_MAX_HEADER_LIST_SIZE;
40  import static io.netty.handler.codec.http2.Http2CodecUtil.isMaxFrameSizeValid;
41  import static io.netty.util.internal.ObjectUtil.checkNotNull;
42  import static java.lang.Integer.toHexString;
43  
44  /**
45   * Settings for one endpoint in an HTTP/2 connection. Each of the values are optional as defined in
46   * the spec for the SETTINGS frame. Permits storage of arbitrary key/value pairs but provides helper
47   * methods for standard settings.
48   */
49  public final class Http2Settings extends CharObjectHashMap<Long> {
50      /**
51       * Default capacity based on the number of standard settings from the HTTP/2 spec, adjusted so that adding all of
52       * the standard settings will not cause the map capacity to change.
53       */
54      private static final int DEFAULT_CAPACITY = (int) (NUM_STANDARD_SETTINGS / DEFAULT_LOAD_FACTOR) + 1;
55      private static final Long FALSE = 0L;
56      private static final Long TRUE = 1L;
57  
58      public Http2Settings() {
59          this(DEFAULT_CAPACITY);
60      }
61  
62      public Http2Settings(int initialCapacity, float loadFactor) {
63          super(initialCapacity, loadFactor);
64      }
65  
66      public Http2Settings(int initialCapacity) {
67          super(initialCapacity);
68      }
69  
70      /**
71       * Adds the given setting key/value pair. For standard settings defined by the HTTP/2 spec, performs
72       * validation on the values.
73       *
74       * @throws IllegalArgumentException if verification for a standard HTTP/2 setting fails.
75       */
76      @Override
77      public Long put(char key, Long value) {
78          verifyStandardSetting(key, value);
79          return super.put(key, value);
80      }
81  
82      /**
83       * Gets the {@code SETTINGS_HEADER_TABLE_SIZE} value. If unavailable, returns {@code null}.
84       */
85      public Long headerTableSize() {
86          return get(SETTINGS_HEADER_TABLE_SIZE);
87      }
88  
89      /**
90       * Sets the {@code SETTINGS_HEADER_TABLE_SIZE} value.
91       *
92       * @throws IllegalArgumentException if verification of the setting fails.
93       */
94      public Http2Settings headerTableSize(long value) {
95          put(SETTINGS_HEADER_TABLE_SIZE, Long.valueOf(value));
96          return this;
97      }
98  
99      /**
100      * Gets the {@code SETTINGS_ENABLE_PUSH} value. If unavailable, returns {@code null}.
101      */
102     public Boolean pushEnabled() {
103         Long value = get(SETTINGS_ENABLE_PUSH);
104         if (value == null) {
105             return null;
106         }
107         return TRUE.equals(value);
108     }
109 
110     /**
111      * Sets the {@code SETTINGS_ENABLE_PUSH} value.
112      */
113     public Http2Settings pushEnabled(boolean enabled) {
114         put(SETTINGS_ENABLE_PUSH, enabled ? TRUE : FALSE);
115         return this;
116     }
117 
118     /**
119      * Gets the {@code SETTINGS_MAX_CONCURRENT_STREAMS} value. If unavailable, returns {@code null}.
120      */
121     public Long maxConcurrentStreams() {
122         return get(SETTINGS_MAX_CONCURRENT_STREAMS);
123     }
124 
125     /**
126      * Sets the {@code SETTINGS_MAX_CONCURRENT_STREAMS} value.
127      *
128      * @throws IllegalArgumentException if verification of the setting fails.
129      */
130     public Http2Settings maxConcurrentStreams(long value) {
131         put(SETTINGS_MAX_CONCURRENT_STREAMS, Long.valueOf(value));
132         return this;
133     }
134 
135     /**
136      * Gets the {@code SETTINGS_INITIAL_WINDOW_SIZE} value. If unavailable, returns {@code null}.
137      */
138     public Integer initialWindowSize() {
139         return getIntValue(SETTINGS_INITIAL_WINDOW_SIZE);
140     }
141 
142     /**
143      * Sets the {@code SETTINGS_INITIAL_WINDOW_SIZE} value.
144      *
145      * @throws IllegalArgumentException if verification of the setting fails.
146      */
147     public Http2Settings initialWindowSize(int value) {
148         put(SETTINGS_INITIAL_WINDOW_SIZE, Long.valueOf(value));
149         return this;
150     }
151 
152     /**
153      * Gets the {@code SETTINGS_MAX_FRAME_SIZE} value. If unavailable, returns {@code null}.
154      */
155     public Integer maxFrameSize() {
156         return getIntValue(SETTINGS_MAX_FRAME_SIZE);
157     }
158 
159     /**
160      * Sets the {@code SETTINGS_MAX_FRAME_SIZE} value.
161      *
162      * @throws IllegalArgumentException if verification of the setting fails.
163      */
164     public Http2Settings maxFrameSize(int value) {
165         put(SETTINGS_MAX_FRAME_SIZE, Long.valueOf(value));
166         return this;
167     }
168 
169     /**
170      * Gets the {@code SETTINGS_MAX_HEADER_LIST_SIZE} value. If unavailable, returns {@code null}.
171      */
172     public Long maxHeaderListSize() {
173         return get(SETTINGS_MAX_HEADER_LIST_SIZE);
174     }
175 
176     /**
177      * Sets the {@code SETTINGS_MAX_HEADER_LIST_SIZE} value.
178      *
179      * @throws IllegalArgumentException if verification of the setting fails.
180      */
181     public Http2Settings maxHeaderListSize(long value) {
182         put(SETTINGS_MAX_HEADER_LIST_SIZE, Long.valueOf(value));
183         return this;
184     }
185 
186     /**
187      * Gets the {@code SETTINGS_ENABLE_CONNECT_PROTOCOL} value. If unavailable, returns {@code null}.
188      */
189     public Boolean connectProtocolEnabled() {
190         Long value = get(SETTINGS_ENABLE_CONNECT_PROTOCOL);
191         if (value == null) {
192             return null;
193         }
194         return TRUE.equals(value);
195     }
196 
197     /**
198      * Sets the {@code SETTINGS_ENABLE_CONNECT_PROTOCOL} value.
199      */
200     public Http2Settings connectProtocolEnabled(boolean enabled) {
201         put(SETTINGS_ENABLE_CONNECT_PROTOCOL, enabled ? TRUE : FALSE);
202         return this;
203     }
204 
205     /**
206      * Clears and then copies the given settings into this object.
207      */
208     public Http2Settings copyFrom(Http2Settings settings) {
209         clear();
210         putAll(settings);
211         return this;
212     }
213 
214     /**
215      * A helper method that returns {@link Long#intValue()} on the return of {@link #get(char)}, if present. Note that
216      * if the range of the value exceeds {@link Integer#MAX_VALUE}, the {@link #get(char)} method should
217      * be used instead to avoid truncation of the value.
218      */
219     public Integer getIntValue(char key) {
220         Long value = get(key);
221         if (value == null) {
222             return null;
223         }
224         return value.intValue();
225     }
226 
227     private static void verifyStandardSetting(int key, Long value) {
228         checkNotNull(value, "value");
229         switch (key) {
230             case SETTINGS_HEADER_TABLE_SIZE:
231                 if (value < MIN_HEADER_TABLE_SIZE || value > MAX_HEADER_TABLE_SIZE) {
232                     throw new IllegalArgumentException("Setting HEADER_TABLE_SIZE is invalid: " + value +
233                             ", expected [" + MIN_HEADER_TABLE_SIZE + ", " + MAX_HEADER_TABLE_SIZE + ']');
234                 }
235                 break;
236             case SETTINGS_ENABLE_PUSH:
237                 if (value != 0L && value != 1L) {
238                     throw new IllegalArgumentException("Setting ENABLE_PUSH is invalid: " + value +
239                             ", expected [0, 1]");
240                 }
241                 break;
242             case SETTINGS_MAX_CONCURRENT_STREAMS:
243                 if (value < MIN_CONCURRENT_STREAMS || value > MAX_CONCURRENT_STREAMS) {
244                     throw new IllegalArgumentException("Setting MAX_CONCURRENT_STREAMS is invalid: " + value +
245                             ", expected [" + MIN_CONCURRENT_STREAMS + ", " + MAX_CONCURRENT_STREAMS + ']');
246                 }
247                 break;
248             case SETTINGS_INITIAL_WINDOW_SIZE:
249                 if (value < MIN_INITIAL_WINDOW_SIZE || value > MAX_INITIAL_WINDOW_SIZE) {
250                     throw new IllegalArgumentException("Setting INITIAL_WINDOW_SIZE is invalid: " + value +
251                             ", expected [" + MIN_INITIAL_WINDOW_SIZE + ", " + MAX_INITIAL_WINDOW_SIZE + ']');
252                 }
253                 break;
254             case SETTINGS_MAX_FRAME_SIZE:
255                 if (!isMaxFrameSizeValid(value.intValue())) {
256                     throw new IllegalArgumentException("Setting MAX_FRAME_SIZE is invalid: " + value +
257                             ", expected [" + MAX_FRAME_SIZE_LOWER_BOUND + ", " + MAX_FRAME_SIZE_UPPER_BOUND + ']');
258                 }
259                 break;
260             case SETTINGS_MAX_HEADER_LIST_SIZE:
261                 if (value < MIN_HEADER_LIST_SIZE || value > MAX_HEADER_LIST_SIZE) {
262                     throw new IllegalArgumentException("Setting MAX_HEADER_LIST_SIZE is invalid: " + value +
263                             ", expected [" + MIN_HEADER_LIST_SIZE + ", " + MAX_HEADER_LIST_SIZE + ']');
264                 }
265                 break;
266             case SETTINGS_ENABLE_CONNECT_PROTOCOL:
267                 if (value != 0L && value != 1L) {
268                     throw new IllegalArgumentException("Setting ENABLE_CONNECT_PROTOCOL is invalid: " + value +
269                             ", expected [0, 1]");
270                 }
271                 break;
272             default:
273                 // Non-standard HTTP/2 setting
274                 if (value < 0 || value > MAX_UNSIGNED_INT) {
275                     throw new IllegalArgumentException("Non-standard setting 0x" + toHexString(key) + " is invalid: " +
276                             value + ", expected unsigned 32-bit value");
277                 }
278                 break;
279         }
280     }
281 
282     @Override
283     protected String keyToString(char key) {
284         switch (key) {
285             case SETTINGS_HEADER_TABLE_SIZE:
286                 return "HEADER_TABLE_SIZE";
287             case SETTINGS_ENABLE_PUSH:
288                 return "ENABLE_PUSH";
289             case SETTINGS_MAX_CONCURRENT_STREAMS:
290                 return "MAX_CONCURRENT_STREAMS";
291             case SETTINGS_INITIAL_WINDOW_SIZE:
292                 return "INITIAL_WINDOW_SIZE";
293             case SETTINGS_MAX_FRAME_SIZE:
294                 return "MAX_FRAME_SIZE";
295             case SETTINGS_MAX_HEADER_LIST_SIZE:
296                 return "MAX_HEADER_LIST_SIZE";
297             case SETTINGS_ENABLE_CONNECT_PROTOCOL:
298                 return "ENABLE_CONNECT_PROTOCOL";
299             default:
300                 // Unknown keys.
301                 return "0x" + toHexString(key);
302         }
303     }
304 
305     public static Http2Settings defaultSettings() {
306         return new Http2Settings().maxHeaderListSize(DEFAULT_HEADER_LIST_SIZE);
307     }
308 }