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.websocketx.WebSocketClientProtocolHandler.ClientHandshakeStateEvent;
19  import io.netty.util.internal.ObjectUtil;
20  
21  import static io.netty.util.internal.ObjectUtil.checkPositive;
22  
23  /**
24   * WebSocket server configuration.
25   */
26  public final class WebSocketServerProtocolConfig {
27  
28      static final long DEFAULT_HANDSHAKE_TIMEOUT_MILLIS = 10000L;
29  
30      private final String websocketPath;
31      private final String subprotocols;
32      private final boolean checkStartsWith;
33      private final long handshakeTimeoutMillis;
34      private final long forceCloseTimeoutMillis;
35      private final boolean handleCloseFrames;
36      private final WebSocketCloseStatus sendCloseFrame;
37      private final boolean dropPongFrames;
38      private final WebSocketDecoderConfig decoderConfig;
39  
40      private WebSocketServerProtocolConfig(
41          String websocketPath,
42          String subprotocols,
43          boolean checkStartsWith,
44          long handshakeTimeoutMillis,
45          long forceCloseTimeoutMillis,
46          boolean handleCloseFrames,
47          WebSocketCloseStatus sendCloseFrame,
48          boolean dropPongFrames,
49          WebSocketDecoderConfig decoderConfig
50      ) {
51          this.websocketPath = websocketPath;
52          this.subprotocols = subprotocols;
53          this.checkStartsWith = checkStartsWith;
54          this.handshakeTimeoutMillis = checkPositive(handshakeTimeoutMillis, "handshakeTimeoutMillis");
55          this.forceCloseTimeoutMillis = forceCloseTimeoutMillis;
56          this.handleCloseFrames = handleCloseFrames;
57          this.sendCloseFrame = sendCloseFrame;
58          this.dropPongFrames = dropPongFrames;
59          this.decoderConfig = decoderConfig == null ? WebSocketDecoderConfig.DEFAULT : decoderConfig;
60      }
61  
62      public String websocketPath() {
63          return websocketPath;
64      }
65  
66      public String subprotocols() {
67          return subprotocols;
68      }
69  
70      public boolean checkStartsWith() {
71          return checkStartsWith;
72      }
73  
74      public long handshakeTimeoutMillis() {
75          return handshakeTimeoutMillis;
76      }
77  
78      public long forceCloseTimeoutMillis() {
79          return forceCloseTimeoutMillis;
80      }
81  
82      public boolean handleCloseFrames() {
83          return handleCloseFrames;
84      }
85  
86      public WebSocketCloseStatus sendCloseFrame() {
87          return sendCloseFrame;
88      }
89  
90      public boolean dropPongFrames() {
91          return dropPongFrames;
92      }
93  
94      public WebSocketDecoderConfig decoderConfig() {
95          return decoderConfig;
96      }
97  
98      @Override
99      public String toString() {
100         return "WebSocketServerProtocolConfig" +
101             " {websocketPath=" + websocketPath +
102             ", subprotocols=" + subprotocols +
103             ", checkStartsWith=" + checkStartsWith +
104             ", handshakeTimeoutMillis=" + handshakeTimeoutMillis +
105             ", forceCloseTimeoutMillis=" + forceCloseTimeoutMillis +
106             ", handleCloseFrames=" + handleCloseFrames +
107             ", sendCloseFrame=" + sendCloseFrame +
108             ", dropPongFrames=" + dropPongFrames +
109             ", decoderConfig=" + decoderConfig +
110             "}";
111     }
112 
113     public Builder toBuilder() {
114         return new Builder(this);
115     }
116 
117     public static Builder newBuilder() {
118         return new Builder("/", null, false, DEFAULT_HANDSHAKE_TIMEOUT_MILLIS, 0L,
119                            true, WebSocketCloseStatus.NORMAL_CLOSURE, true, WebSocketDecoderConfig.DEFAULT);
120     }
121 
122     public static final class Builder {
123         private String websocketPath;
124         private String subprotocols;
125         private boolean checkStartsWith;
126         private long handshakeTimeoutMillis;
127         private long forceCloseTimeoutMillis;
128         private boolean handleCloseFrames;
129         private WebSocketCloseStatus sendCloseFrame;
130         private boolean dropPongFrames;
131         private WebSocketDecoderConfig decoderConfig;
132         private WebSocketDecoderConfig.Builder decoderConfigBuilder;
133 
134         private Builder(WebSocketServerProtocolConfig serverConfig) {
135             this(ObjectUtil.checkNotNull(serverConfig, "serverConfig").websocketPath(),
136                  serverConfig.subprotocols(),
137                  serverConfig.checkStartsWith(),
138                  serverConfig.handshakeTimeoutMillis(),
139                  serverConfig.forceCloseTimeoutMillis(),
140                  serverConfig.handleCloseFrames(),
141                  serverConfig.sendCloseFrame(),
142                  serverConfig.dropPongFrames(),
143                  serverConfig.decoderConfig()
144             );
145         }
146 
147         private Builder(String websocketPath,
148                         String subprotocols,
149                         boolean checkStartsWith,
150                         long handshakeTimeoutMillis,
151                         long forceCloseTimeoutMillis,
152                         boolean handleCloseFrames,
153                         WebSocketCloseStatus sendCloseFrame,
154                         boolean dropPongFrames,
155                         WebSocketDecoderConfig decoderConfig) {
156             this.websocketPath = websocketPath;
157             this.subprotocols = subprotocols;
158             this.checkStartsWith = checkStartsWith;
159             this.handshakeTimeoutMillis = handshakeTimeoutMillis;
160             this.forceCloseTimeoutMillis = forceCloseTimeoutMillis;
161             this.handleCloseFrames = handleCloseFrames;
162             this.sendCloseFrame = sendCloseFrame;
163             this.dropPongFrames = dropPongFrames;
164             this.decoderConfig = decoderConfig;
165         }
166 
167         /**
168          * URI path component to handle websocket upgrade requests on.
169          */
170         public Builder websocketPath(String websocketPath) {
171             this.websocketPath = websocketPath;
172             return this;
173         }
174 
175         /**
176          * CSV of supported protocols
177          */
178         public Builder subprotocols(String subprotocols) {
179             this.subprotocols = subprotocols;
180             return this;
181         }
182 
183         /**
184          * {@code true} to handle all requests, where URI path component starts from
185          * {@link WebSocketServerProtocolConfig#websocketPath()}, {@code false} for exact match (default).
186          */
187         public Builder checkStartsWith(boolean checkStartsWith) {
188             this.checkStartsWith = checkStartsWith;
189             return this;
190         }
191 
192         /**
193          * Handshake timeout in mills, when handshake timeout, will trigger user
194          * event {@link ClientHandshakeStateEvent#HANDSHAKE_TIMEOUT}
195          */
196         public Builder handshakeTimeoutMillis(long handshakeTimeoutMillis) {
197             this.handshakeTimeoutMillis = handshakeTimeoutMillis;
198             return this;
199         }
200 
201         /**
202          * Close the connection if it was not closed by the client after timeout specified
203          */
204         public Builder forceCloseTimeoutMillis(long forceCloseTimeoutMillis) {
205             this.forceCloseTimeoutMillis = forceCloseTimeoutMillis;
206             return this;
207         }
208 
209         /**
210          * {@code true} if close frames should not be forwarded and just close the channel
211          */
212         public Builder handleCloseFrames(boolean handleCloseFrames) {
213             this.handleCloseFrames = handleCloseFrames;
214             return this;
215         }
216 
217         /**
218          * Close frame to send, when close frame was not send manually. Or {@code null} to disable proper close.
219          */
220         public Builder sendCloseFrame(WebSocketCloseStatus sendCloseFrame) {
221             this.sendCloseFrame = sendCloseFrame;
222             return this;
223         }
224 
225         /**
226          * {@code true} if pong frames should not be forwarded
227          */
228         public Builder dropPongFrames(boolean dropPongFrames) {
229             this.dropPongFrames = dropPongFrames;
230             return this;
231         }
232 
233         /**
234          * Frames decoder configuration.
235          */
236         public Builder decoderConfig(WebSocketDecoderConfig decoderConfig) {
237             this.decoderConfig = decoderConfig == null ? WebSocketDecoderConfig.DEFAULT : decoderConfig;
238             this.decoderConfigBuilder = null;
239             return this;
240         }
241 
242         private WebSocketDecoderConfig.Builder decoderConfigBuilder() {
243             if (decoderConfigBuilder == null) {
244                 decoderConfigBuilder = decoderConfig.toBuilder();
245             }
246             return decoderConfigBuilder;
247         }
248 
249         public Builder maxFramePayloadLength(int maxFramePayloadLength) {
250             decoderConfigBuilder().maxFramePayloadLength(maxFramePayloadLength);
251             return this;
252         }
253 
254         public Builder expectMaskedFrames(boolean expectMaskedFrames) {
255             decoderConfigBuilder().expectMaskedFrames(expectMaskedFrames);
256             return this;
257         }
258 
259         public Builder allowMaskMismatch(boolean allowMaskMismatch) {
260             decoderConfigBuilder().allowMaskMismatch(allowMaskMismatch);
261             return this;
262         }
263 
264         public Builder allowExtensions(boolean allowExtensions) {
265             decoderConfigBuilder().allowExtensions(allowExtensions);
266             return this;
267         }
268 
269         public Builder closeOnProtocolViolation(boolean closeOnProtocolViolation) {
270             decoderConfigBuilder().closeOnProtocolViolation(closeOnProtocolViolation);
271             return this;
272         }
273 
274         public Builder withUTF8Validator(boolean withUTF8Validator) {
275             decoderConfigBuilder().withUTF8Validator(withUTF8Validator);
276             return this;
277         }
278 
279         /**
280          * Build unmodifiable server protocol configuration.
281          */
282         public WebSocketServerProtocolConfig build() {
283             return new WebSocketServerProtocolConfig(
284                 websocketPath,
285                 subprotocols,
286                 checkStartsWith,
287                 handshakeTimeoutMillis,
288                 forceCloseTimeoutMillis,
289                 handleCloseFrames,
290                 sendCloseFrame,
291                 dropPongFrames,
292                 decoderConfigBuilder == null ? decoderConfig : decoderConfigBuilder.build()
293             );
294         }
295     }
296 }