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 WebSocketClientHandshaker13 extends WebSocketClientHandshaker {
45
46 private static final InternalLogger logger = InternalLoggerFactory.getInstance(WebSocketClientHandshaker13.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 WebSocketClientHandshaker13(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 WebSocketClientHandshaker13(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 @Override
118 public ChannelFuture handshake(Channel channel) throws Exception {
119
120 URI wsURL = getWebSocketUrl();
121 String path = wsURL.getPath();
122 if (wsURL.getQuery() != null && wsURL.getQuery().length() > 0) {
123 path = wsURL.getPath() + "?" + wsURL.getQuery();
124 }
125
126
127 byte[] nonce = WebSocketUtil.randomBytes(16);
128 String key = WebSocketUtil.base64(nonce);
129
130 String acceptSeed = key + MAGIC_GUID;
131 byte[] sha1 = WebSocketUtil.sha1(acceptSeed.getBytes(CharsetUtil.US_ASCII.name()));
132 expectedChallengeResponseString = WebSocketUtil.base64(sha1);
133
134 if (logger.isDebugEnabled()) {
135 logger.debug(String.format("WS Version 13 Client Handshake key: %s. Expected response: %s.", key,
136 expectedChallengeResponseString));
137 }
138
139
140 HttpRequest request = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, path);
141 request.addHeader(Names.UPGRADE, Values.WEBSOCKET.toLowerCase());
142 request.addHeader(Names.CONNECTION, Values.UPGRADE);
143 request.addHeader(Names.SEC_WEBSOCKET_KEY, key);
144 request.addHeader(Names.HOST, wsURL.getHost());
145
146 int wsPort = wsURL.getPort();
147 String originValue = "http://" + wsURL.getHost();
148 if (wsPort != 80 && wsPort != 443) {
149
150
151 originValue = originValue + ":" + wsPort;
152 }
153 request.addHeader(Names.ORIGIN, originValue);
154
155 String expectedSubprotocol = getExpectedSubprotocol();
156 if (expectedSubprotocol != null && !expectedSubprotocol.equals("")) {
157 request.addHeader(Names.SEC_WEBSOCKET_PROTOCOL, expectedSubprotocol);
158 }
159
160 request.addHeader(Names.SEC_WEBSOCKET_VERSION, "13");
161
162 if (customHeaders != null) {
163 for (String header : customHeaders.keySet()) {
164 request.addHeader(header, customHeaders.get(header));
165 }
166 }
167
168 ChannelFuture future = channel.write(request);
169
170 channel.getPipeline().replace(HttpRequestEncoder.class, "ws-encoder", new WebSocket13FrameEncoder(true));
171
172 return future;
173 }
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194 @Override
195 public void finishHandshake(Channel channel, HttpResponse response) throws WebSocketHandshakeException {
196 final HttpResponseStatus status = HttpResponseStatus.SWITCHING_PROTOCOLS;
197
198 if (!response.getStatus().equals(status)) {
199 throw new WebSocketHandshakeException("Invalid handshake response status: " + response.getStatus());
200 }
201
202 String upgrade = response.getHeader(Names.UPGRADE);
203
204
205 if (upgrade == null || !upgrade.toLowerCase().equals(Values.WEBSOCKET.toLowerCase())) {
206 throw new WebSocketHandshakeException("Invalid handshake response upgrade: "
207 + response.getHeader(Names.UPGRADE));
208 }
209
210
211
212 String connection = response.getHeader(Names.CONNECTION);
213 if (connection == null || !connection.toLowerCase().equals(Values.UPGRADE.toLowerCase())) {
214 throw new WebSocketHandshakeException("Invalid handshake response connection: "
215 + response.getHeader(Names.CONNECTION));
216 }
217
218 String accept = response.getHeader(Names.SEC_WEBSOCKET_ACCEPT);
219 if (accept == null || !accept.equals(expectedChallengeResponseString)) {
220 throw new WebSocketHandshakeException(String.format("Invalid challenge. Actual: %s. Expected: %s", accept,
221 expectedChallengeResponseString));
222 }
223
224 String subprotocol = response.getHeader(Names.SEC_WEBSOCKET_PROTOCOL);
225 setActualSubprotocol(subprotocol);
226
227 setHandshakeComplete();
228
229 channel.getPipeline().get(HttpResponseDecoder.class).replace("ws-decoder",
230 new WebSocket13FrameDecoder(false, allowExtensions, getMaxFramePayloadLength()));
231
232
233 }
234 }