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