1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.jboss.netty.handler.codec.http.websocketx;
17
18 import org.jboss.netty.buffer.ChannelBuffer;
19 import org.jboss.netty.buffer.ChannelBuffers;
20 import org.jboss.netty.channel.Channel;
21 import org.jboss.netty.channel.ChannelFuture;
22 import org.jboss.netty.channel.ChannelFutureListener;
23 import org.jboss.netty.channel.ChannelPipeline;
24 import org.jboss.netty.channel.DefaultChannelFuture;
25 import org.jboss.netty.handler.codec.http.DefaultHttpRequest;
26 import org.jboss.netty.handler.codec.http.HttpHeaders.Names;
27 import org.jboss.netty.handler.codec.http.HttpHeaders.Values;
28 import org.jboss.netty.handler.codec.http.HttpMethod;
29 import org.jboss.netty.handler.codec.http.HttpRequest;
30 import org.jboss.netty.handler.codec.http.HttpRequestEncoder;
31 import org.jboss.netty.handler.codec.http.HttpResponse;
32 import org.jboss.netty.handler.codec.http.HttpResponseDecoder;
33 import org.jboss.netty.handler.codec.http.HttpResponseStatus;
34 import org.jboss.netty.handler.codec.http.HttpVersion;
35 import org.jboss.netty.logging.InternalLogger;
36 import org.jboss.netty.logging.InternalLoggerFactory;
37 import org.jboss.netty.util.CharsetUtil;
38
39 import java.net.URI;
40 import java.util.Map;
41
42
43
44
45
46
47
48
49 public class WebSocketClientHandshaker08 extends WebSocketClientHandshaker {
50
51 private static final InternalLogger logger = InternalLoggerFactory.getInstance(WebSocketClientHandshaker08.class);
52
53 public static final String MAGIC_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
54
55 private String expectedChallengeResponseString;
56
57 private final boolean allowExtensions;
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74 public WebSocketClientHandshaker08(URI webSocketURL, WebSocketVersion version, String subprotocol,
75 boolean allowExtensions, Map<String, String> customHeaders) {
76 this(webSocketURL, version, subprotocol, allowExtensions, customHeaders, Long.MAX_VALUE);
77 }
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96 public WebSocketClientHandshaker08(URI webSocketURL, WebSocketVersion version, String subprotocol,
97 boolean allowExtensions, Map<String, String> customHeaders, long maxFramePayloadLength) {
98 super(webSocketURL, version, subprotocol, customHeaders, maxFramePayloadLength);
99 this.allowExtensions = allowExtensions;
100 }
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123 @Override
124 public ChannelFuture handshake(Channel channel) throws Exception {
125
126 URI wsURL = getWebSocketUrl();
127 String path = wsURL.getPath();
128 if (wsURL.getQuery() != null && wsURL.getQuery().length() > 0) {
129 path = wsURL.getPath() + '?' + wsURL.getQuery();
130 }
131
132 if (path == null || path.length() == 0) {
133 path = "/";
134 }
135
136
137 ChannelBuffer nonce = ChannelBuffers.wrappedBuffer(WebSocketUtil.randomBytes(16));
138 String key = WebSocketUtil.base64(nonce);
139
140 String acceptSeed = key + MAGIC_GUID;
141 ChannelBuffer sha1 = WebSocketUtil.sha1(ChannelBuffers.copiedBuffer(acceptSeed, CharsetUtil.US_ASCII));
142 expectedChallengeResponseString = WebSocketUtil.base64(sha1);
143
144 if (logger.isDebugEnabled()) {
145 logger.debug(String.format("WS Version 08 Client Handshake key: %s. Expected response: %s.", key,
146 expectedChallengeResponseString));
147 }
148
149
150 HttpRequest request = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, path);
151 request.addHeader(Names.UPGRADE, Values.WEBSOCKET.toLowerCase());
152 request.addHeader(Names.CONNECTION, Values.UPGRADE);
153 request.addHeader(Names.SEC_WEBSOCKET_KEY, key);
154 request.addHeader(Names.HOST, wsURL.getHost());
155
156 int wsPort = wsURL.getPort();
157 String originValue = "http://" + wsURL.getHost();
158 if (wsPort != 80 && wsPort != 443) {
159
160
161 originValue = originValue + ':' + wsPort;
162 }
163
164
165
166 request.addHeader(Names.SEC_WEBSOCKET_ORIGIN, originValue);
167
168 String expectedSubprotocol = getExpectedSubprotocol();
169 if (expectedSubprotocol != null && expectedSubprotocol.length() != 0) {
170 request.addHeader(Names.SEC_WEBSOCKET_PROTOCOL, expectedSubprotocol);
171 }
172
173 request.addHeader(Names.SEC_WEBSOCKET_VERSION, "8");
174
175 if (customHeaders != null) {
176 for (Map.Entry<String, String> e: customHeaders.entrySet()) {
177 request.addHeader(e.getKey(), e.getValue());
178 }
179 }
180
181 final ChannelFuture handshakeFuture = new DefaultChannelFuture(channel, false);
182 ChannelFuture future = channel.write(request);
183
184 future.addListener(new ChannelFutureListener() {
185 public void operationComplete(ChannelFuture future) {
186 ChannelPipeline p = future.getChannel().getPipeline();
187 p.replace(HttpRequestEncoder.class, "ws-encoder", new WebSocket08FrameEncoder(true));
188
189 if (future.isSuccess()) {
190 handshakeFuture.setSuccess();
191 } else {
192 handshakeFuture.setFailure(future.getCause());
193 }
194 }
195 });
196
197 return handshakeFuture;
198 }
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219 @Override
220 public void finishHandshake(Channel channel, HttpResponse response) {
221 final HttpResponseStatus status = HttpResponseStatus.SWITCHING_PROTOCOLS;
222
223 if (!response.getStatus().equals(status)) {
224 throw new WebSocketHandshakeException("Invalid handshake response status: " + response.getStatus());
225 }
226
227 String upgrade = response.getHeader(Names.UPGRADE);
228
229
230 if (upgrade == null || !upgrade.toLowerCase().equals(Values.WEBSOCKET.toLowerCase())) {
231 throw new WebSocketHandshakeException("Invalid handshake response upgrade: "
232 + response.getHeader(Names.UPGRADE));
233 }
234
235
236
237 String connection = response.getHeader(Names.CONNECTION);
238 if (connection == null || !connection.toLowerCase().equals(Values.UPGRADE.toLowerCase())) {
239 throw new WebSocketHandshakeException("Invalid handshake response connection: "
240 + response.getHeader(Names.CONNECTION));
241 }
242
243 String accept = response.getHeader(Names.SEC_WEBSOCKET_ACCEPT);
244 if (accept == null || !accept.equals(expectedChallengeResponseString)) {
245 throw new WebSocketHandshakeException(String.format("Invalid challenge. Actual: %s. Expected: %s", accept,
246 expectedChallengeResponseString));
247 }
248
249 String subprotocol = response.getHeader(Names.SEC_WEBSOCKET_PROTOCOL);
250 setActualSubprotocol(subprotocol);
251
252
253 setHandshakeComplete();
254
255 channel.getPipeline().get(HttpResponseDecoder.class).replace("ws-decoder",
256 new WebSocket08FrameDecoder(false, allowExtensions, getMaxFramePayloadLength()));
257
258
259 }
260 }