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