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