OLD | NEW |
1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | 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 | 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. | 3 // BSD-style license that can be found in the LICENSE file. |
4 | 4 |
5 part of dart.io; | 5 part of dart.io; |
6 | 6 |
7 const String _webSocketGUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; | 7 const String _webSocketGUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; |
8 | 8 |
9 class _WebSocketMessageType { | 9 class _WebSocketMessageType { |
10 static const int NONE = 0; | 10 static const int NONE = 0; |
11 static const int BINARY = 1; | 11 static const int BINARY = 1; |
(...skipping 23 matching lines...) Expand all Loading... |
35 /** | 35 /** |
36 * The web socket protocol processor handles the protocol byte stream | 36 * The web socket protocol processor handles the protocol byte stream |
37 * which is supplied through the [:update:] and [:closed:] | 37 * which is supplied through the [:update:] and [:closed:] |
38 * methods. As the protocol is processed the following callbacks are | 38 * methods. As the protocol is processed the following callbacks are |
39 * called: | 39 * called: |
40 * | 40 * |
41 * [:onMessageStart:] | 41 * [:onMessageStart:] |
42 * [:onMessageData:] | 42 * [:onMessageData:] |
43 * [:onMessageEnd:] | 43 * [:onMessageEnd:] |
44 * [:onClosed:] | 44 * [:onClosed:] |
45 * [:onError:] | |
46 * | 45 * |
47 */ | 46 */ |
48 class _WebSocketProtocolProcessor { | 47 class _WebSocketProtocolProcessor { |
49 static const int START = 0; | 48 static const int START = 0; |
50 static const int LEN_FIRST = 1; | 49 static const int LEN_FIRST = 1; |
51 static const int LEN_REST = 2; | 50 static const int LEN_REST = 2; |
52 static const int MASK = 3; | 51 static const int MASK = 3; |
53 static const int PAYLOAD = 4; | 52 static const int PAYLOAD = 4; |
54 static const int CLOSED = 5; | 53 static const int CLOSED = 5; |
55 static const int FAILURE = 6; | 54 static const int FAILURE = 6; |
56 | 55 |
57 _WebSocketProtocolProcessor() { | 56 _WebSocketProtocolProcessor() { |
58 _prepareForNextFrame(); | 57 _prepareForNextFrame(); |
59 _currentMessageType = _WebSocketMessageType.NONE; | 58 _currentMessageType = _WebSocketMessageType.NONE; |
60 } | 59 } |
61 | 60 |
62 /** | 61 /** |
63 * Process data received from the underlying communication channel. | 62 * Process data received from the underlying communication channel. |
64 */ | 63 */ |
65 void update(List<int> buffer) { | 64 void update(List<int> buffer, int offset, int count) { |
66 int index = 0; | 65 int index = offset; |
67 int lastIndex = buffer.length; | 66 int lastIndex = offset + count; |
68 try { | 67 try { |
69 if (_state == CLOSED) { | 68 if (_state == CLOSED) { |
70 throw new WebSocketException("Data on closed connection"); | 69 throw new WebSocketException("Data on closed connection"); |
71 } | 70 } |
72 if (_state == FAILURE) { | 71 if (_state == FAILURE) { |
73 throw new WebSocketException("Data on failed connection"); | 72 throw new WebSocketException("Data on failed connection"); |
74 } | 73 } |
75 while ((index < lastIndex) && _state != CLOSED && _state != FAILURE) { | 74 while ((index < lastIndex) && _state != CLOSED && _state != FAILURE) { |
76 int byte = buffer[index]; | 75 int byte = buffer[index]; |
77 switch (_state) { | 76 switch (_state) { |
(...skipping 211 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
289 String reason = ""; | 288 String reason = ""; |
290 if (_controlPayload.length > 0) { | 289 if (_controlPayload.length > 0) { |
291 if (_controlPayload.length == 1) { | 290 if (_controlPayload.length == 1) { |
292 throw new WebSocketException("Protocol error"); | 291 throw new WebSocketException("Protocol error"); |
293 } | 292 } |
294 status = _controlPayload[0] << 8 | _controlPayload[1]; | 293 status = _controlPayload[0] << 8 | _controlPayload[1]; |
295 if (status == WebSocketStatus.NO_STATUS_RECEIVED) { | 294 if (status == WebSocketStatus.NO_STATUS_RECEIVED) { |
296 throw new WebSocketException("Protocol error"); | 295 throw new WebSocketException("Protocol error"); |
297 } | 296 } |
298 if (_controlPayload.length > 2) { | 297 if (_controlPayload.length > 2) { |
299 var decoder = _StringDecoders.decoder(Encoding.UTF_8); | 298 reason = _decodeString( |
300 decoder.write( | |
301 _controlPayload.getRange(2, _controlPayload.length - 2)); | 299 _controlPayload.getRange(2, _controlPayload.length - 2)); |
302 reason = decoder.decoded(); | |
303 } | 300 } |
304 } | 301 } |
305 if (onClosed != null) onClosed(status, reason); | 302 if (onClosed != null) onClosed(status, reason); |
306 _state = CLOSED; | 303 _state = CLOSED; |
307 break; | 304 break; |
308 | 305 |
309 case _WebSocketOpcode.PING: | 306 case _WebSocketOpcode.PING: |
310 if (onPing != null) onPing(_controlPayload); | 307 if (onPing != null) onPing(_controlPayload); |
311 break; | 308 break; |
312 | 309 |
(...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
353 | 350 |
354 Function onMessageStart; | 351 Function onMessageStart; |
355 Function onMessageData; | 352 Function onMessageData; |
356 Function onMessageEnd; | 353 Function onMessageEnd; |
357 Function onPing; | 354 Function onPing; |
358 Function onPong; | 355 Function onPong; |
359 Function onClosed; | 356 Function onClosed; |
360 } | 357 } |
361 | 358 |
362 | 359 |
363 class _WebSocketConnectionBase { | 360 class _WebSocketTransformerImpl implements WebSocketTransformer { |
364 void _socketConnected(Socket socket) { | 361 final StreamController<WebSocket> _controller = |
365 _socket = socket; | 362 new StreamController<WebSocket>(); |
366 _socket.onError = (e) => _socket.close(); | 363 |
367 } | 364 Stream<WebSocket> bind(Stream<HttpRequest> stream) { |
368 | 365 stream.listen((request) { |
369 void _startProcessing(List<int> unparsedData) { | 366 var response = request.response; |
370 _WebSocketProtocolProcessor processor = new _WebSocketProtocolProcessor(); | 367 if (!_isWebSocketUpgrade(request)) { |
371 processor.onMessageStart = _onWebSocketMessageStart; | 368 _controller.signalError( |
372 processor.onMessageData = _onWebSocketMessageData; | 369 new AsyncError( |
373 processor.onMessageEnd = _onWebSocketMessageEnd; | 370 new WebSocketException("Invalid WebSocket upgrade request"))); |
374 processor.onPing = _onWebSocketPing; | 371 request.listen((_) {}, onDone: () { |
375 processor.onPong = _onWebSocketPong; | 372 response.statusCode = HttpStatus.BAD_REQUEST; |
376 processor.onClosed = _onWebSocketClosed; | 373 response.contentLength = 0; |
377 if (unparsedData != null) { | 374 response.close(); |
378 processor.update(unparsedData); | 375 }); |
379 } | 376 return; |
380 _socket.onData = () { | 377 } |
381 processor.update(_socket.read()); | 378 // Send the upgrade response. |
382 }; | 379 response.statusCode = HttpStatus.SWITCHING_PROTOCOLS; |
383 _socket.onClosed = () { | 380 response.headers.add(HttpHeaders.CONNECTION, "Upgrade"); |
384 processor.closed(); | 381 response.headers.add(HttpHeaders.UPGRADE, "websocket"); |
385 if (_closeSent) { | 382 String key = request.headers.value("Sec-WebSocket-Key"); |
386 // Got socket close in response to close frame. Don't treat | 383 SHA1 sha1 = new SHA1(); |
387 // that as an error. | 384 sha1.add("$key$_webSocketGUID".charCodes); |
388 if (_closeTimer != null) _closeTimer.cancel(); | 385 String accept = _Base64._encode(sha1.close()); |
389 } else { | 386 response.headers.add("Sec-WebSocket-Accept", accept); |
390 if (_onClosed != null) _onClosed(WebSocketStatus.ABNORMAL_CLOSURE, | 387 response.headers.contentLength = 0; |
391 "Unexpected close"); | 388 response.detachSocket() |
392 } | 389 .then((socket) { |
| 390 _controller.add(new _WebSocketImpl._fromSocket(socket)); |
| 391 }, onError: (error) { |
| 392 _controller.signalError(error); |
| 393 }); |
| 394 }); |
| 395 |
| 396 return _controller.stream; |
| 397 } |
| 398 |
| 399 bool _isWebSocketUpgrade(HttpRequest request) { |
| 400 if (request.method != "GET") { |
| 401 return false; |
| 402 } |
| 403 if (request.headers[HttpHeaders.CONNECTION] == null) { |
| 404 return false; |
| 405 } |
| 406 bool isUpgrade = false; |
| 407 request.headers[HttpHeaders.CONNECTION].forEach((String value) { |
| 408 if (value.toLowerCase() == "upgrade") isUpgrade = true; |
| 409 }); |
| 410 if (!isUpgrade) return false; |
| 411 String upgrade = request.headers.value(HttpHeaders.UPGRADE); |
| 412 if (upgrade == null || upgrade.toLowerCase() != "websocket") { |
| 413 return false; |
| 414 } |
| 415 String version = request.headers.value("Sec-WebSocket-Version"); |
| 416 if (version == null || version != "13") { |
| 417 return false; |
| 418 } |
| 419 String key = request.headers.value("Sec-WebSocket-Key"); |
| 420 if (key == null) { |
| 421 return false; |
| 422 } |
| 423 return true; |
| 424 } |
| 425 } |
| 426 |
| 427 |
| 428 class _WebSocketImpl extends Stream<Event> implements WebSocket { |
| 429 final StreamController<Event> _controller = new StreamController<Event>(); |
| 430 |
| 431 final _WebSocketProtocolProcessor _processor = |
| 432 new _WebSocketProtocolProcessor(); |
| 433 |
| 434 final Socket _socket; |
| 435 int _readyState = WebSocket.CONNECTING; |
| 436 |
| 437 static final HttpClient _httpClient = new HttpClient(); |
| 438 |
| 439 static Future<WebSocket> connect(String url, [protocols]) { |
| 440 Uri uri = Uri.parse(url); |
| 441 if (uri.scheme != "ws" && uri.scheme != "wss") { |
| 442 throw new WebSocketException("Unsupported URL scheme '${uri.scheme}'"); |
| 443 } |
| 444 if (uri.userInfo != "") { |
| 445 throw new WebSocketException("Unsupported user info '${uri.userInfo}'"); |
| 446 } |
| 447 |
| 448 Random random = new Random(); |
| 449 // Generate 16 random bytes. |
| 450 List<int> nonceData = new List<int>.fixedLength(16); |
| 451 for (int i = 0; i < 16; i++) { |
| 452 nonceData[i] = random.nextInt(256); |
| 453 } |
| 454 String nonce = _Base64._encode(nonceData); |
| 455 |
| 456 uri = new Uri.fromComponents(scheme: uri.scheme == "wss" ? "https" : "http", |
| 457 userInfo: uri.userInfo, |
| 458 domain: uri.domain, |
| 459 port: uri.port, |
| 460 path: uri.path, |
| 461 query: uri.query, |
| 462 fragment: uri.fragment); |
| 463 return _httpClient.openUrl("GET", uri) |
| 464 .then((request) { |
| 465 // Setup the initial handshake. |
| 466 request.headers.add(HttpHeaders.CONNECTION, "upgrade"); |
| 467 request.headers.set(HttpHeaders.UPGRADE, "websocket"); |
| 468 request.headers.set("Sec-WebSocket-Key", nonce); |
| 469 request.headers.set("Sec-WebSocket-Version", "13"); |
| 470 return request.close(); |
| 471 }) |
| 472 .then((response) { |
| 473 void error(String message) { |
| 474 // Flush data. |
| 475 response.detachSocket().then((socket) { |
| 476 socket.destroy(); |
| 477 }); |
| 478 throw new WebSocketException(message); |
| 479 } |
| 480 if (response.statusCode != HttpStatus.SWITCHING_PROTOCOLS || |
| 481 response.headers[HttpHeaders.CONNECTION] == null || |
| 482 !response.headers[HttpHeaders.CONNECTION].any( |
| 483 (value) => value.toLowerCase() == "upgrade") || |
| 484 response.headers.value(HttpHeaders.UPGRADE).toLowerCase() != |
| 485 "websocket") { |
| 486 error("Connection to '$uri' was not upgraded to websocket"); |
| 487 } |
| 488 String accept = response.headers.value("Sec-WebSocket-Accept"); |
| 489 if (accept == null) { |
| 490 error("Response did not contain a 'Sec-WebSocket-Accept' header"); |
| 491 } |
| 492 SHA1 sha1 = new SHA1(); |
| 493 sha1.add("$nonce$_webSocketGUID".charCodes); |
| 494 List<int> expectedAccept = sha1.close(); |
| 495 List<int> receivedAccept = _Base64._decode(accept); |
| 496 if (expectedAccept.length != receivedAccept.length) { |
| 497 error("Reasponse header 'Sec-WebSocket-Accept' is the wrong length"); |
| 498 } |
| 499 for (int i = 0; i < expectedAccept.length; i++) { |
| 500 if (expectedAccept[i] != receivedAccept[i]) { |
| 501 error("Bad response 'Sec-WebSocket-Accept' header"); |
| 502 } |
| 503 } |
| 504 return response.detachSocket() |
| 505 .then((socket) => new _WebSocketImpl._fromSocket(socket)); |
| 506 }); |
| 507 } |
| 508 |
| 509 _WebSocketImpl._fromSocket(Socket this._socket) { |
| 510 _readyState = WebSocket.OPEN; |
| 511 |
| 512 int type; |
| 513 var data; |
| 514 _processor.onMessageStart = (int t) { |
| 515 type = t; |
| 516 if (type == _WebSocketMessageType.TEXT) { |
| 517 data = new StringBuffer(); |
| 518 } else { |
| 519 data = []; |
| 520 } |
| 521 }; |
| 522 _processor.onMessageData = (buffer, offset, count) { |
| 523 if (type == _WebSocketMessageType.TEXT) { |
| 524 data.add(_decodeString(buffer.getRange(offset, count))); |
| 525 } else { |
| 526 data.addAll(buffer.getRange(offset, count)); |
| 527 } |
| 528 }; |
| 529 _processor.onMessageEnd = () { |
| 530 if (type == _WebSocketMessageType.TEXT) { |
| 531 _controller.add(new _WebSocketMessageEvent(data.toString())); |
| 532 } else { |
| 533 _controller.add(new _WebSocketMessageEvent(data)); |
| 534 } |
| 535 }; |
| 536 _processor.onClosed = (code, reason) { |
| 537 bool clean = true; |
| 538 if (_readyState == WebSocket.OPEN) { |
| 539 _readyState = WebSocket.CLOSING; |
| 540 if (code != WebSocketStatus.NO_STATUS_RECEIVED) { |
| 541 _close(code); |
| 542 } else { |
| 543 _close(); |
| 544 clean = false; |
| 545 } |
| 546 _readyState = WebSocket.CLOSED; |
| 547 } |
| 548 _controller.add(new _WebSocketCloseEvent(clean, code, reason)); |
| 549 _controller.close(); |
| 550 }; |
| 551 |
| 552 _socket.listen( |
| 553 (data) => _processor.update(data, 0, data.length), |
| 554 onDone: () => _processor.closed(), |
| 555 onError: (error) => _controller.signalError(error)); |
| 556 } |
| 557 |
| 558 StreamSubscription<Event> listen(void onData(Event event), |
| 559 {void onError(AsyncError error), |
| 560 void onDone(), |
| 561 bool unsubscribeOnError}) { |
| 562 return _controller.stream.listen(onData, |
| 563 onError: onError, |
| 564 onDone: onDone, |
| 565 unsubscribeOnError: unsubscribeOnError); |
| 566 } |
| 567 |
| 568 int get readyState => _readyState; |
| 569 int get bufferedAmount => 0; |
| 570 |
| 571 String get extensions => null; |
| 572 String get protocol => null; |
| 573 |
| 574 void close([int code, String reason]) { |
| 575 if (_readyState < WebSocket.CLOSING) _readyState = WebSocket.CLOSING; |
| 576 if (code == WebSocketStatus.RESERVED_1004 || |
| 577 code == WebSocketStatus.NO_STATUS_RECEIVED || |
| 578 code == WebSocketStatus.RESERVED_1015) { |
| 579 throw new WebSocketException("Reserved status code $code"); |
| 580 } |
| 581 _close(code, reason); |
| 582 } |
| 583 |
| 584 void _close([int code, String reason]) { |
| 585 List<int> data; |
| 586 if (code != null) { |
| 587 data = new List<int>(); |
| 588 data.add((code >> 8) & 0xFF); |
| 589 data.add(code & 0xFF); |
| 590 if (reason != null) { |
| 591 data.addAll(_encodeString(reason)); |
| 592 } |
| 593 } |
| 594 _sendFrame(_WebSocketOpcode.CLOSE, data); |
| 595 |
| 596 if (_readyState == WebSocket.CLOSED) { |
| 597 // Close the socket when the close frame has been sent - if it |
| 598 // does not take too long. |
| 599 // TODO(ajohnsen): Honor comment. |
| 600 _socket.destroy(); |
| 601 } else { |
| 602 // Half close the socket and expect a close frame in response |
| 603 // before closing the socket. If a close frame does not arrive |
| 604 // within a reasonable amount of time just close the socket. |
| 605 // TODO(ajohnsen): Honor comment. |
393 _socket.close(); | 606 _socket.close(); |
394 }; | 607 } |
395 } | 608 } |
396 | 609 |
397 void set onMessage(void callback(Object message)) { | 610 void send(message) { |
398 _onMessage = callback; | 611 if (readyState != WebSocket.OPEN) { |
399 } | 612 throw new StateError("Connection not open"); |
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 } | 613 } |
409 List<int> data; | 614 List<int> data; |
410 int opcode; | 615 int opcode; |
411 if (message != null) { | 616 if (message != null) { |
412 if (message is String) { | 617 if (message is String) { |
413 opcode = _WebSocketOpcode.TEXT; | 618 opcode = _WebSocketOpcode.TEXT; |
414 data = _StringEncoders.encoder(Encoding.UTF_8).encodeString(message); | 619 data = _encodeString(message); |
415 } else { | 620 } else { |
416 if (message is !List<int>) { | 621 if (message is !List<int>) { |
417 throw new ArgumentError(message); | 622 throw new ArgumentError(message); |
418 } | 623 } |
419 opcode = _WebSocketOpcode.BINARY; | 624 opcode = _WebSocketOpcode.BINARY; |
420 data = message; | 625 data = message; |
421 } | 626 } |
422 } else { | 627 } else { |
423 opcode = _WebSocketOpcode.TEXT; | 628 opcode = _WebSocketOpcode.TEXT; |
424 } | 629 } |
425 _sendFrame(opcode, data); | 630 _sendFrame(opcode, data); |
426 } | 631 } |
427 | 632 |
428 close([int status, String reason]) { | 633 void _sendFrame(int opcode, [List<int> data]) { |
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(const Duration(seconds: 5), _socket.close); | |
457 } else { | |
458 // Half close the socket and expect a close frame in response | |
459 // before closing the socket. If a close frame does not arrive | |
460 // within a reasonable amount of time just close the socket. | |
461 _socket.outputStream.close(); | |
462 _closeTimer = new Timer(const Duration(seconds: 5), _socket.close); | |
463 } | |
464 _closeSent = true; | |
465 } | |
466 | |
467 int get hashCode => _hash; | |
468 | |
469 _onWebSocketMessageStart(int type) { | |
470 _currentMessageType = type; | |
471 if (_currentMessageType == _WebSocketMessageType.TEXT) { | |
472 _decoder = _StringDecoders.decoder(Encoding.UTF_8); | |
473 } else { | |
474 _outputStream = new ListOutputStream(); | |
475 } | |
476 } | |
477 | |
478 _onWebSocketMessageData(List<int> buffer, int offset, int count) { | |
479 if (_currentMessageType == _WebSocketMessageType.TEXT) { | |
480 _decoder.write(buffer.getRange(offset, count)); | |
481 } else { | |
482 _outputStream.write(buffer.getRange(offset, count)); | |
483 } | |
484 } | |
485 | |
486 _onWebSocketMessageEnd() { | |
487 if (_onMessage != null) { | |
488 if (_currentMessageType == _WebSocketMessageType.TEXT) { | |
489 _onMessage(_decoder.decoded()); | |
490 } else { | |
491 _onMessage(_outputStream.read()); | |
492 } | |
493 } | |
494 _decoder = null; | |
495 _outputStream = null; | |
496 } | |
497 | |
498 _onWebSocketPing(List<int> payload) { | |
499 _sendFrame(_WebSocketOpcode.PONG, payload); | |
500 } | |
501 | |
502 _onWebSocketPong(List<int> payload) { | |
503 // Currently pong messages are ignored. | |
504 } | |
505 | |
506 _onWebSocketClosed(int status, String reason) { | |
507 _closeReceived = true; | |
508 if (_onClosed != null) _onClosed(status, reason); | |
509 if (_closeSent) { | |
510 // Got close frame in response to close frame. Now close the socket. | |
511 if (_closeTimer != null) _closeTimer.cancel(); | |
512 _socket.close(); | |
513 } else { | |
514 if (status != WebSocketStatus.NO_STATUS_RECEIVED) { | |
515 close(status); | |
516 } else { | |
517 close(); | |
518 } | |
519 } | |
520 } | |
521 | |
522 _sendFrame(int opcode, [List<int> data]) { | |
523 bool mask = false; // Masking not implemented for server. | 634 bool mask = false; // Masking not implemented for server. |
524 int dataLength = data == null ? 0 : data.length; | 635 int dataLength = data == null ? 0 : data.length; |
525 // Determine the header size. | 636 // Determine the header size. |
526 int headerSize = (mask) ? 6 : 2; | 637 int headerSize = (mask) ? 6 : 2; |
527 if (dataLength > 65535) { | 638 if (dataLength > 65535) { |
528 headerSize += 8; | 639 headerSize += 8; |
529 } else if (dataLength > 125) { | 640 } else if (dataLength > 125) { |
530 headerSize += 2; | 641 headerSize += 2; |
531 } | 642 } |
532 List<int> header = new List<int>.fixedLength(headerSize); | 643 List<int> header = new List<int>.fixedLength(headerSize); |
533 int index = 0; | 644 int index = 0; |
534 // Set FIN and opcode. | 645 // Set FIN and opcode. |
535 header[index++] = 0x80 | opcode; | 646 header[index++] = 0x80 | opcode; |
536 // Determine size and position of length field. | 647 // Determine size and position of length field. |
537 int lengthBytes = 1; | 648 int lengthBytes = 1; |
538 int firstLengthByte = 1; | 649 int firstLengthByte = 1; |
539 if (dataLength > 65535) { | 650 if (dataLength > 65535) { |
540 header[index++] = 127; | 651 header[index++] = 127; |
541 lengthBytes = 8; | 652 lengthBytes = 8; |
542 } else if (dataLength > 125) { | 653 } else if (dataLength > 125) { |
543 header[index++] = 126; | 654 header[index++] = 126; |
544 lengthBytes = 2; | 655 lengthBytes = 2; |
545 } | 656 } |
546 // Write the length in network byte order into the header. | 657 // Write the length in network byte order into the header. |
547 for (int i = 0; i < lengthBytes; i++) { | 658 for (int i = 0; i < lengthBytes; i++) { |
548 header[index++] = dataLength >> (((lengthBytes - 1) - i) * 8) & 0xFF; | 659 header[index++] = dataLength >> (((lengthBytes - 1) - i) * 8) & 0xFF; |
549 } | 660 } |
550 assert(index == headerSize); | 661 assert(index == headerSize); |
551 _socket.outputStream.write(header); | 662 _socket.add(header); |
552 if (data != null) { | 663 if (data != null) { |
553 _socket.outputStream.write(data); | 664 _socket.add(data); |
554 } | 665 } |
555 } | 666 } |
556 | |
557 Socket _socket; | |
558 Timer _closeTimer; | |
559 int _hash; | |
560 | |
561 Function _onMessage; | |
562 Function _onClosed; | |
563 | |
564 int _currentMessageType = _WebSocketMessageType.NONE; | |
565 _StringDecoder _decoder; | |
566 ListOutputStream _outputStream; | |
567 bool _closeReceived = false; | |
568 bool _closeSent = false; | |
569 } | |
570 | |
571 | |
572 class _WebSocketConnection | |
573 extends _WebSocketConnectionBase implements WebSocketConnection { | |
574 _WebSocketConnection(DetachedSocket detached) { | |
575 _hash = detached.socket.hashCode; | |
576 _socketConnected(detached.socket); | |
577 _startProcessing(detached.unparsedData); | |
578 } | |
579 } | 667 } |
580 | 668 |
581 | 669 |
582 class _WebSocketHandler implements WebSocketHandler { | |
583 void onRequest(HttpRequest request, HttpResponse response) { | |
584 // Check that this is a web socket upgrade. | |
585 if (!_isWebSocketUpgrade(request)) { | |
586 response.statusCode = HttpStatus.BAD_REQUEST; | |
587 response.outputStream.close(); | |
588 return; | |
589 } | |
590 | |
591 // Send the upgrade response. | |
592 response.statusCode = HttpStatus.SWITCHING_PROTOCOLS; | |
593 response.headers.add(HttpHeaders.CONNECTION, "Upgrade"); | |
594 response.headers.add(HttpHeaders.UPGRADE, "websocket"); | |
595 String key = request.headers.value("Sec-WebSocket-Key"); | |
596 SHA1 sha1 = new SHA1(); | |
597 sha1.add("$key$_webSocketGUID".charCodes); | |
598 String accept = _Base64._encode(sha1.close()); | |
599 response.headers.add("Sec-WebSocket-Accept", accept); | |
600 response.contentLength = 0; | |
601 | |
602 // Upgrade the connection and get the underlying socket. | |
603 WebSocketConnection conn = | |
604 new _WebSocketConnection(response.detachSocket()); | |
605 if (_onOpen != null) _onOpen(conn); | |
606 } | |
607 | |
608 void set onOpen(callback(WebSocketConnection connection)) { | |
609 _onOpen = callback; | |
610 } | |
611 | |
612 bool _isWebSocketUpgrade(HttpRequest request) { | |
613 if (request.method != "GET") { | |
614 return false; | |
615 } | |
616 if (request.headers[HttpHeaders.CONNECTION] == null) { | |
617 return false; | |
618 } | |
619 bool isUpgrade = false; | |
620 request.headers[HttpHeaders.CONNECTION].forEach((String value) { | |
621 if (value.toLowerCase() == "upgrade") isUpgrade = true; | |
622 }); | |
623 if (!isUpgrade) return false; | |
624 String upgrade = request.headers.value(HttpHeaders.UPGRADE); | |
625 if (upgrade == null || upgrade.toLowerCase() != "websocket") { | |
626 return false; | |
627 } | |
628 String version = request.headers.value("Sec-WebSocket-Version"); | |
629 if (version == null || version != "13") { | |
630 return false; | |
631 } | |
632 String key = request.headers.value("Sec-WebSocket-Key"); | |
633 if (key == null) { | |
634 return false; | |
635 } | |
636 return true; | |
637 } | |
638 | |
639 Function _onOpen; | |
640 } | |
641 | |
642 | |
643 class _WebSocketClientConnection | |
644 extends _WebSocketConnectionBase implements WebSocketClientConnection { | |
645 _WebSocketClientConnection(HttpClientConnection this._conn, | |
646 [List<String> protocols]) { | |
647 _conn.onRequest = _onHttpClientRequest; | |
648 _conn.onResponse = _onHttpClientResponse; | |
649 _conn.onError = (e) { | |
650 if (_onClosed != null) { | |
651 _onClosed(WebSocketStatus.ABNORMAL_CLOSURE, "$e"); | |
652 } | |
653 }; | |
654 | |
655 // Generate the nonce now as it is also used to set the hash code. | |
656 _generateNonceAndHash(); | |
657 } | |
658 | |
659 void set onRequest(void callback(HttpClientRequest request)) { | |
660 _onRequest = callback; | |
661 } | |
662 | |
663 void set onOpen(void callback()) { | |
664 _onOpen = callback; | |
665 } | |
666 | |
667 void set onNoUpgrade(void callback(HttpClientResponse request)) { | |
668 _onNoUpgrade = callback; | |
669 } | |
670 | |
671 void _onHttpClientRequest(HttpClientRequest request) { | |
672 if (_onRequest != null) { | |
673 _onRequest(request); | |
674 } | |
675 // Setup the initial handshake. | |
676 request.headers.add(HttpHeaders.CONNECTION, "upgrade"); | |
677 request.headers.set(HttpHeaders.UPGRADE, "websocket"); | |
678 request.headers.set("Sec-WebSocket-Key", _nonce); | |
679 request.headers.set("Sec-WebSocket-Version", "13"); | |
680 request.contentLength = 0; | |
681 request.outputStream.close(); | |
682 } | |
683 | |
684 void _onHttpClientResponse(HttpClientResponse response) { | |
685 if (response.statusCode != HttpStatus.SWITCHING_PROTOCOLS) { | |
686 if (_onNoUpgrade != null) { | |
687 _onNoUpgrade(response); | |
688 } else { | |
689 _conn.detachSocket().socket.close(); | |
690 throw new WebSocketException("Protocol upgrade refused"); | |
691 } | |
692 return; | |
693 } | |
694 | |
695 if (!_isWebSocketUpgrade(response)) { | |
696 _conn.detachSocket().socket.close(); | |
697 throw new WebSocketException("Protocol upgrade failed"); | |
698 } | |
699 | |
700 // Connection upgrade successful. | |
701 DetachedSocket detached = _conn.detachSocket(); | |
702 _socketConnected(detached.socket); | |
703 if (_onOpen != null) _onOpen(); | |
704 _startProcessing(detached.unparsedData); | |
705 } | |
706 | |
707 void _generateNonceAndHash() { | |
708 Random random = new Random(); | |
709 assert(_nonce == null); | |
710 void intToBigEndianBytes(int value, List<int> bytes, int offset) { | |
711 bytes[offset] = (value >> 24) & 0xFF; | |
712 bytes[offset + 1] = (value >> 16) & 0xFF; | |
713 bytes[offset + 2] = (value >> 8) & 0xFF; | |
714 bytes[offset + 3] = value & 0xFF; | |
715 } | |
716 | |
717 // Generate 16 random bytes. Use the last four bytes for the hash code. | |
718 List<int> nonce = new List<int>.fixedLength(16); | |
719 for (int i = 0; i < 4; i++) { | |
720 int r = random.nextInt(0x100000000); | |
721 intToBigEndianBytes(r, nonce, i * 4); | |
722 } | |
723 _nonce = _Base64._encode(nonce); | |
724 _hash = random.nextInt(0x100000000); | |
725 } | |
726 | |
727 bool _isWebSocketUpgrade(HttpClientResponse response) { | |
728 if (response.headers[HttpHeaders.CONNECTION] == null) { | |
729 return false; | |
730 } | |
731 bool isUpgrade = false; | |
732 response.headers[HttpHeaders.CONNECTION].forEach((String value) { | |
733 if (value.toLowerCase() == "upgrade") isUpgrade = true; | |
734 }); | |
735 if (!isUpgrade) return false; | |
736 String upgrade = response.headers.value(HttpHeaders.UPGRADE); | |
737 if (upgrade == null || upgrade.toLowerCase() != "websocket") { | |
738 return false; | |
739 } | |
740 String accept = response.headers.value("Sec-WebSocket-Accept"); | |
741 if (accept == null) { | |
742 return false; | |
743 } | |
744 SHA1 sha1 = new SHA1(); | |
745 sha1.add("$_nonce$_webSocketGUID".charCodes); | |
746 List<int> expectedAccept = sha1.close(); | |
747 List<int> receivedAccept = _Base64._decode(accept); | |
748 if (expectedAccept.length != receivedAccept.length) return false; | |
749 for (int i = 0; i < expectedAccept.length; i++) { | |
750 if (expectedAccept[i] != receivedAccept[i]) return false; | |
751 } | |
752 return true; | |
753 } | |
754 | |
755 Function _onRequest; | |
756 Function _onOpen; | |
757 Function _onNoUpgrade; | |
758 HttpClientConnection _conn; | |
759 String _nonce; | |
760 } | |
761 | |
762 | |
763 class _WebSocket implements WebSocket { | |
764 _WebSocket(String url, [protocols]) { | |
765 Uri uri = Uri.parse(url); | |
766 if (uri.scheme != "ws" && uri.scheme != "wss") { | |
767 throw new WebSocketException("Unsupported URL scheme ${uri.scheme}"); | |
768 } | |
769 if (uri.userInfo != "") { | |
770 throw new WebSocketException("Unsupported user info ${uri.userInfo}"); | |
771 } | |
772 int port = uri.port == 0 ? HttpClient.DEFAULT_HTTP_PORT : uri.port; | |
773 String path = uri.path; | |
774 if (path.length == 0) path = "/"; | |
775 if (uri.query != "") { | |
776 if (uri.fragment != "") { | |
777 path = "${path}?${uri.query}#${uri.fragment}"; | |
778 } else { | |
779 path = "${path}?${uri.query}"; | |
780 } | |
781 } | |
782 | |
783 HttpClient client = new HttpClient(); | |
784 bool secure = (uri.scheme == 'wss'); | |
785 HttpClientConnection conn = client.openUrl("GET", | |
786 new Uri.fromComponents(scheme: secure ? "https" : "http", | |
787 domain: uri.domain, | |
788 port: port, | |
789 path: 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 { | 670 class _WebSocketMessageEvent implements MessageEvent { |
858 _WebSocketMessageEvent(this._data); | 671 _WebSocketMessageEvent(this._data); |
859 get data => _data; | 672 get data => _data; |
860 var _data; | 673 var _data; |
861 } | 674 } |
862 | 675 |
863 | 676 |
864 class _WebSocketCloseEvent implements CloseEvent { | 677 class _WebSocketCloseEvent implements CloseEvent { |
865 _WebSocketCloseEvent(this._wasClean, this._code, this._reason); | 678 _WebSocketCloseEvent(this._wasClean, this._code, this._reason); |
866 bool get wasClean => _wasClean; | 679 bool get wasClean => _wasClean; |
867 int get code => _code; | 680 int get code => _code; |
868 String get reason => _reason; | 681 String get reason => _reason; |
869 bool _wasClean; | 682 bool _wasClean; |
870 int _code; | 683 int _code; |
871 String _reason; | 684 String _reason; |
872 } | 685 } |
OLD | NEW |