OLD | NEW |
| (Empty) |
1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | |
2 // for details. All rights reserved. Use of this source code is governed by a | |
3 // BSD-style license that can be found in the LICENSE file. | |
4 | |
5 const String _webSocketGUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; | |
6 | |
7 class _WebSocketMessageType { | |
8 static const int NONE = 0; | |
9 static const int BINARY = 1; | |
10 static const int TEXT = 2; | |
11 } | |
12 | |
13 | |
14 class _WebSocketOpcode { | |
15 static const int CONTINUATION = 0; | |
16 static const int TEXT = 1; | |
17 static const int BINARY = 2; | |
18 static const int RESERVED_3 = 3; | |
19 static const int RESERVED_4 = 4; | |
20 static const int RESERVED_5 = 5; | |
21 static const int RESERVED_6 = 6; | |
22 static const int RESERVED_7 = 7; | |
23 static const int CLOSE = 8; | |
24 static const int PING = 9; | |
25 static const int PONG = 10; | |
26 static const int RESERVED_B = 11; | |
27 static const int RESERVED_C = 12; | |
28 static const int RESERVED_D = 13; | |
29 static const int RESERVED_E = 14; | |
30 static const int RESERVED_F = 15; | |
31 } | |
32 | |
33 /** | |
34 * The web socket protocol processor handles the protocol byte stream | |
35 * which is supplied through the [:update:] and [:closed:] | |
36 * methods. As the protocol is processed the following callbacks are | |
37 * called: | |
38 * | |
39 * [:onMessageStart:] | |
40 * [:onMessageData:] | |
41 * [:onMessageEnd:] | |
42 * [:onClosed:] | |
43 * [:onError:] | |
44 * | |
45 */ | |
46 class _WebSocketProtocolProcessor { | |
47 static const int START = 0; | |
48 static const int LEN_FIRST = 1; | |
49 static const int LEN_REST = 2; | |
50 static const int MASK = 3; | |
51 static const int PAYLOAD = 4; | |
52 static const int CLOSED = 5; | |
53 static const int FAILURE = 6; | |
54 | |
55 _WebSocketProtocolProcessor() { | |
56 _prepareForNextFrame(); | |
57 _currentMessageType = _WebSocketMessageType.NONE; | |
58 } | |
59 | |
60 /** | |
61 * Process data received from the underlying communication channel. | |
62 */ | |
63 void update(List<int> buffer, int offset, int count) { | |
64 int index = offset; | |
65 int lastIndex = offset + count; | |
66 try { | |
67 if (_state == CLOSED) { | |
68 throw new WebSocketException("Data on closed connection"); | |
69 } | |
70 if (_state == FAILURE) { | |
71 throw new WebSocketException("Data on failed connection"); | |
72 } | |
73 while ((index < lastIndex) && _state != CLOSED && _state != FAILURE) { | |
74 int byte = buffer[index]; | |
75 switch (_state) { | |
76 case START: | |
77 _fin = (byte & 0x80) != 0; | |
78 _opcode = (byte & 0xF); | |
79 switch (_opcode) { | |
80 case _WebSocketOpcode.CONTINUATION: | |
81 if (_currentMessageType == _WebSocketMessageType.NONE) { | |
82 throw new WebSocketException("Protocol error"); | |
83 } | |
84 break; | |
85 | |
86 case _WebSocketOpcode.TEXT: | |
87 if (_currentMessageType != _WebSocketMessageType.NONE) { | |
88 throw new WebSocketException("Protocol error"); | |
89 } | |
90 _currentMessageType = _WebSocketMessageType.TEXT; | |
91 if (onMessageStart !== null) { | |
92 onMessageStart(_WebSocketMessageType.TEXT); | |
93 } | |
94 break; | |
95 | |
96 case _WebSocketOpcode.BINARY: | |
97 if (_currentMessageType != _WebSocketMessageType.NONE) { | |
98 throw new WebSocketException("Protocol error"); | |
99 } | |
100 _currentMessageType = _WebSocketMessageType.BINARY; | |
101 if (onMessageStart !== null) { | |
102 onMessageStart(_WebSocketMessageType.BINARY); | |
103 } | |
104 break; | |
105 | |
106 case _WebSocketOpcode.CLOSE: | |
107 case _WebSocketOpcode.PING: | |
108 case _WebSocketOpcode.PONG: | |
109 // Control frames cannot be fragmented. | |
110 if (!_fin) throw new WebSocketException("Protocol error"); | |
111 break; | |
112 | |
113 default: | |
114 throw new WebSocketException("Protocol error"); | |
115 } | |
116 _state = LEN_FIRST; | |
117 break; | |
118 | |
119 case LEN_FIRST: | |
120 _masked = (byte & 0x80) != 0; | |
121 _len = byte & 0x7F; | |
122 if (_isControlFrame() && _len > 126) { | |
123 throw new WebSocketException("Protocol error"); | |
124 } | |
125 if (_len < 126) { | |
126 _lengthDone(); | |
127 } else if (_len == 126) { | |
128 _len = 0; | |
129 _remainingLenBytes = 2; | |
130 _state = LEN_REST; | |
131 } else if (_len == 127) { | |
132 _len = 0; | |
133 _remainingLenBytes = 8; | |
134 _state = LEN_REST; | |
135 } | |
136 break; | |
137 | |
138 case LEN_REST: | |
139 _len = _len << 8 | byte; | |
140 _remainingLenBytes--; | |
141 if (_remainingLenBytes == 0) { | |
142 _lengthDone(); | |
143 } | |
144 break; | |
145 | |
146 case MASK: | |
147 _maskingKey = _maskingKey << 8 | byte; | |
148 _remainingMaskingKeyBytes--; | |
149 if (_remainingMaskingKeyBytes == 0) { | |
150 _maskDone(); | |
151 } | |
152 break; | |
153 | |
154 case PAYLOAD: | |
155 // The payload is not handled one byte at a time but in blocks. | |
156 int payload; | |
157 if (lastIndex - index <= _remainingPayloadBytes) { | |
158 payload = lastIndex - index; | |
159 } else { | |
160 payload = _remainingPayloadBytes; | |
161 } | |
162 _remainingPayloadBytes -= payload; | |
163 | |
164 // Unmask payload if masked. | |
165 if (_masked) { | |
166 for (int i = 0; i < payload; i++) { | |
167 int maskingByte = | |
168 ((_maskingKey >> ((3 - _unmaskingIndex) * 8)) & 0xFF); | |
169 buffer[index + i] = buffer[index + i] ^ maskingByte; | |
170 _unmaskingIndex = (_unmaskingIndex + 1) % 4; | |
171 } | |
172 } | |
173 | |
174 if (_isControlFrame()) { | |
175 if (payload > 0) { | |
176 // Allocate a buffer for collecting the control frame | |
177 // payload if any. | |
178 if (_controlPayload == null) { | |
179 _controlPayload = new List<int>(); | |
180 } | |
181 _controlPayload.addAll(buffer.getRange(index, payload)); | |
182 index += payload; | |
183 } | |
184 | |
185 if (_remainingPayloadBytes == 0) { | |
186 _controlFrameEnd(); | |
187 } | |
188 } else { | |
189 switch (_currentMessageType) { | |
190 case _WebSocketMessageType.NONE: | |
191 throw new WebSocketException("Protocol error"); | |
192 | |
193 case _WebSocketMessageType.TEXT: | |
194 case _WebSocketMessageType.BINARY: | |
195 if (onMessageData !== null) { | |
196 onMessageData(buffer, index, payload); | |
197 } | |
198 index += payload; | |
199 if (_remainingPayloadBytes == 0) { | |
200 _messageFrameEnd(); | |
201 } | |
202 break; | |
203 | |
204 default: | |
205 throw new WebSocketException("Protocol error"); | |
206 } | |
207 } | |
208 | |
209 // Hack - as we always do index++ below. | |
210 index--; | |
211 } | |
212 | |
213 // Move to the next byte. | |
214 index++; | |
215 } | |
216 } catch (e) { | |
217 if (onClosed !== null) onClosed(WebSocketStatus.PROTOCOL_ERROR, | |
218 "Protocol error"); | |
219 _state = FAILURE; | |
220 } | |
221 } | |
222 | |
223 /** | |
224 * Indicate that the underlying communication channel has been closed. | |
225 */ | |
226 void closed() { | |
227 if (_state == START || _state == CLOSED || _state == FAILURE) return; | |
228 if (onClosed !== null) onClosed(WebSocketStatus.ABNORMAL_CLOSURE, | |
229 "Connection closed unexpectedly"); | |
230 _state = CLOSED; | |
231 } | |
232 | |
233 void _lengthDone() { | |
234 if (_masked) { | |
235 _state = MASK; | |
236 _remainingMaskingKeyBytes = 4; | |
237 } else { | |
238 _remainingPayloadBytes = _len; | |
239 _startPayload(); | |
240 } | |
241 } | |
242 | |
243 void _maskDone() { | |
244 _remainingPayloadBytes = _len; | |
245 _startPayload(); | |
246 } | |
247 | |
248 void _startPayload() { | |
249 // If there is no actual payload perform perform callbacks without | |
250 // going through the PAYLOAD state. | |
251 if (_remainingPayloadBytes == 0) { | |
252 if (_isControlFrame()) { | |
253 switch (_opcode) { | |
254 case _WebSocketOpcode.CLOSE: | |
255 if (onClosed !== null) onClosed(1005, ""); | |
256 _state = CLOSED; | |
257 break; | |
258 case _WebSocketOpcode.PING: | |
259 if (onPing !== null) onPing(null); | |
260 break; | |
261 case _WebSocketOpcode.PONG: | |
262 if (onPong !== null) onPong(null); | |
263 break; | |
264 } | |
265 _prepareForNextFrame(); | |
266 } else { | |
267 _messageFrameEnd(); | |
268 } | |
269 } else { | |
270 _state = PAYLOAD; | |
271 } | |
272 } | |
273 | |
274 void _messageFrameEnd() { | |
275 if (_fin) { | |
276 if (onMessageEnd !== null) onMessageEnd(); | |
277 _currentMessageType = _WebSocketMessageType.NONE; | |
278 } | |
279 _prepareForNextFrame(); | |
280 } | |
281 | |
282 void _controlFrameEnd() { | |
283 switch (_opcode) { | |
284 case _WebSocketOpcode.CLOSE: | |
285 int status = WebSocketStatus.NO_STATUS_RECEIVED; | |
286 String reason = ""; | |
287 if (_controlPayload.length > 0) { | |
288 if (_controlPayload.length == 1) { | |
289 throw new WebSocketException("Protocol error"); | |
290 } | |
291 status = _controlPayload[0] << 8 | _controlPayload[1]; | |
292 if (status == WebSocketStatus.NO_STATUS_RECEIVED) { | |
293 throw new WebSocketException("Protocol error"); | |
294 } | |
295 if (_controlPayload.length > 2) { | |
296 var decoder = _StringDecoders.decoder(Encoding.UTF_8); | |
297 decoder.write( | |
298 _controlPayload.getRange(2, _controlPayload.length - 2)); | |
299 reason = decoder.decoded(); | |
300 } | |
301 } | |
302 if (onClosed !== null) onClosed(status, reason); | |
303 _state = CLOSED; | |
304 break; | |
305 | |
306 case _WebSocketOpcode.PING: | |
307 if (onPing !== null) onPing(_controlPayload); | |
308 break; | |
309 | |
310 case _WebSocketOpcode.PONG: | |
311 if (onPong !== null) onPong(_controlPayload); | |
312 break; | |
313 } | |
314 _prepareForNextFrame(); | |
315 } | |
316 | |
317 bool _isControlFrame() { | |
318 return _opcode == _WebSocketOpcode.CLOSE || | |
319 _opcode == _WebSocketOpcode.PING || | |
320 _opcode == _WebSocketOpcode.PONG; | |
321 } | |
322 | |
323 void _prepareForNextFrame() { | |
324 if (_state != CLOSED && _state != FAILURE) _state = START; | |
325 _fin = null; | |
326 _opcode = null; | |
327 _len = null; | |
328 _masked = null; | |
329 _maskingKey = 0; | |
330 _remainingLenBytes = null; | |
331 _remainingMaskingKeyBytes = null; | |
332 _remainingPayloadBytes = null; | |
333 _unmaskingIndex = 0; | |
334 _controlPayload = null; | |
335 } | |
336 | |
337 int _state; | |
338 bool _fin; | |
339 int _opcode; | |
340 int _len; | |
341 bool _masked; | |
342 int _maskingKey; | |
343 int _remainingLenBytes; | |
344 int _remainingMaskingKeyBytes; | |
345 int _remainingPayloadBytes; | |
346 int _unmaskingIndex; | |
347 | |
348 int _currentMessageType; | |
349 List<int> _controlPayload; | |
350 | |
351 Function onMessageStart; | |
352 Function onMessageData; | |
353 Function onMessageEnd; | |
354 Function onPing; | |
355 Function onPong; | |
356 Function onClosed; | |
357 } | |
358 | |
359 | |
360 class _WebSocketConnectionBase { | |
361 void _socketConnected(Socket socket) { | |
362 _socket = socket; | |
363 _socket.onError = (e) => _socket.close(); | |
364 } | |
365 | |
366 void _startProcessing(List<int> unparsedData) { | |
367 _WebSocketProtocolProcessor processor = new _WebSocketProtocolProcessor(); | |
368 processor.onMessageStart = _onWebSocketMessageStart; | |
369 processor.onMessageData = _onWebSocketMessageData; | |
370 processor.onMessageEnd = _onWebSocketMessageEnd; | |
371 processor.onPing = _onWebSocketPing; | |
372 processor.onPong = _onWebSocketPong; | |
373 processor.onClosed = _onWebSocketClosed; | |
374 if (unparsedData !== null) { | |
375 processor.update(unparsedData, 0, unparsedData.length); | |
376 } | |
377 _socket.onData = () { | |
378 int available = _socket.available(); | |
379 List<int> data = new List<int>(available); | |
380 int read = _socket.readList(data, 0, available); | |
381 processor.update(data, 0, read); | |
382 }; | |
383 _socket.onClosed = () { | |
384 processor.closed(); | |
385 if (_closeSent) { | |
386 // Got socket close in response to close frame. Don't treat | |
387 // that as an error. | |
388 if (_closeTimer !== null) _closeTimer.cancel(); | |
389 } else { | |
390 if (_onClosed !== null) _onClosed(WebSocketStatus.ABNORMAL_CLOSURE, | |
391 "Unexpected close"); | |
392 } | |
393 _socket.close(); | |
394 }; | |
395 } | |
396 | |
397 void set onMessage(void callback(Object message)) { | |
398 _onMessage = callback; | |
399 } | |
400 | |
401 void set onClosed(void callback(int status, String reason)) { | |
402 _onClosed = callback; | |
403 } | |
404 | |
405 send(message) { | |
406 if (_closeSent) { | |
407 throw new WebSocketException("Connection closed"); | |
408 } | |
409 List<int> data; | |
410 int opcode; | |
411 if (message !== null) { | |
412 if (message is String) { | |
413 opcode = _WebSocketOpcode.TEXT; | |
414 data = _StringEncoders.encoder(Encoding.UTF_8).encodeString(message); | |
415 } else { | |
416 if (message is !List<int>) { | |
417 throw new ArgumentError(message); | |
418 } | |
419 opcode = _WebSocketOpcode.BINARY; | |
420 data = message; | |
421 } | |
422 } else { | |
423 opcode = _WebSocketOpcode.TEXT; | |
424 } | |
425 _sendFrame(opcode, data); | |
426 } | |
427 | |
428 close([int status, String reason]) { | |
429 if (status == WebSocketStatus.RESERVED_1004 || | |
430 status == WebSocketStatus.NO_STATUS_RECEIVED || | |
431 status == WebSocketStatus.RESERVED_1015) { | |
432 throw new WebSocketException("Reserved status code $status"); | |
433 } | |
434 | |
435 if (_closeSent) return; | |
436 List<int> data; | |
437 if (status !== null) { | |
438 data = new List<int>(); | |
439 data.add((status >> 8) & 0xFF); | |
440 data.add(status & 0xFF); | |
441 if (reason !== null) { | |
442 data.addAll( | |
443 _StringEncoders.encoder(Encoding.UTF_8).encodeString(reason)); | |
444 } | |
445 } | |
446 _sendFrame(_WebSocketOpcode.CLOSE, data); | |
447 | |
448 if (_closeReceived) { | |
449 // Close the socket when the close frame has been sent - if it | |
450 // does not take too long. | |
451 _socket.outputStream.close(); | |
452 _socket.outputStream.onClosed = () { | |
453 if (_closeTimer !== null) _closeTimer.cancel(); | |
454 _socket.close(); | |
455 }; | |
456 _closeTimer = new Timer(5000, (t) { | |
457 _socket.close(); | |
458 }); | |
459 } else { | |
460 // Half close the socket and expect a close frame in response | |
461 // before closing the socket. If a close frame does not arrive | |
462 // within a reasonable amount of time just close the socket. | |
463 _socket.outputStream.close(); | |
464 _closeTimer = new Timer(5000, (t) { | |
465 _socket.close(); | |
466 }); | |
467 } | |
468 _closeSent = true; | |
469 } | |
470 | |
471 int get hashCode => _hash; | |
472 | |
473 _onWebSocketMessageStart(int type) { | |
474 _currentMessageType = type; | |
475 if (_currentMessageType == _WebSocketMessageType.TEXT) { | |
476 _decoder = _StringDecoders.decoder(Encoding.UTF_8); | |
477 } else { | |
478 _outputStream = new ListOutputStream(); | |
479 } | |
480 } | |
481 | |
482 _onWebSocketMessageData(List<int> buffer, int offset, int count) { | |
483 if (_currentMessageType == _WebSocketMessageType.TEXT) { | |
484 _decoder.write(buffer.getRange(offset, count)); | |
485 } else { | |
486 _outputStream.write(buffer.getRange(offset, count)); | |
487 } | |
488 } | |
489 | |
490 _onWebSocketMessageEnd() { | |
491 if (_onMessage !== null) { | |
492 if (_currentMessageType == _WebSocketMessageType.TEXT) { | |
493 _onMessage(_decoder.decoded()); | |
494 } else { | |
495 _onMessage(_outputStream.read()); | |
496 } | |
497 } | |
498 _decoder = null; | |
499 _outputStream = null; | |
500 } | |
501 | |
502 _onWebSocketPing(List<int> payload) { | |
503 _sendFrame(_WebSocketOpcode.PONG, payload); | |
504 } | |
505 | |
506 _onWebSocketPong(List<int> payload) { | |
507 // Currently pong messages are ignored. | |
508 } | |
509 | |
510 _onWebSocketClosed(int status, String reason) { | |
511 _closeReceived = true; | |
512 if (_onClosed !== null) _onClosed(status, reason); | |
513 if (_closeSent) { | |
514 // Got close frame in response to close frame. Now close the socket. | |
515 if (_closeTimer !== null) _closeTimer.cancel(); | |
516 _socket.close(); | |
517 } else { | |
518 if (status != WebSocketStatus.NO_STATUS_RECEIVED) { | |
519 close(status); | |
520 } else { | |
521 close(); | |
522 } | |
523 } | |
524 } | |
525 | |
526 _sendFrame(int opcode, [List<int> data]) { | |
527 bool mask = false; // Masking not implemented for server. | |
528 int dataLength = data == null ? 0 : data.length; | |
529 // Determine the header size. | |
530 int headerSize = (mask) ? 6 : 2; | |
531 if (dataLength > 65535) { | |
532 headerSize += 8; | |
533 } else if (dataLength > 125) { | |
534 headerSize += 2; | |
535 } | |
536 List<int> header = new List<int>(headerSize); | |
537 int index = 0; | |
538 // Set FIN and opcode. | |
539 header[index++] = 0x80 | opcode; | |
540 // Determine size and position of length field. | |
541 int lengthBytes = 1; | |
542 int firstLengthByte = 1; | |
543 if (dataLength > 65535) { | |
544 header[index++] = 127; | |
545 lengthBytes = 8; | |
546 } else if (dataLength > 125) { | |
547 header[index++] = 126; | |
548 lengthBytes = 2; | |
549 } | |
550 // Write the length in network byte order into the header. | |
551 for (int i = 0; i < lengthBytes; i++) { | |
552 header[index++] = dataLength >> (((lengthBytes - 1) - i) * 8) & 0xFF; | |
553 } | |
554 assert(index == headerSize); | |
555 _socket.outputStream.write(header); | |
556 if (data !== null) { | |
557 _socket.outputStream.write(data); | |
558 } | |
559 } | |
560 | |
561 Socket _socket; | |
562 Timer _closeTimer; | |
563 int _hash; | |
564 | |
565 Function _onMessage; | |
566 Function _onClosed; | |
567 | |
568 int _currentMessageType = _WebSocketMessageType.NONE; | |
569 _StringDecoder _decoder; | |
570 ListOutputStream _outputStream; | |
571 bool _closeReceived = false; | |
572 bool _closeSent = false; | |
573 } | |
574 | |
575 | |
576 class _WebSocketConnection | |
577 extends _WebSocketConnectionBase implements WebSocketConnection { | |
578 _WebSocketConnection(DetachedSocket detached) { | |
579 _hash = detached.socket.hashCode; | |
580 _socketConnected(detached.socket); | |
581 _startProcessing(detached.unparsedData); | |
582 } | |
583 } | |
584 | |
585 | |
586 class _WebSocketHandler implements WebSocketHandler { | |
587 void onRequest(HttpRequest request, HttpResponse response) { | |
588 // Check that this is a web socket upgrade. | |
589 if (!_isWebSocketUpgrade(request)) { | |
590 response.statusCode = HttpStatus.BAD_REQUEST; | |
591 response.outputStream.close(); | |
592 return; | |
593 } | |
594 | |
595 // Send the upgrade response. | |
596 response.statusCode = HttpStatus.SWITCHING_PROTOCOLS; | |
597 response.headers.add(HttpHeaders.CONNECTION, "Upgrade"); | |
598 response.headers.add(HttpHeaders.UPGRADE, "websocket"); | |
599 String key = request.headers.value("Sec-WebSocket-Key"); | |
600 SHA1 sha1 = new SHA1(); | |
601 sha1.update("$key$_webSocketGUID".charCodes); | |
602 String accept = _Base64._encode(sha1.digest()); | |
603 response.headers.add("Sec-WebSocket-Accept", accept); | |
604 response.contentLength = 0; | |
605 | |
606 // Upgrade the connection and get the underlying socket. | |
607 WebSocketConnection conn = | |
608 new _WebSocketConnection(response.detachSocket()); | |
609 if (_onOpen !== null) _onOpen(conn); | |
610 } | |
611 | |
612 void set onOpen(callback(WebSocketConnection connection)) { | |
613 _onOpen = callback; | |
614 } | |
615 | |
616 bool _isWebSocketUpgrade(HttpRequest request) { | |
617 if (request.method != "GET") { | |
618 return false; | |
619 } | |
620 if (request.headers[HttpHeaders.CONNECTION] == null) { | |
621 return false; | |
622 } | |
623 bool isUpgrade = false; | |
624 request.headers[HttpHeaders.CONNECTION].forEach((String value) { | |
625 if (value.toLowerCase() == "upgrade") isUpgrade = true; | |
626 }); | |
627 if (!isUpgrade) return false; | |
628 String upgrade = request.headers.value(HttpHeaders.UPGRADE); | |
629 if (upgrade == null || upgrade.toLowerCase() != "websocket") { | |
630 return false; | |
631 } | |
632 String version = request.headers.value("Sec-WebSocket-Version"); | |
633 if (version == null || version != "13") { | |
634 return false; | |
635 } | |
636 String key = request.headers.value("Sec-WebSocket-Key"); | |
637 if (key == null) { | |
638 return false; | |
639 } | |
640 return true; | |
641 } | |
642 | |
643 Function _onOpen; | |
644 } | |
645 | |
646 | |
647 class _WebSocketClientConnection | |
648 extends _WebSocketConnectionBase implements WebSocketClientConnection { | |
649 _WebSocketClientConnection(HttpClientConnection this._conn, | |
650 [List<String> protocols]) { | |
651 _conn.onRequest = _onHttpClientRequest; | |
652 _conn.onResponse = _onHttpClientResponse; | |
653 _conn.onError = (e) { | |
654 if (_onClosed !== null) { | |
655 _onClosed(WebSocketStatus.ABNORMAL_CLOSURE, "$e"); | |
656 } | |
657 }; | |
658 | |
659 // Generate the nonce now as it is also used to set the hash code. | |
660 _generateNonceAndHash(); | |
661 } | |
662 | |
663 void set onRequest(void callback(HttpClientRequest request)) { | |
664 _onRequest = callback; | |
665 } | |
666 | |
667 void set onOpen(void callback()) { | |
668 _onOpen = callback; | |
669 } | |
670 | |
671 void set onNoUpgrade(void callback(HttpClientResponse request)) { | |
672 _onNoUpgrade = callback; | |
673 } | |
674 | |
675 void _onHttpClientRequest(HttpClientRequest request) { | |
676 if (_onRequest !== null) { | |
677 _onRequest(request); | |
678 } | |
679 // Setup the initial handshake. | |
680 request.headers.add(HttpHeaders.CONNECTION, "upgrade"); | |
681 request.headers.set(HttpHeaders.UPGRADE, "websocket"); | |
682 request.headers.set("Sec-WebSocket-Key", _nonce); | |
683 request.headers.set("Sec-WebSocket-Version", "13"); | |
684 request.contentLength = 0; | |
685 request.outputStream.close(); | |
686 } | |
687 | |
688 void _onHttpClientResponse(HttpClientResponse response) { | |
689 if (response.statusCode != HttpStatus.SWITCHING_PROTOCOLS) { | |
690 if (_onNoUpgrade !== null) { | |
691 _onNoUpgrade(response); | |
692 } else { | |
693 _conn.detachSocket().socket.close(); | |
694 throw new WebSocketException("Protocol upgrade refused"); | |
695 } | |
696 return; | |
697 } | |
698 | |
699 if (!_isWebSocketUpgrade(response)) { | |
700 _conn.detachSocket().socket.close(); | |
701 throw new WebSocketException("Protocol upgrade failed"); | |
702 return; | |
703 } | |
704 | |
705 // Connection upgrade successful. | |
706 DetachedSocket detached = _conn.detachSocket(); | |
707 _socketConnected(detached.socket); | |
708 if (_onOpen !== null) _onOpen(); | |
709 _startProcessing(detached.unparsedData); | |
710 } | |
711 | |
712 void _generateNonceAndHash() { | |
713 Random random = new Random(); | |
714 assert(_nonce == null); | |
715 void intToBigEndianBytes(int value, List<int> bytes, int offset) { | |
716 bytes[offset] = (value >> 24) & 0xFF; | |
717 bytes[offset + 1] = (value >> 16) & 0xFF; | |
718 bytes[offset + 2] = (value >> 8) & 0xFF; | |
719 bytes[offset + 3] = value & 0xFF; | |
720 } | |
721 | |
722 // Generate 16 random bytes. Use the last four bytes for the hash code. | |
723 List<int> nonce = new List<int>(16); | |
724 for (int i = 0; i < 4; i++) { | |
725 int r = random.nextInt(0x100000000); | |
726 intToBigEndianBytes(r, nonce, i * 4); | |
727 } | |
728 _nonce = _Base64._encode(nonce); | |
729 _hash = random.nextInt(0x100000000); | |
730 } | |
731 | |
732 bool _isWebSocketUpgrade(HttpClientResponse response) { | |
733 if (response.headers[HttpHeaders.CONNECTION] == null) { | |
734 return false; | |
735 } | |
736 bool isUpgrade = false; | |
737 response.headers[HttpHeaders.CONNECTION].forEach((String value) { | |
738 if (value.toLowerCase() == "upgrade") isUpgrade = true; | |
739 }); | |
740 if (!isUpgrade) return false; | |
741 String upgrade = response.headers.value(HttpHeaders.UPGRADE); | |
742 if (upgrade == null || upgrade.toLowerCase() != "websocket") { | |
743 return false; | |
744 } | |
745 String accept = response.headers.value("Sec-WebSocket-Accept"); | |
746 if (accept == null) { | |
747 return false; | |
748 } | |
749 SHA1 sha1 = new SHA1(); | |
750 sha1.update("$_nonce$_webSocketGUID".charCodes); | |
751 List<int> expectedAccept = sha1.digest(); | |
752 List<int> receivedAccept = _Base64._decode(accept); | |
753 if (expectedAccept.length != receivedAccept.length) return false; | |
754 for (int i = 0; i < expectedAccept.length; i++) { | |
755 if (expectedAccept[i] != receivedAccept[i]) return false; | |
756 } | |
757 return true; | |
758 } | |
759 | |
760 Function _onRequest; | |
761 Function _onOpen; | |
762 Function _onNoUpgrade; | |
763 HttpClientConnection _conn; | |
764 String _nonce; | |
765 } | |
766 | |
767 | |
768 class _WebSocket implements WebSocket { | |
769 _WebSocket(String url, [protocols]) { | |
770 Uri uri = new Uri.fromString(url); | |
771 if (uri.scheme != "ws") { | |
772 throw new WebSocketException("Unsupported URL scheme ${uri.scheme}"); | |
773 } | |
774 if (uri.userInfo != "") { | |
775 throw new WebSocketException("Unsupported user info ${uri.userInfo}"); | |
776 } | |
777 int port = uri.port == 0 ? HttpClient.DEFAULT_HTTP_PORT : uri.port; | |
778 String path = uri.path; | |
779 if (path.length == 0) path = "/"; | |
780 if (uri.query != "") { | |
781 if (uri.fragment != "") { | |
782 path = "${path}?${uri.query}#${uri.fragment}"; | |
783 } else { | |
784 path = "${path}?${uri.query}"; | |
785 } | |
786 } | |
787 | |
788 HttpClient client = new HttpClient(); | |
789 HttpClientConnection conn = client.open("GET", uri.domain, port, path); | |
790 if (protocols is String) protocols = [protocols]; | |
791 _wsconn = new WebSocketClientConnection(conn, protocols); | |
792 _wsconn.onOpen = () { | |
793 // HTTP client not needed after socket have been detached. | |
794 client.shutdown(); | |
795 client = null; | |
796 _readyState = WebSocket.OPEN; | |
797 if (_onopen !== null) _onopen(); | |
798 }; | |
799 _wsconn.onMessage = (message) { | |
800 if (_onmessage !== null) { | |
801 _onmessage(new _WebSocketMessageEvent(message)); | |
802 } | |
803 }; | |
804 _wsconn.onClosed = (status, reason) { | |
805 _readyState = WebSocket.CLOSED; | |
806 if (_onclose !== null) { | |
807 _onclose(new _WebSocketCloseEvent(true, status, reason)); | |
808 } | |
809 }; | |
810 _wsconn.onNoUpgrade = (response) { | |
811 if (_onclose !== null) { | |
812 _onclose( | |
813 new _WebSocketCloseEvent(true, | |
814 WebSocketStatus.ABNORMAL_CLOSURE, | |
815 "Connection not upgraded")); | |
816 } | |
817 }; | |
818 } | |
819 | |
820 int get readyState => _readyState; | |
821 int get bufferedAmount => 0; | |
822 | |
823 void set onopen(Function callback) { | |
824 _onopen = callback; | |
825 } | |
826 | |
827 void set onerror(Function callback) {} | |
828 | |
829 void set onclose(Function callback) { | |
830 _onclose = callback; | |
831 } | |
832 | |
833 String get extensions => null; | |
834 String get protocol => null; | |
835 | |
836 void close(int code, String reason) { | |
837 if (_readyState < WebSocket.CLOSING) _readyState = WebSocket.CLOSING; | |
838 _wsconn.close(code, reason); | |
839 } | |
840 | |
841 void set onmessage(Function callback) { | |
842 _onmessage = callback; | |
843 } | |
844 | |
845 void send(data) { | |
846 _wsconn.send(data); | |
847 } | |
848 | |
849 WebSocketClientConnection _wsconn; | |
850 int _readyState = WebSocket.CONNECTING; | |
851 Function _onopen; | |
852 Function _onclose; | |
853 Function _onmessage; | |
854 } | |
855 | |
856 | |
857 class _WebSocketMessageEvent implements MessageEvent { | |
858 _WebSocketMessageEvent(this._data); | |
859 get data => _data; | |
860 var _data; | |
861 } | |
862 | |
863 | |
864 class _WebSocketCloseEvent implements CloseEvent { | |
865 _WebSocketCloseEvent(this._wasClean, this._code, this._reason); | |
866 bool get wasClean => _wasClean; | |
867 int get code => _code; | |
868 String get reason => _reason; | |
869 bool _wasClean; | |
870 int _code; | |
871 String _reason; | |
872 } | |
OLD | NEW |