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    *   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.channel.ChannelHandlerContext;
19  import io.netty.channel.ChannelInboundHandler;
20  import io.netty.channel.ChannelPipeline;
21  import io.netty.handler.codec.http.HttpHeaders;
22  
23  import java.net.URI;
24  import java.util.List;
25  
26  /**
27   * This handler does all the heavy lifting for you to run a websocket client.
28   *
29   * It takes care of websocket handshaking as well as processing of Ping, Pong frames. Text and Binary
30   * data frames are passed to the next handler in the pipeline (implemented by you) for processing.
31   * Also the close frame is passed to the next handler as you may want inspect it before close the connection if
32   * the {@code handleCloseFrames} is {@code false}, default is {@code true}.
33   *
34   * This implementation will establish the websocket connection once the connection to the remote server was complete.
35   *
36   * To know once a handshake was done you can intercept the
37   * {@link ChannelInboundHandler#userEventTriggered(ChannelHandlerContext, Object)} and check if the event was of type
38   * {@link ClientHandshakeStateEvent#HANDSHAKE_ISSUED} or {@link ClientHandshakeStateEvent#HANDSHAKE_COMPLETE}.
39   */
40  public class WebSocketClientProtocolHandler extends WebSocketProtocolHandler {
41  
42      private final WebSocketClientHandshaker handshaker;
43      private final boolean handleCloseFrames;
44  
45      /**
46       * Returns the used handshaker
47       */
48      public WebSocketClientHandshaker handshaker() { return handshaker; }
49  
50      /**
51       * Events that are fired to notify about handshake status
52       */
53      public enum ClientHandshakeStateEvent {
54          /**
55           * The Handshake was started but the server did not response yet to the request
56           */
57          HANDSHAKE_ISSUED,
58  
59          /**
60           * The Handshake was complete succesful and so the channel was upgraded to websockets
61           */
62          HANDSHAKE_COMPLETE
63      }
64  
65      /**
66       * Base constructor
67       *
68       * @param webSocketURL
69       *            URL for web socket communications. e.g "ws://myhost.com/mypath". Subsequent web socket frames will be
70       *            sent to this URL.
71       * @param version
72       *            Version of web socket specification to use to connect to the server
73       * @param subprotocol
74       *            Sub protocol request sent to the server.
75       * @param customHeaders
76       *            Map of custom headers to add to the client request
77       * @param maxFramePayloadLength
78       *            Maximum length of a frame's payload
79       * @param handleCloseFrames
80       *            {@code true} if close frames should not be forwarded and just close the channel
81       * @param performMasking
82       *            Whether to mask all written websocket frames. This must be set to true in order to be fully compatible
83       *            with the websocket specifications. Client applications that communicate with a non-standard server
84       *            which doesn't require masking might set this to false to achieve a higher performance.
85       * @param allowMaskMismatch
86       *            When set to true, frames which are not masked properly according to the standard will still be
87       *            accepted.
88       */
89      public WebSocketClientProtocolHandler(URI webSocketURL, WebSocketVersion version, String subprotocol,
90                                            boolean allowExtensions, HttpHeaders customHeaders,
91                                            int maxFramePayloadLength, boolean handleCloseFrames,
92                                            boolean performMasking, boolean allowMaskMismatch) {
93          this(WebSocketClientHandshakerFactory.newHandshaker(webSocketURL, version, subprotocol,
94                                                              allowExtensions, customHeaders, maxFramePayloadLength,
95                                                              performMasking, allowMaskMismatch), handleCloseFrames);
96      }
97  
98      /**
99       * Base constructor
100      *
101      * @param webSocketURL
102      *            URL for web socket communications. e.g "ws://myhost.com/mypath". Subsequent web socket frames will be
103      *            sent to this URL.
104      * @param version
105      *            Version of web socket specification to use to connect to the server
106      * @param subprotocol
107      *            Sub protocol request sent to the server.
108      * @param customHeaders
109      *            Map of custom headers to add to the client request
110      * @param maxFramePayloadLength
111      *            Maximum length of a frame's payload
112      * @param handleCloseFrames
113      *            {@code true} if close frames should not be forwarded and just close the channel
114      */
115     public WebSocketClientProtocolHandler(URI webSocketURL, WebSocketVersion version, String subprotocol,
116                                                    boolean allowExtensions, HttpHeaders customHeaders,
117                                                    int maxFramePayloadLength, boolean handleCloseFrames) {
118         this(webSocketURL, version, subprotocol, allowExtensions, customHeaders, maxFramePayloadLength,
119              handleCloseFrames, true, false);
120     }
121 
122     /**
123      * Base constructor
124      *
125      * @param webSocketURL
126      *            URL for web socket communications. e.g "ws://myhost.com/mypath". Subsequent web socket frames will be
127      *            sent to this URL.
128      * @param version
129      *            Version of web socket specification to use to connect to the server
130      * @param subprotocol
131      *            Sub protocol request sent to the server.
132      * @param customHeaders
133      *            Map of custom headers to add to the client request
134      * @param maxFramePayloadLength
135      *            Maximum length of a frame's payload
136      */
137     public WebSocketClientProtocolHandler(URI webSocketURL, WebSocketVersion version, String subprotocol,
138                                           boolean allowExtensions, HttpHeaders customHeaders,
139                                           int maxFramePayloadLength) {
140         this(webSocketURL, version, subprotocol,
141                 allowExtensions, customHeaders, maxFramePayloadLength, true);
142     }
143 
144     /**
145      * Base constructor
146      *
147      * @param handshaker
148      *            The {@link WebSocketClientHandshaker} which will be used to issue the handshake once the connection
149      *            was established to the remote peer.
150      * @param handleCloseFrames
151      *            {@code true} if close frames should not be forwarded and just close the channel
152      */
153     public WebSocketClientProtocolHandler(WebSocketClientHandshaker handshaker, boolean handleCloseFrames) {
154         this(handshaker, handleCloseFrames, true);
155     }
156 
157     /**
158      * Base constructor
159      *
160      * @param handshaker
161      *            The {@link WebSocketClientHandshaker} which will be used to issue the handshake once the connection
162      *            was established to the remote peer.
163      * @param handleCloseFrames
164      *            {@code true} if close frames should not be forwarded and just close the channel
165      * @param dropPongFrames
166      *            {@code true} if pong frames should not be forwarded
167      */
168     public WebSocketClientProtocolHandler(WebSocketClientHandshaker handshaker, boolean handleCloseFrames,
169                                           boolean dropPongFrames) {
170         super(dropPongFrames);
171         this.handshaker = handshaker;
172         this.handleCloseFrames = handleCloseFrames;
173     }
174 
175     /**
176      * Base constructor
177      *
178      * @param handshaker
179      *            The {@link WebSocketClientHandshaker} which will be used to issue the handshake once the connection
180      *            was established to the remote peer.
181      */
182     public WebSocketClientProtocolHandler(WebSocketClientHandshaker handshaker) {
183         this(handshaker, true);
184     }
185 
186     @Override
187     protected void decode(ChannelHandlerContext ctx, WebSocketFrame frame, List<Object> out) throws Exception {
188         if (handleCloseFrames && frame instanceof CloseWebSocketFrame) {
189             ctx.close();
190             return;
191         }
192         super.decode(ctx, frame, out);
193     }
194 
195     @Override
196     public void handlerAdded(ChannelHandlerContext ctx) {
197         ChannelPipeline cp = ctx.pipeline();
198         if (cp.get(WebSocketClientProtocolHandshakeHandler.class) == null) {
199             // Add the WebSocketClientProtocolHandshakeHandler before this one.
200             ctx.pipeline().addBefore(ctx.name(), WebSocketClientProtocolHandshakeHandler.class.getName(),
201                     new WebSocketClientProtocolHandshakeHandler(handshaker));
202         }
203         if (cp.get(Utf8FrameValidator.class) == null) {
204             // Add the UFT8 checking before this one.
205             ctx.pipeline().addBefore(ctx.name(), Utf8FrameValidator.class.getName(),
206                     new Utf8FrameValidator());
207         }
208     }
209 }