View Javadoc
1   /*
2    * Copyright 2019 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.websocketx;
17  
18  import io.netty.handler.codec.http.EmptyHttpHeaders;
19  import io.netty.handler.codec.http.HttpHeaders;
20  import io.netty.handler.codec.http.websocketx.WebSocketClientProtocolHandler.ClientHandshakeStateEvent;
21  import io.netty.util.internal.ObjectUtil;
22  
23  import java.net.URI;
24  
25  import static io.netty.handler.codec.http.websocketx.WebSocketServerProtocolConfig.DEFAULT_FORCE_CLOSE_TIMEOUT_MILLIS;
26  import static io.netty.handler.codec.http.websocketx.WebSocketServerProtocolConfig.DEFAULT_HANDSHAKE_TIMEOUT_MILLIS;
27  import static io.netty.util.internal.ObjectUtil.checkPositive;
28  
29  /**
30   * WebSocket server configuration.
31   */
32  public final class WebSocketClientProtocolConfig {
33  
34      static final boolean DEFAULT_PERFORM_MASKING = true;
35      static final boolean DEFAULT_ALLOW_MASK_MISMATCH = false;
36      static final boolean DEFAULT_HANDLE_CLOSE_FRAMES = true;
37      static final boolean DEFAULT_DROP_PONG_FRAMES = true;
38      static final boolean DEFAULT_GENERATE_ORIGIN_HEADER = true;
39      static final boolean DEFAULT_WITH_UTF8_VALIDATOR = true;
40  
41      private final URI webSocketUri;
42      private final String subprotocol;
43      private final WebSocketVersion version;
44      private final boolean allowExtensions;
45      private final HttpHeaders customHeaders;
46      private final int maxFramePayloadLength;
47      private final boolean performMasking;
48      private final boolean allowMaskMismatch;
49      private final boolean handleCloseFrames;
50      private final WebSocketCloseStatus sendCloseFrame;
51      private final boolean dropPongFrames;
52      private final long handshakeTimeoutMillis;
53      private final long forceCloseTimeoutMillis;
54      private final boolean absoluteUpgradeUrl;
55      private final boolean generateOriginHeader;
56      private final boolean withUTF8Validator;
57  
58      private WebSocketClientProtocolConfig(
59          URI webSocketUri,
60          String subprotocol,
61          WebSocketVersion version,
62          boolean allowExtensions,
63          HttpHeaders customHeaders,
64          int maxFramePayloadLength,
65          boolean performMasking,
66          boolean allowMaskMismatch,
67          boolean handleCloseFrames,
68          WebSocketCloseStatus sendCloseFrame,
69          boolean dropPongFrames,
70          long handshakeTimeoutMillis,
71          long forceCloseTimeoutMillis,
72          boolean absoluteUpgradeUrl,
73          boolean generateOriginHeader,
74          boolean withUTF8Validator
75      ) {
76          this.webSocketUri = webSocketUri;
77          this.subprotocol = subprotocol;
78          this.version = version;
79          this.allowExtensions = allowExtensions;
80          this.customHeaders = customHeaders;
81          this.maxFramePayloadLength = maxFramePayloadLength;
82          this.performMasking = performMasking;
83          this.allowMaskMismatch = allowMaskMismatch;
84          this.forceCloseTimeoutMillis = forceCloseTimeoutMillis;
85          this.handleCloseFrames = handleCloseFrames;
86          this.sendCloseFrame = sendCloseFrame;
87          this.dropPongFrames = dropPongFrames;
88          this.handshakeTimeoutMillis = checkPositive(handshakeTimeoutMillis, "handshakeTimeoutMillis");
89          this.absoluteUpgradeUrl = absoluteUpgradeUrl;
90          this.generateOriginHeader = generateOriginHeader;
91          this.withUTF8Validator = withUTF8Validator;
92      }
93  
94      public URI webSocketUri() {
95          return webSocketUri;
96      }
97  
98      public String subprotocol() {
99          return subprotocol;
100     }
101 
102     public WebSocketVersion version() {
103         return version;
104     }
105 
106     public boolean allowExtensions() {
107         return allowExtensions;
108     }
109 
110     public HttpHeaders customHeaders() {
111         return customHeaders;
112     }
113 
114     public int maxFramePayloadLength() {
115         return maxFramePayloadLength;
116     }
117 
118     public boolean performMasking() {
119         return performMasking;
120     }
121 
122     public boolean allowMaskMismatch() {
123         return allowMaskMismatch;
124     }
125 
126     public boolean handleCloseFrames() {
127         return handleCloseFrames;
128     }
129 
130     public WebSocketCloseStatus sendCloseFrame() {
131         return sendCloseFrame;
132     }
133 
134     public boolean dropPongFrames() {
135         return dropPongFrames;
136     }
137 
138     public long handshakeTimeoutMillis() {
139         return handshakeTimeoutMillis;
140     }
141 
142     public long forceCloseTimeoutMillis() {
143         return forceCloseTimeoutMillis;
144     }
145 
146     public boolean absoluteUpgradeUrl() {
147         return absoluteUpgradeUrl;
148     }
149 
150     public boolean generateOriginHeader() {
151         return generateOriginHeader;
152     }
153 
154     public boolean withUTF8Validator() {
155         return withUTF8Validator;
156     }
157 
158     @Override
159     public String toString() {
160         return "WebSocketClientProtocolConfig" +
161                " {webSocketUri=" + webSocketUri +
162                ", subprotocol=" + subprotocol +
163                ", version=" + version +
164                ", allowExtensions=" + allowExtensions +
165                ", customHeaders=" + customHeaders +
166                ", maxFramePayloadLength=" + maxFramePayloadLength +
167                ", performMasking=" + performMasking +
168                ", allowMaskMismatch=" + allowMaskMismatch +
169                ", handleCloseFrames=" + handleCloseFrames +
170                ", sendCloseFrame=" + sendCloseFrame +
171                ", dropPongFrames=" + dropPongFrames +
172                ", handshakeTimeoutMillis=" + handshakeTimeoutMillis +
173                ", forceCloseTimeoutMillis=" + forceCloseTimeoutMillis +
174                ", absoluteUpgradeUrl=" + absoluteUpgradeUrl +
175                ", generateOriginHeader=" + generateOriginHeader +
176                "}";
177     }
178 
179     public Builder toBuilder() {
180         return new Builder(this);
181     }
182 
183     public static Builder newBuilder() {
184         return new Builder(
185                 URI.create("https://localhost/"),
186                 null,
187                 WebSocketVersion.V13,
188                 false,
189                 EmptyHttpHeaders.INSTANCE,
190                 65536,
191                 DEFAULT_PERFORM_MASKING,
192                 DEFAULT_ALLOW_MASK_MISMATCH,
193                 DEFAULT_HANDLE_CLOSE_FRAMES,
194                 WebSocketCloseStatus.NORMAL_CLOSURE,
195                 DEFAULT_DROP_PONG_FRAMES,
196                 DEFAULT_HANDSHAKE_TIMEOUT_MILLIS,
197                 DEFAULT_FORCE_CLOSE_TIMEOUT_MILLIS,
198                 false,
199                 DEFAULT_GENERATE_ORIGIN_HEADER,
200                 DEFAULT_WITH_UTF8_VALIDATOR);
201     }
202 
203     public static final class Builder {
204         private URI webSocketUri;
205         private String subprotocol;
206         private WebSocketVersion version;
207         private boolean allowExtensions;
208         private HttpHeaders customHeaders;
209         private int maxFramePayloadLength;
210         private boolean performMasking;
211         private boolean allowMaskMismatch;
212         private boolean handleCloseFrames;
213         private WebSocketCloseStatus sendCloseFrame;
214         private boolean dropPongFrames;
215         private long handshakeTimeoutMillis;
216         private long forceCloseTimeoutMillis;
217         private boolean absoluteUpgradeUrl;
218         private boolean generateOriginHeader;
219         private boolean withUTF8Validator;
220 
221         private Builder(WebSocketClientProtocolConfig clientConfig) {
222             this(ObjectUtil.checkNotNull(clientConfig, "clientConfig").webSocketUri(),
223                  clientConfig.subprotocol(),
224                  clientConfig.version(),
225                  clientConfig.allowExtensions(),
226                  clientConfig.customHeaders(),
227                  clientConfig.maxFramePayloadLength(),
228                  clientConfig.performMasking(),
229                  clientConfig.allowMaskMismatch(),
230                  clientConfig.handleCloseFrames(),
231                  clientConfig.sendCloseFrame(),
232                  clientConfig.dropPongFrames(),
233                  clientConfig.handshakeTimeoutMillis(),
234                  clientConfig.forceCloseTimeoutMillis(),
235                  clientConfig.absoluteUpgradeUrl(),
236                  clientConfig.generateOriginHeader(),
237                  clientConfig.withUTF8Validator());
238         }
239 
240         private Builder(URI webSocketUri,
241                         String subprotocol,
242                         WebSocketVersion version,
243                         boolean allowExtensions,
244                         HttpHeaders customHeaders,
245                         int maxFramePayloadLength,
246                         boolean performMasking,
247                         boolean allowMaskMismatch,
248                         boolean handleCloseFrames,
249                         WebSocketCloseStatus sendCloseFrame,
250                         boolean dropPongFrames,
251                         long handshakeTimeoutMillis,
252                         long forceCloseTimeoutMillis,
253                         boolean absoluteUpgradeUrl,
254                         boolean generateOriginHeader,
255                         boolean withUTF8Validator) {
256             this.webSocketUri = webSocketUri;
257             this.subprotocol = subprotocol;
258             this.version = version;
259             this.allowExtensions = allowExtensions;
260             this.customHeaders = customHeaders;
261             this.maxFramePayloadLength = maxFramePayloadLength;
262             this.performMasking = performMasking;
263             this.allowMaskMismatch = allowMaskMismatch;
264             this.handleCloseFrames = handleCloseFrames;
265             this.sendCloseFrame = sendCloseFrame;
266             this.dropPongFrames = dropPongFrames;
267             this.handshakeTimeoutMillis = handshakeTimeoutMillis;
268             this.forceCloseTimeoutMillis = forceCloseTimeoutMillis;
269             this.absoluteUpgradeUrl = absoluteUpgradeUrl;
270             this.generateOriginHeader = generateOriginHeader;
271             this.withUTF8Validator = withUTF8Validator;
272         }
273 
274         /**
275          * URL for web socket communications. e.g "ws://myhost.com/mypath". Subsequent web socket frames will be
276          * sent to this URL.
277          */
278         public Builder webSocketUri(String webSocketUri) {
279             return webSocketUri(URI.create(webSocketUri));
280         }
281 
282         /**
283          * URL for web socket communications. e.g "ws://myhost.com/mypath". Subsequent web socket frames will be
284          * sent to this URL.
285          */
286         public Builder webSocketUri(URI webSocketUri) {
287             this.webSocketUri = webSocketUri;
288             return this;
289         }
290 
291         /**
292          * Sub protocol request sent to the server.
293          */
294         public Builder subprotocol(String subprotocol) {
295             this.subprotocol = subprotocol;
296             return this;
297         }
298 
299         /**
300          * Version of web socket specification to use to connect to the server
301          */
302         public Builder version(WebSocketVersion version) {
303             this.version = version;
304             return this;
305         }
306 
307         /**
308          * Allow extensions to be used in the reserved bits of the web socket frame
309          */
310         public Builder allowExtensions(boolean allowExtensions) {
311             this.allowExtensions = allowExtensions;
312             return this;
313         }
314 
315         /**
316          * Map of custom headers to add to the client request
317          */
318         public Builder customHeaders(HttpHeaders customHeaders) {
319             this.customHeaders = customHeaders;
320             return this;
321         }
322 
323         /**
324          * Maximum length of a frame's payload
325          */
326         public Builder maxFramePayloadLength(int maxFramePayloadLength) {
327             this.maxFramePayloadLength = maxFramePayloadLength;
328             return this;
329         }
330 
331         /**
332          * Whether to mask all written websocket frames. This must be set to true in order to be fully compatible
333          * with the websocket specifications. Client applications that communicate with a non-standard server
334          * which doesn't require masking might set this to false to achieve a higher performance.
335          */
336         public Builder performMasking(boolean performMasking) {
337             this.performMasking = performMasking;
338             return this;
339         }
340 
341         /**
342          * When set to true, frames which are not masked properly according to the standard will still be accepted.
343          */
344         public Builder allowMaskMismatch(boolean allowMaskMismatch) {
345             this.allowMaskMismatch = allowMaskMismatch;
346             return this;
347         }
348 
349         /**
350          * {@code true} if close frames should not be forwarded and just close the channel
351          */
352         public Builder handleCloseFrames(boolean handleCloseFrames) {
353             this.handleCloseFrames = handleCloseFrames;
354             return this;
355         }
356 
357         /**
358          * Close frame to send, when close frame was not send manually. Or {@code null} to disable proper close.
359          */
360         public Builder sendCloseFrame(WebSocketCloseStatus sendCloseFrame) {
361             this.sendCloseFrame = sendCloseFrame;
362             return this;
363         }
364 
365         /**
366          * {@code true} if pong frames should not be forwarded
367          */
368         public Builder dropPongFrames(boolean dropPongFrames) {
369             this.dropPongFrames = dropPongFrames;
370             return this;
371         }
372 
373         /**
374          * Handshake timeout in mills, when handshake timeout, will trigger user
375          * event {@link ClientHandshakeStateEvent#HANDSHAKE_TIMEOUT}
376          */
377         public Builder handshakeTimeoutMillis(long handshakeTimeoutMillis) {
378             this.handshakeTimeoutMillis = handshakeTimeoutMillis;
379             return this;
380         }
381 
382         /**
383          * Close the connection if it was not closed by the server after timeout specified
384          */
385         public Builder forceCloseTimeoutMillis(long forceCloseTimeoutMillis) {
386             this.forceCloseTimeoutMillis = forceCloseTimeoutMillis;
387             return this;
388         }
389 
390         /**
391          * Use an absolute url for the Upgrade request, typically when connecting through an HTTP proxy over clear HTTP
392          */
393         public Builder absoluteUpgradeUrl(boolean absoluteUpgradeUrl) {
394             this.absoluteUpgradeUrl = absoluteUpgradeUrl;
395             return this;
396         }
397 
398         /**
399          * Allows to generate the `Origin`|`Sec-WebSocket-Origin` header value for handshake request
400          * according the given webSocketURI. Usually it's not necessary and can be disabled,
401          * but for backward compatibility is set to {@code true} as default.
402          */
403         public Builder generateOriginHeader(boolean generateOriginHeader) {
404             this.generateOriginHeader = generateOriginHeader;
405             return this;
406         }
407 
408         /**
409          * Toggles UTF8 validation for payload of text websocket frames. By default validation is enabled.
410          */
411         public Builder withUTF8Validator(boolean withUTF8Validator) {
412             this.withUTF8Validator = withUTF8Validator;
413             return this;
414         }
415 
416         /**
417          * Build unmodifiable client protocol configuration.
418          */
419         public WebSocketClientProtocolConfig build() {
420             return new WebSocketClientProtocolConfig(
421                 webSocketUri,
422                 subprotocol,
423                 version,
424                 allowExtensions,
425                 customHeaders,
426                 maxFramePayloadLength,
427                 performMasking,
428                 allowMaskMismatch,
429                 handleCloseFrames,
430                 sendCloseFrame,
431                 dropPongFrames,
432                 handshakeTimeoutMillis,
433                 forceCloseTimeoutMillis,
434                 absoluteUpgradeUrl,
435                 generateOriginHeader,
436                 withUTF8Validator
437             );
438         }
439     }
440 }