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 }