| OLD | NEW |
| 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2013, 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; |
| (...skipping 414 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 425 } | 425 } |
| 426 String key = request.headers.value("Sec-WebSocket-Key"); | 426 String key = request.headers.value("Sec-WebSocket-Key"); |
| 427 if (key == null) { | 427 if (key == null) { |
| 428 return false; | 428 return false; |
| 429 } | 429 } |
| 430 return true; | 430 return true; |
| 431 } | 431 } |
| 432 } | 432 } |
| 433 | 433 |
| 434 | 434 |
| 435 class _WebSocketOutgoingTransformer extends StreamEventTransformer { |
| 436 final _WebSocketImpl webSocket; |
| 437 |
| 438 _WebSocketOutgoingTransformer(_WebSocketImpl this.webSocket); |
| 439 |
| 440 void handleData(message, EventSink<List<int>> sink) { |
| 441 List<int> data; |
| 442 int opcode; |
| 443 if (message != null) { |
| 444 if (message is String) { |
| 445 opcode = _WebSocketOpcode.TEXT; |
| 446 data = _encodeString(message); |
| 447 } else { |
| 448 if (message is !List<int>) { |
| 449 throw new ArgumentError(message); |
| 450 } |
| 451 opcode = _WebSocketOpcode.BINARY; |
| 452 data = message; |
| 453 } |
| 454 } else { |
| 455 opcode = _WebSocketOpcode.TEXT; |
| 456 } |
| 457 addFrame(opcode, data, sink); |
| 458 } |
| 459 |
| 460 void handleDone(EventSink<List<int>> sink) { |
| 461 int code = webSocket._outCloseCode; |
| 462 String reason = webSocket._outCloseReason; |
| 463 List<int> data; |
| 464 if (code != null) { |
| 465 data = new List<int>(); |
| 466 data.add((code >> 8) & 0xFF); |
| 467 data.add(code & 0xFF); |
| 468 if (reason != null) { |
| 469 data.addAll(_encodeString(reason)); |
| 470 } |
| 471 } |
| 472 addFrame(_WebSocketOpcode.CLOSE, data, sink); |
| 473 sink.close(); |
| 474 } |
| 475 |
| 476 void addFrame(int opcode, List<int> data, EventSink<List<int>> sink) { |
| 477 bool mask = !webSocket._serverSide; // Masking not implemented for server. |
| 478 int dataLength = data == null ? 0 : data.length; |
| 479 // Determine the header size. |
| 480 int headerSize = (mask) ? 6 : 2; |
| 481 if (dataLength > 65535) { |
| 482 headerSize += 8; |
| 483 } else if (dataLength > 125) { |
| 484 headerSize += 2; |
| 485 } |
| 486 List<int> header = new List<int>(headerSize); |
| 487 int index = 0; |
| 488 // Set FIN and opcode. |
| 489 header[index++] = 0x80 | opcode; |
| 490 // Determine size and position of length field. |
| 491 int lengthBytes = 1; |
| 492 int firstLengthByte = 1; |
| 493 if (dataLength > 65535) { |
| 494 header[index++] = 127; |
| 495 lengthBytes = 8; |
| 496 } else if (dataLength > 125) { |
| 497 header[index++] = 126; |
| 498 lengthBytes = 2; |
| 499 } |
| 500 // Write the length in network byte order into the header. |
| 501 for (int i = 0; i < lengthBytes; i++) { |
| 502 header[index++] = dataLength >> (((lengthBytes - 1) - i) * 8) & 0xFF; |
| 503 } |
| 504 if (mask) { |
| 505 header[1] |= 1 << 7; |
| 506 var maskBytes = _IOCrypto.getRandomBytes(4); |
| 507 header.setRange(index, index + 4, maskBytes); |
| 508 index += 4; |
| 509 if (data != null) { |
| 510 var list = new Uint8List(data.length); |
| 511 for (int i = 0; i < data.length; i++) { |
| 512 list[i] = data[i] ^ maskBytes[i % 4]; |
| 513 } |
| 514 data = list; |
| 515 } |
| 516 } |
| 517 assert(index == headerSize); |
| 518 sink.add(header); |
| 519 if (data != null) { |
| 520 sink.add(data); |
| 521 } |
| 522 } |
| 523 } |
| 524 |
| 525 |
| 526 class _WebSocketConsumer implements StreamConsumer { |
| 527 final _WebSocketImpl webSocket; |
| 528 final Socket socket; |
| 529 StreamController _controller; |
| 530 StreamSubscription _subscription; |
| 531 Completer _closeCompleter = new Completer(); |
| 532 Completer _completer; |
| 533 |
| 534 _WebSocketConsumer(_WebSocketImpl this.webSocket, Socket this.socket); |
| 535 |
| 536 void _onListen() { |
| 537 if (_subscription != null) { |
| 538 _subscription.cancel(); |
| 539 } |
| 540 } |
| 541 |
| 542 _ensureController() { |
| 543 if (_controller != null) return; |
| 544 _controller = new StreamController(onPause: () => _subscription.pause(), |
| 545 onResume: () => _subscription.resume(), |
| 546 onCancel: _onListen); |
| 547 var stream = _controller.stream.transform( |
| 548 new _WebSocketOutgoingTransformer(webSocket)); |
| 549 socket.addStream(stream) |
| 550 .then((_) { |
| 551 _done(); |
| 552 _closeCompleter.complete(webSocket); |
| 553 }, |
| 554 onError: (error) { |
| 555 if (!_done(error)) { |
| 556 _closeCompleter.completeError(error); |
| 557 } |
| 558 }); |
| 559 } |
| 560 |
| 561 bool _done([error]) { |
| 562 if (_completer == null) return false; |
| 563 var tmp = _completer; |
| 564 _completer = null; |
| 565 if (error != null) { |
| 566 tmp.completeError(error); |
| 567 } else { |
| 568 tmp.complete(webSocket); |
| 569 } |
| 570 return true; |
| 571 } |
| 572 |
| 573 Future addStream(var stream) { |
| 574 _ensureController(); |
| 575 _completer = new Completer(); |
| 576 _subscription = stream.listen( |
| 577 (data) { |
| 578 _controller.add(data); |
| 579 }, |
| 580 onDone: () { |
| 581 _done(); |
| 582 }, |
| 583 onError: (error) { |
| 584 _done(error); |
| 585 }, |
| 586 cancelOnError: true); |
| 587 return _completer.future; |
| 588 } |
| 589 |
| 590 Future close() { |
| 591 Future closeSocket() { |
| 592 return socket.close().then((_) => webSocket); |
| 593 } |
| 594 if (_controller == null) return closeSocket(); |
| 595 _controller.close(); |
| 596 return _closeCompleter.future.then((_) => closeSocket()); |
| 597 } |
| 598 } |
| 599 |
| 600 |
| 435 class _WebSocketImpl extends Stream implements WebSocket { | 601 class _WebSocketImpl extends Stream implements WebSocket { |
| 436 final StreamController _controller = new StreamController(); | 602 final StreamController _controller = new StreamController(); |
| 603 StreamSink _sink; |
| 437 | 604 |
| 438 final Socket _socket; | 605 final Socket _socket; |
| 439 final bool _serverSide; | 606 final bool _serverSide; |
| 440 int _readyState = WebSocket.CONNECTING; | 607 int _readyState = WebSocket.CONNECTING; |
| 441 bool _writeClosed = false; | 608 bool _writeClosed = false; |
| 442 int _closeCode; | 609 int _closeCode; |
| 443 String _closeReason; | 610 String _closeReason; |
| 444 | 611 |
| 612 int _outCloseCode; |
| 613 String _outCloseReason; |
| 614 |
| 445 static final HttpClient _httpClient = new HttpClient(); | 615 static final HttpClient _httpClient = new HttpClient(); |
| 446 | 616 |
| 447 static Future<WebSocket> connect(String url, [protocols]) { | 617 static Future<WebSocket> connect(String url, [protocols]) { |
| 448 Uri uri = Uri.parse(url); | 618 Uri uri = Uri.parse(url); |
| 449 if (uri.scheme != "ws" && uri.scheme != "wss") { | 619 if (uri.scheme != "ws" && uri.scheme != "wss") { |
| 450 throw new WebSocketException("Unsupported URL scheme '${uri.scheme}'"); | 620 throw new WebSocketException("Unsupported URL scheme '${uri.scheme}'"); |
| 451 } | 621 } |
| 452 if (uri.userInfo != "") { | 622 if (uri.userInfo != "") { |
| 453 throw new WebSocketException("Unsupported user info '${uri.userInfo}'"); | 623 throw new WebSocketException("Unsupported user info '${uri.userInfo}'"); |
| 454 } | 624 } |
| (...skipping 54 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 509 error("Bad response 'Sec-WebSocket-Accept' header"); | 679 error("Bad response 'Sec-WebSocket-Accept' header"); |
| 510 } | 680 } |
| 511 } | 681 } |
| 512 return response.detachSocket() | 682 return response.detachSocket() |
| 513 .then((socket) => new _WebSocketImpl._fromSocket(socket)); | 683 .then((socket) => new _WebSocketImpl._fromSocket(socket)); |
| 514 }); | 684 }); |
| 515 } | 685 } |
| 516 | 686 |
| 517 _WebSocketImpl._fromSocket(Socket this._socket, | 687 _WebSocketImpl._fromSocket(Socket this._socket, |
| 518 [bool this._serverSide = false]) { | 688 [bool this._serverSide = false]) { |
| 689 _sink = new _StreamSinkImpl(new _WebSocketConsumer(this, _socket)); |
| 519 _readyState = WebSocket.OPEN; | 690 _readyState = WebSocket.OPEN; |
| 520 | 691 |
| 521 bool closed = false; | |
| 522 var transformer = new _WebSocketProtocolTransformer(_serverSide); | 692 var transformer = new _WebSocketProtocolTransformer(_serverSide); |
| 523 _socket.transform(transformer).listen( | 693 _socket.transform(transformer).listen( |
| 524 (data) { | 694 (data) { |
| 525 _controller.add(data); | 695 _controller.add(data); |
| 526 }, | 696 }, |
| 527 onError: (error) { | 697 onError: (error) { |
| 528 if (closed) return; | |
| 529 closed = true; | |
| 530 _controller.addError(error); | 698 _controller.addError(error); |
| 531 _controller.close(); | 699 _controller.close(); |
| 532 }, | 700 }, |
| 533 onDone: () { | 701 onDone: () { |
| 534 if (closed) return; | |
| 535 closed = true; | |
| 536 if (_readyState == WebSocket.OPEN) { | 702 if (_readyState == WebSocket.OPEN) { |
| 537 _readyState = WebSocket.CLOSING; | 703 _readyState = WebSocket.CLOSING; |
| 538 if (transformer.closeCode != WebSocketStatus.NO_STATUS_RECEIVED) { | 704 if (transformer.closeCode != WebSocketStatus.NO_STATUS_RECEIVED) { |
| 539 _close(transformer.closeCode); | 705 close(transformer.closeCode); |
| 540 } else { | 706 } else { |
| 541 _close(); | 707 close(); |
| 542 } | 708 } |
| 543 _readyState = WebSocket.CLOSED; | 709 _readyState = WebSocket.CLOSED; |
| 544 } | 710 } |
| 545 _closeCode = transformer.closeCode; | 711 _closeCode = transformer.closeCode; |
| 546 _closeReason = transformer.closeReason; | 712 _closeReason = transformer.closeReason; |
| 547 _controller.close(); | 713 _controller.close(); |
| 548 if (_writeClosed) _socket.destroy(); | |
| 549 }, | 714 }, |
| 550 cancelOnError: true); | 715 cancelOnError: true); |
| 551 | |
| 552 _socket.done | |
| 553 .catchError((error) { | |
| 554 if (closed) return; | |
| 555 closed = true; | |
| 556 _readyState = WebSocket.CLOSED; | |
| 557 _closeCode = WebSocketStatus.ABNORMAL_CLOSURE; | |
| 558 _controller.addError(error); | |
| 559 _controller.close(); | |
| 560 }) | |
| 561 .whenComplete(() { | |
| 562 _writeClosed = true; | |
| 563 }); | |
| 564 } | 716 } |
| 565 | 717 |
| 566 StreamSubscription listen(void onData(message), | 718 StreamSubscription listen(void onData(message), |
| 567 {void onError(error), | 719 {void onError(error), |
| 568 void onDone(), | 720 void onDone(), |
| 569 bool cancelOnError}) { | 721 bool cancelOnError}) { |
| 570 return _controller.stream.listen(onData, | 722 return _controller.stream.listen(onData, |
| 571 onError: onError, | 723 onError: onError, |
| 572 onDone: onDone, | 724 onDone: onDone, |
| 573 cancelOnError: cancelOnError); | 725 cancelOnError: cancelOnError); |
| 574 } | 726 } |
| 575 | 727 |
| 576 int get readyState => _readyState; | 728 int get readyState => _readyState; |
| 577 | 729 |
| 578 String get extensions => null; | 730 String get extensions => null; |
| 579 String get protocol => null; | 731 String get protocol => null; |
| 580 int get closeCode => _closeCode; | 732 int get closeCode => _closeCode; |
| 581 String get closeReason => _closeReason; | 733 String get closeReason => _closeReason; |
| 582 | 734 |
| 583 void close([int code, String reason]) { | 735 void add(data) => _sink.add(data); |
| 584 if (_readyState < WebSocket.CLOSING) _readyState = WebSocket.CLOSING; | 736 void addError(error) => _sink.addError(error); |
| 585 if (code == WebSocketStatus.RESERVED_1004 || | 737 Future addStream(Stream stream) => _sink.addStream(stream); |
| 586 code == WebSocketStatus.NO_STATUS_RECEIVED || | 738 Future get done => _sink.done; |
| 587 code == WebSocketStatus.RESERVED_1015) { | |
| 588 throw new WebSocketException("Reserved status code $code"); | |
| 589 } | |
| 590 _close(code, reason); | |
| 591 } | |
| 592 | 739 |
| 593 void _close([int code, String reason]) { | 740 Future close([int code, String reason]) { |
| 594 List<int> data; | 741 if (!_writeClosed) { |
| 595 if (code != null) { | 742 if (code == WebSocketStatus.RESERVED_1004 || |
| 596 data = new List<int>(); | 743 code == WebSocketStatus.NO_STATUS_RECEIVED || |
| 597 data.add((code >> 8) & 0xFF); | 744 code == WebSocketStatus.RESERVED_1015) { |
| 598 data.add(code & 0xFF); | 745 throw new WebSocketException("Reserved status code $code"); |
| 599 if (reason != null) { | |
| 600 data.addAll(_encodeString(reason)); | |
| 601 } | 746 } |
| 602 } | 747 _outCloseCode = code; |
| 603 _sendFrame(_WebSocketOpcode.CLOSE, data); | 748 _outCloseReason = reason; |
| 604 | |
| 605 if (_readyState == WebSocket.CLOSED) { | |
| 606 // Close the socket when the close frame has been sent - if it | |
| 607 // does not take too long. | |
| 608 // TODO(ajohnsen): Honor comment. | |
| 609 _socket.destroy(); | |
| 610 } else { | |
| 611 // Half close the socket and expect a close frame in response | |
| 612 // before closing the socket. If a close frame does not arrive | |
| 613 // within a reasonable amount of time just close the socket. | |
| 614 // TODO(ajohnsen): Honor comment. | |
| 615 _socket.close(); | |
| 616 } | |
| 617 } | |
| 618 | |
| 619 void send(message) { | |
| 620 if (readyState != WebSocket.OPEN) { | |
| 621 throw new StateError("Connection not open"); | |
| 622 } | |
| 623 List<int> data; | |
| 624 int opcode; | |
| 625 if (message != null) { | |
| 626 if (message is String) { | |
| 627 opcode = _WebSocketOpcode.TEXT; | |
| 628 data = _encodeString(message); | |
| 629 } else { | |
| 630 if (message is !List<int>) { | |
| 631 throw new ArgumentError(message); | |
| 632 } | |
| 633 opcode = _WebSocketOpcode.BINARY; | |
| 634 data = message; | |
| 635 } | |
| 636 } else { | |
| 637 opcode = _WebSocketOpcode.TEXT; | |
| 638 } | |
| 639 _sendFrame(opcode, data); | |
| 640 } | |
| 641 | |
| 642 void _sendFrame(int opcode, [List<int> data]) { | |
| 643 if (_writeClosed) return; | |
| 644 bool mask = !_serverSide; // Masking not implemented for server. | |
| 645 int dataLength = data == null ? 0 : data.length; | |
| 646 // Determine the header size. | |
| 647 int headerSize = (mask) ? 6 : 2; | |
| 648 if (dataLength > 65535) { | |
| 649 headerSize += 8; | |
| 650 } else if (dataLength > 125) { | |
| 651 headerSize += 2; | |
| 652 } | |
| 653 List<int> header = new List<int>(headerSize); | |
| 654 int index = 0; | |
| 655 // Set FIN and opcode. | |
| 656 header[index++] = 0x80 | opcode; | |
| 657 // Determine size and position of length field. | |
| 658 int lengthBytes = 1; | |
| 659 int firstLengthByte = 1; | |
| 660 if (dataLength > 65535) { | |
| 661 header[index++] = 127; | |
| 662 lengthBytes = 8; | |
| 663 } else if (dataLength > 125) { | |
| 664 header[index++] = 126; | |
| 665 lengthBytes = 2; | |
| 666 } | |
| 667 // Write the length in network byte order into the header. | |
| 668 for (int i = 0; i < lengthBytes; i++) { | |
| 669 header[index++] = dataLength >> (((lengthBytes - 1) - i) * 8) & 0xFF; | |
| 670 } | |
| 671 if (mask) { | |
| 672 header[1] |= 1 << 7; | |
| 673 var maskBytes = _IOCrypto.getRandomBytes(4); | |
| 674 header.setRange(index, index + 4, maskBytes); | |
| 675 index += 4; | |
| 676 if (data != null) { | |
| 677 var list = new Uint8List(data.length); | |
| 678 for (int i = 0; i < data.length; i++) { | |
| 679 list[i] = data[i] ^ maskBytes[i % 4]; | |
| 680 } | |
| 681 data = list; | |
| 682 } | |
| 683 } | |
| 684 assert(index == headerSize); | |
| 685 try { | |
| 686 _socket.add(header); | |
| 687 if (data != null) { | |
| 688 _socket.add(data); | |
| 689 } | |
| 690 } catch (_) { | |
| 691 // The socket can be closed before _socket.done have a chance | |
| 692 // to complete. | |
| 693 _writeClosed = true; | 749 _writeClosed = true; |
| 694 } | 750 } |
| 751 return _sink.close(); |
| 695 } | 752 } |
| 696 } | 753 } |
| OLD | NEW |