View Javadoc
1   /*
2    * Copyright 2013 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.channel.ChannelHandler;
19  import io.netty5.channel.ChannelHandlerContext;
20  import io.netty5.channel.ChannelPipeline;
21  import io.netty5.handler.codec.http.HttpHeaders;
22  import io.netty5.util.Resource;
23  
24  import java.net.URI;
25  import java.util.Objects;
26  
27  import static io.netty5.handler.codec.http.websocketx.WebSocketClientProtocolConfig.DEFAULT_ALLOW_MASK_MISMATCH;
28  import static io.netty5.handler.codec.http.websocketx.WebSocketClientProtocolConfig.DEFAULT_DROP_PONG_FRAMES;
29  import static io.netty5.handler.codec.http.websocketx.WebSocketClientProtocolConfig.DEFAULT_HANDLE_CLOSE_FRAMES;
30  import static io.netty5.handler.codec.http.websocketx.WebSocketClientProtocolConfig.DEFAULT_PERFORM_MASKING;
31  import static io.netty5.handler.codec.http.websocketx.WebSocketServerProtocolConfig.DEFAULT_HANDSHAKE_TIMEOUT_MILLIS;
32  
33  /**
34   * This handler does all the heavy lifting for you to run a websocket client.
35   *
36   * It takes care of websocket handshaking as well as processing of Ping, Pong frames. Text and Binary
37   * data frames are passed to the next handler in the pipeline (implemented by you) for processing.
38   * Also the close frame is passed to the next handler as you may want inspect it before close the connection if
39   * the {@code handleCloseFrames} is {@code false}, default is {@code true}.
40   *
41   * This implementation will establish the websocket connection once the connection to the remote server was complete.
42   *
43   * To know once a handshake was done you can intercept the
44   * {@link ChannelHandler#channelInboundEvent(ChannelHandlerContext, Object)} and check if the event was of type
45   * {@link WebSocketHandshakeCompletionEvent}.
46   */
47  public class WebSocketClientProtocolHandler extends WebSocketProtocolHandler {
48      private final WebSocketClientHandshaker handshaker;
49      private final WebSocketClientProtocolConfig clientConfig;
50  
51      /**
52       * Returns the used handshaker
53       */
54      public WebSocketClientHandshaker handshaker() {
55          return handshaker;
56      }
57  
58      /**
59       * Base constructor
60       *
61       * @param clientConfig
62       *            Client protocol configuration.
63       */
64      public WebSocketClientProtocolHandler(WebSocketClientProtocolConfig clientConfig) {
65          super(Objects.requireNonNull(clientConfig, "clientConfig").dropPongFrames(),
66                clientConfig.sendCloseFrame(), clientConfig.forceCloseTimeoutMillis());
67          this.handshaker = WebSocketClientHandshakerFactory.newHandshaker(
68              clientConfig.webSocketUri(),
69              clientConfig.version(),
70              clientConfig.subprotocol(),
71              clientConfig.allowExtensions(),
72              clientConfig.customHeaders(),
73              clientConfig.maxFramePayloadLength(),
74              clientConfig.performMasking(),
75              clientConfig.allowMaskMismatch(),
76              clientConfig.forceCloseTimeoutMillis(),
77              clientConfig.absoluteUpgradeUrl()
78          );
79          this.clientConfig = clientConfig;
80      }
81  
82      /**
83       * Base constructor
84       *
85       * @param webSocketURL
86       *            URL for web socket communications. e.g "ws://myhost.com/mypath". Subsequent web socket frames will be
87       *            sent to this URL.
88       * @param version
89       *            Version of web socket specification to use to connect to the server
90       * @param subprotocol
91       *            Sub protocol request sent to the server.
92       * @param customHeaders
93       *            Map of custom headers to add to the client request
94       * @param maxFramePayloadLength
95       *            Maximum length of a frame's payload
96       * @param handleCloseFrames
97       *            {@code true} if close frames should not be forwarded and just close the channel
98       * @param performMasking
99       *            Whether to mask all written websocket frames. This must be set to true in order to be fully compatible
100      *            with the websocket specifications. Client applications that communicate with a non-standard server
101      *            which doesn't require masking might set this to false to achieve a higher performance.
102      * @param allowMaskMismatch
103      *            When set to true, frames which are not masked properly according to the standard will still be
104      *            accepted.
105      */
106     public WebSocketClientProtocolHandler(URI webSocketURL, WebSocketVersion version, String subprotocol,
107                                           boolean allowExtensions, HttpHeaders customHeaders,
108                                           int maxFramePayloadLength, boolean handleCloseFrames,
109                                           boolean performMasking, boolean allowMaskMismatch) {
110         this(webSocketURL, version, subprotocol, allowExtensions, customHeaders, maxFramePayloadLength,
111             handleCloseFrames, performMasking, allowMaskMismatch, DEFAULT_HANDSHAKE_TIMEOUT_MILLIS);
112     }
113 
114     /**
115      * Base constructor
116      *
117      * @param webSocketURL
118      *            URL for web socket communications. e.g "ws://myhost.com/mypath". Subsequent web socket frames will be
119      *            sent to this URL.
120      * @param version
121      *            Version of web socket specification to use to connect to the server
122      * @param subprotocol
123      *            Sub protocol request sent to the server.
124      * @param customHeaders
125      *            Map of custom headers to add to the client request
126      * @param maxFramePayloadLength
127      *            Maximum length of a frame's payload
128      * @param handleCloseFrames
129      *            {@code true} if close frames should not be forwarded and just close the channel
130      * @param performMasking
131      *            Whether to mask all written websocket frames. This must be set to true in order to be fully compatible
132      *            with the websocket specifications. Client applications that communicate with a non-standard server
133      *            which doesn't require masking might set this to false to achieve a higher performance.
134      * @param allowMaskMismatch
135      *            When set to true, frames which are not masked properly according to the standard will still be
136      *            accepted.
137      * @param handshakeTimeoutMillis
138      *            Handshake timeout in mills, when handshake timeout, will trigger an inbound channel
139      *            event {@link WebSocketClientHandshakeCompletionEvent} with a
140      *            {@link WebSocketHandshakeTimeoutException}.
141      */
142     public WebSocketClientProtocolHandler(URI webSocketURL, WebSocketVersion version, String subprotocol,
143                                           boolean allowExtensions, HttpHeaders customHeaders,
144                                           int maxFramePayloadLength, boolean handleCloseFrames, boolean performMasking,
145                                           boolean allowMaskMismatch, long handshakeTimeoutMillis) {
146         this(WebSocketClientHandshakerFactory.newHandshaker(webSocketURL, version, subprotocol,
147                                                             allowExtensions, customHeaders, maxFramePayloadLength,
148                                                             performMasking, allowMaskMismatch),
149              handleCloseFrames, handshakeTimeoutMillis);
150     }
151 
152     /**
153      * Base constructor
154      *
155      * @param webSocketURL
156      *            URL for web socket communications. e.g "ws://myhost.com/mypath". Subsequent web socket frames will be
157      *            sent to this URL.
158      * @param version
159      *            Version of web socket specification to use to connect to the server
160      * @param subprotocol
161      *            Sub protocol request sent to the server.
162      * @param customHeaders
163      *            Map of custom headers to add to the client request
164      * @param maxFramePayloadLength
165      *            Maximum length of a frame's payload
166      * @param handleCloseFrames
167      *            {@code true} if close frames should not be forwarded and just close the channel
168      */
169     public WebSocketClientProtocolHandler(URI webSocketURL, WebSocketVersion version, String subprotocol,
170                                                    boolean allowExtensions, HttpHeaders customHeaders,
171                                                    int maxFramePayloadLength, boolean handleCloseFrames) {
172         this(webSocketURL, version, subprotocol, allowExtensions, customHeaders, maxFramePayloadLength,
173              handleCloseFrames, DEFAULT_HANDSHAKE_TIMEOUT_MILLIS);
174     }
175 
176     /**
177      * Base constructor
178      *
179      * @param webSocketURL
180      *            URL for web socket communications. e.g "ws://myhost.com/mypath". Subsequent web socket frames will be
181      *            sent to this URL.
182      * @param version
183      *            Version of web socket specification to use to connect to the server
184      * @param subprotocol
185      *            Sub protocol request sent to the server.
186      * @param customHeaders
187      *            Map of custom headers to add to the client request
188      * @param maxFramePayloadLength
189      *            Maximum length of a frame's payload
190      * @param handleCloseFrames
191      *            {@code true} if close frames should not be forwarded and just close the channel
192      * @param handshakeTimeoutMillis
193      *            Handshake timeout in mills, when handshake timeout, will trigger an inbound channel
194      *            event {@link WebSocketClientHandshakeCompletionEvent} with a
195      *            {@link WebSocketHandshakeTimeoutException}.
196      */
197     public WebSocketClientProtocolHandler(URI webSocketURL, WebSocketVersion version, String subprotocol,
198                                           boolean allowExtensions, HttpHeaders customHeaders, int maxFramePayloadLength,
199                                           boolean handleCloseFrames, long handshakeTimeoutMillis) {
200         this(webSocketURL, version, subprotocol, allowExtensions, customHeaders, maxFramePayloadLength,
201              handleCloseFrames, DEFAULT_PERFORM_MASKING, DEFAULT_ALLOW_MASK_MISMATCH, handshakeTimeoutMillis);
202     }
203 
204     /**
205      * Base constructor
206      *
207      * @param webSocketURL
208      *            URL for web socket communications. e.g "ws://myhost.com/mypath". Subsequent web socket frames will be
209      *            sent to this URL.
210      * @param version
211      *            Version of web socket specification to use to connect to the server
212      * @param subprotocol
213      *            Sub protocol request sent to the server.
214      * @param customHeaders
215      *            Map of custom headers to add to the client request
216      * @param maxFramePayloadLength
217      *            Maximum length of a frame's payload
218      */
219     public WebSocketClientProtocolHandler(URI webSocketURL, WebSocketVersion version, String subprotocol,
220                                           boolean allowExtensions, HttpHeaders customHeaders,
221                                           int maxFramePayloadLength) {
222         this(webSocketURL, version, subprotocol, allowExtensions,
223              customHeaders, maxFramePayloadLength, DEFAULT_HANDSHAKE_TIMEOUT_MILLIS);
224     }
225 
226     /**
227      * Base constructor
228      *
229      * @param webSocketURL
230      *            URL for web socket communications. e.g "ws://myhost.com/mypath". Subsequent web socket frames will be
231      *            sent to this URL.
232      * @param version
233      *            Version of web socket specification to use to connect to the server
234      * @param subprotocol
235      *            Sub protocol request sent to the server.
236      * @param customHeaders
237      *            Map of custom headers to add to the client request
238      * @param maxFramePayloadLength
239      *            Maximum length of a frame's payload
240      * @param handshakeTimeoutMillis
241      *            Handshake timeout in mills, when handshake timeout, will trigger an inbound channel
242      *            event {@link WebSocketClientHandshakeCompletionEvent} with a
243      *            {@link WebSocketHandshakeTimeoutException}.
244      */
245     public WebSocketClientProtocolHandler(URI webSocketURL, WebSocketVersion version, String subprotocol,
246                                           boolean allowExtensions, HttpHeaders customHeaders,
247                                           int maxFramePayloadLength, long handshakeTimeoutMillis) {
248         this(webSocketURL, version, subprotocol, allowExtensions, customHeaders,
249              maxFramePayloadLength, DEFAULT_HANDLE_CLOSE_FRAMES, handshakeTimeoutMillis);
250     }
251 
252     /**
253      * Base constructor
254      *
255      * @param handshaker
256      *            The {@link WebSocketClientHandshaker} which will be used to issue the handshake once the connection
257      *            was established to the remote peer.
258      * @param handleCloseFrames
259      *            {@code true} if close frames should not be forwarded and just close the channel
260      */
261     public WebSocketClientProtocolHandler(WebSocketClientHandshaker handshaker, boolean handleCloseFrames) {
262         this(handshaker, handleCloseFrames, DEFAULT_HANDSHAKE_TIMEOUT_MILLIS);
263     }
264 
265     /**
266      * Base constructor
267      *
268      * @param handshaker
269      *            The {@link WebSocketClientHandshaker} which will be used to issue the handshake once the connection
270      *            was established to the remote peer.
271      * @param handleCloseFrames
272      *            {@code true} if close frames should not be forwarded and just close the channel
273      * @param handshakeTimeoutMillis
274      *            Handshake timeout in mills, when handshake timeout, will trigger an inbound channel
275      *            event {@link WebSocketClientHandshakeCompletionEvent} with a
276      *            {@link WebSocketHandshakeTimeoutException}.
277      */
278     public WebSocketClientProtocolHandler(WebSocketClientHandshaker handshaker, boolean handleCloseFrames,
279                                           long handshakeTimeoutMillis) {
280         this(handshaker, handleCloseFrames, DEFAULT_DROP_PONG_FRAMES, handshakeTimeoutMillis);
281     }
282 
283     /**
284      * Base constructor
285      *
286      * @param handshaker
287      *            The {@link WebSocketClientHandshaker} which will be used to issue the handshake once the connection
288      *            was established to the remote peer.
289      * @param handleCloseFrames
290      *            {@code true} if close frames should not be forwarded and just close the channel
291      * @param dropPongFrames
292      *            {@code true} if pong frames should not be forwarded
293      */
294     public WebSocketClientProtocolHandler(WebSocketClientHandshaker handshaker, boolean handleCloseFrames,
295                                           boolean dropPongFrames) {
296         this(handshaker, handleCloseFrames, dropPongFrames, DEFAULT_HANDSHAKE_TIMEOUT_MILLIS);
297     }
298 
299     /**
300      * Base constructor
301      *
302      * @param handshaker
303      *            The {@link WebSocketClientHandshaker} which will be used to issue the handshake once the connection
304      *            was established to the remote peer.
305      * @param handleCloseFrames
306      *            {@code true} if close frames should not be forwarded and just close the channel
307      * @param dropPongFrames
308      *            {@code true} if pong frames should not be forwarded
309      * @param handshakeTimeoutMillis
310      *            Handshake timeout in mills, when handshake timeout, will trigger an inbound channel
311      *            event {@link WebSocketClientHandshakeCompletionEvent} with a
312      *            {@link WebSocketHandshakeTimeoutException}.
313      */
314     public WebSocketClientProtocolHandler(WebSocketClientHandshaker handshaker, boolean handleCloseFrames,
315                                           boolean dropPongFrames, long handshakeTimeoutMillis) {
316         super(dropPongFrames);
317         this.handshaker = handshaker;
318         this.clientConfig = WebSocketClientProtocolConfig.newBuilder()
319             .handleCloseFrames(handleCloseFrames)
320             .handshakeTimeoutMillis(handshakeTimeoutMillis)
321             .build();
322     }
323 
324     /**
325      * Base constructor
326      *
327      * @param handshaker
328      *            The {@link WebSocketClientHandshaker} which will be used to issue the handshake once the connection
329      *            was established to the remote peer.
330      */
331     public WebSocketClientProtocolHandler(WebSocketClientHandshaker handshaker) {
332         this(handshaker, DEFAULT_HANDSHAKE_TIMEOUT_MILLIS);
333     }
334 
335     /**
336      * Base constructor
337      *
338      * @param handshaker
339      *            The {@link WebSocketClientHandshaker} which will be used to issue the handshake once the connection
340      *            was established to the remote peer.
341      * @param handshakeTimeoutMillis
342      *            Handshake timeout in mills, when handshake timeout, will trigger an inbound channel
343      *            event {@link WebSocketClientHandshakeCompletionEvent} with a
344      *            {@link WebSocketHandshakeTimeoutException}.
345      */
346     public WebSocketClientProtocolHandler(WebSocketClientHandshaker handshaker, long handshakeTimeoutMillis) {
347         this(handshaker, DEFAULT_HANDLE_CLOSE_FRAMES, handshakeTimeoutMillis);
348     }
349 
350     @Override
351     protected void decodeAndClose(ChannelHandlerContext ctx, WebSocketFrame frame) throws Exception {
352         if (clientConfig.handleCloseFrames() && frame instanceof CloseWebSocketFrame) {
353             Resource.dispose(frame);
354             ctx.close();
355             return;
356         }
357         super.decodeAndClose(ctx, frame);
358     }
359 
360     @Override
361     protected WebSocketClientHandshakeException buildHandshakeException(String message) {
362         return new WebSocketClientHandshakeException(message);
363     }
364 
365     @Override
366     public void handlerAdded(ChannelHandlerContext ctx) {
367         ChannelPipeline cp = ctx.pipeline();
368         if (cp.get(WebSocketClientProtocolHandshakeHandler.class) == null) {
369             // Add the WebSocketClientProtocolHandshakeHandler before this one.
370             ctx.pipeline().addBefore(ctx.name(), WebSocketClientProtocolHandshakeHandler.class.getName(),
371                 new WebSocketClientProtocolHandshakeHandler(handshaker, clientConfig.handshakeTimeoutMillis()));
372         }
373         if (cp.get(Utf8FrameValidator.class) == null) {
374             // Add the UFT8 checking before this one.
375             ctx.pipeline().addBefore(ctx.name(), Utf8FrameValidator.class.getName(),
376                     new Utf8FrameValidator());
377         }
378     }
379 }