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