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