View Javadoc
1   /*
2    * Copyright 2012 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 org.jboss.netty.handler.codec.http.websocketx;
17  
18  import org.jboss.netty.channel.Channel;
19  import org.jboss.netty.channel.ChannelFuture;
20  import org.jboss.netty.channel.ChannelHandler;
21  import org.jboss.netty.channel.ChannelHandlerContext;
22  import org.jboss.netty.channel.ChannelPipeline;
23  import org.jboss.netty.handler.codec.http.HttpResponse;
24  import org.jboss.netty.handler.codec.http.HttpResponseDecoder;
25  
26  import java.net.URI;
27  import java.util.Map;
28  
29  /**
30   * Base class for web socket client handshake implementations
31   */
32  public abstract class WebSocketClientHandshaker {
33  
34      private final URI webSocketUrl;
35  
36      private final WebSocketVersion version;
37  
38      private volatile boolean handshakeComplete;
39  
40      private final String expectedSubprotocol;
41  
42      private volatile String actualSubprotocol;
43  
44      protected final Map<String, String> customHeaders;
45  
46      private final long maxFramePayloadLength;
47  
48      /**
49       * Base constructor with default values
50       *
51       * @param webSocketUrl
52       *            URL for web socket communications. e.g "ws://myhost.com/mypath". Subsequent web socket frames will be
53       *            sent to this URL.
54       * @param version
55       *            Version of web socket specification to use to connect to the server
56       * @param subprotocol
57       *            Sub protocol request sent to the server.
58       * @param customHeaders
59       *            Map of custom headers to add to the client request
60       */
61      protected WebSocketClientHandshaker(URI webSocketUrl, WebSocketVersion version, String subprotocol,
62                                          Map<String, String> customHeaders) {
63          this(webSocketUrl, version, subprotocol, customHeaders, Long.MAX_VALUE);
64      }
65  
66      /**
67       * Base constructor
68       *
69       * @param webSocketUrl
70       *            URL for web socket communications. e.g "ws://myhost.com/mypath". Subsequent web socket frames will be
71       *            sent to this URL.
72       * @param version
73       *            Version of web socket specification to use to connect to the server
74       * @param subprotocol
75       *            CSV of requested subprotocol(s) sent to the server.
76       * @param customHeaders
77       *            Map of custom headers to add to the client request
78       * @param maxFramePayloadLength
79       *            Maximum length of a frame's payload
80       */
81      protected WebSocketClientHandshaker(URI webSocketUrl, WebSocketVersion version, String subprotocol,
82                                          Map<String, String> customHeaders, long maxFramePayloadLength) {
83          this.webSocketUrl = webSocketUrl;
84          this.version = version;
85          expectedSubprotocol = subprotocol;
86          this.customHeaders = customHeaders;
87          this.maxFramePayloadLength = maxFramePayloadLength;
88      }
89  
90      /**
91       * Returns the URI to the web socket. e.g. "ws://myhost.com/path"
92       */
93      public URI getWebSocketUrl() {
94          return webSocketUrl;
95      }
96  
97      /**
98       * Version of the web socket specification that is being used
99       */
100     public WebSocketVersion getVersion() {
101         return version;
102     }
103 
104     /**
105      * Returns the max length for any frame's payload
106      */
107     public long getMaxFramePayloadLength() {
108         return maxFramePayloadLength;
109     }
110 
111     /**
112      * Flag to indicate if the opening handshake is complete
113      */
114     public boolean isHandshakeComplete() {
115         return handshakeComplete;
116     }
117 
118     protected void setHandshakeComplete() {
119         handshakeComplete = true;
120     }
121 
122     /**
123      * Returns the CSV of requested subprotocol(s) sent to the server as specified in the constructor
124      */
125     public String getExpectedSubprotocol() {
126         return expectedSubprotocol;
127     }
128 
129     /**
130      * Returns the subprotocol response sent by the server. Only available after end of handshake.
131      * Null if no subprotocol was requested or confirmed by the server.
132      */
133     public String getActualSubprotocol() {
134         return actualSubprotocol;
135     }
136 
137     protected void setActualSubprotocol(String actualSubprotocol) {
138         this.actualSubprotocol = actualSubprotocol;
139     }
140 
141     /**
142      * Begins the opening handshake
143      *
144      * @param channel
145      *            Channel
146      */
147     public abstract ChannelFuture handshake(Channel channel) throws Exception;
148 
149     /**
150      * Validates and finishes the opening handshake initiated by {@link #handshake}}.
151      *
152      * @param channel
153      *            Channel
154      * @param response
155      *            HTTP response containing the closing handshake details
156      */
157     public abstract void finishHandshake(Channel channel, HttpResponse response);
158 
159     /**
160      * Replace the HTTP decoder with a new Web Socket decoder.
161      * Note that we do not use {@link ChannelPipeline#replace(String, String, ChannelHandler)}, because the server
162      * might have sent the first frame immediately after the upgrade response.  In such a case, the HTTP decoder might
163      * have the first frame in its cumulation buffer and the HTTP decoder will forward it to the next handler.
164      * The Web Socket decoder will not receive it if we simply replaced it.  For more information, refer to
165      * {@link HttpResponseDecoder} and its unit tests.
166      */
167     static void replaceDecoder(Channel channel, ChannelHandler wsDecoder) {
168         ChannelPipeline p = channel.getPipeline();
169         ChannelHandlerContext httpDecoderCtx = p.getContext(HttpResponseDecoder.class);
170         if (httpDecoderCtx == null) {
171             throw new IllegalStateException("can't find an HTTP decoder from the pipeline");
172         }
173         p.addAfter(httpDecoderCtx.getName(), "ws-decoder", wsDecoder);
174         p.remove(httpDecoderCtx.getName());
175     }
176 }