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