| 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 375 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 386 | 386 |
| 387 class _WebSocketPong { | 387 class _WebSocketPong { |
| 388 final List<int> payload; | 388 final List<int> payload; |
| 389 _WebSocketPong([this.payload = null]); | 389 _WebSocketPong([this.payload = null]); |
| 390 } | 390 } |
| 391 | 391 |
| 392 | 392 |
| 393 class _WebSocketTransformerImpl implements WebSocketTransformer { | 393 class _WebSocketTransformerImpl implements WebSocketTransformer { |
| 394 final StreamController<WebSocket> _controller = | 394 final StreamController<WebSocket> _controller = |
| 395 new StreamController<WebSocket>(sync: true); | 395 new StreamController<WebSocket>(sync: true); |
| 396 final Function _protocolSelector; |
| 397 |
| 398 _WebSocketTransformerImpl(this._protocolSelector); |
| 396 | 399 |
| 397 Stream<WebSocket> bind(Stream<HttpRequest> stream) { | 400 Stream<WebSocket> bind(Stream<HttpRequest> stream) { |
| 398 stream.listen((request) { | 401 stream.listen((request) { |
| 399 _upgrade(request) | 402 _upgrade(request, _protocolSelector) |
| 400 .then((WebSocket webSocket) => _controller.add(webSocket)) | 403 .then((WebSocket webSocket) => _controller.add(webSocket)) |
| 401 .catchError(_controller.addError); | 404 .catchError(_controller.addError); |
| 402 }); | 405 }); |
| 403 | 406 |
| 404 return _controller.stream; | 407 return _controller.stream; |
| 405 } | 408 } |
| 406 | 409 |
| 407 static Future<WebSocket> _upgrade(HttpRequest request) { | 410 static Future<WebSocket> _upgrade(HttpRequest request, _protocolSelector) { |
| 408 var response = request.response; | 411 var response = request.response; |
| 409 if (!_isUpgradeRequest(request)) { | 412 if (!_isUpgradeRequest(request)) { |
| 410 // Send error response and drain the request. | 413 // Send error response. |
| 411 request.listen((_) {}, onDone: () { | 414 response.statusCode = HttpStatus.BAD_REQUEST; |
| 412 response.statusCode = HttpStatus.BAD_REQUEST; | 415 response.close(); |
| 413 response.contentLength = 0; | |
| 414 response.close(); | |
| 415 }); | |
| 416 return new Future.error( | 416 return new Future.error( |
| 417 new WebSocketException("Invalid WebSocket upgrade request")); | 417 new WebSocketException("Invalid WebSocket upgrade request")); |
| 418 } | 418 } |
| 419 | 419 |
| 420 // Send the upgrade response. | 420 Future upgrade(String protocol) { |
| 421 response.statusCode = HttpStatus.SWITCHING_PROTOCOLS; | 421 // Send the upgrade response. |
| 422 response.headers.add(HttpHeaders.CONNECTION, "Upgrade"); | 422 response.statusCode = HttpStatus.SWITCHING_PROTOCOLS; |
| 423 response.headers.add(HttpHeaders.UPGRADE, "websocket"); | 423 response.headers.add(HttpHeaders.CONNECTION, "Upgrade"); |
| 424 String key = request.headers.value("Sec-WebSocket-Key"); | 424 response.headers.add(HttpHeaders.UPGRADE, "websocket"); |
| 425 _SHA1 sha1 = new _SHA1(); | 425 String key = request.headers.value("Sec-WebSocket-Key"); |
| 426 sha1.add("$key$_webSocketGUID".codeUnits); | 426 _SHA1 sha1 = new _SHA1(); |
| 427 String accept = _CryptoUtils.bytesToBase64(sha1.close()); | 427 sha1.add("$key$_webSocketGUID".codeUnits); |
| 428 response.headers.add("Sec-WebSocket-Accept", accept); | 428 String accept = _CryptoUtils.bytesToBase64(sha1.close()); |
| 429 response.headers.contentLength = 0; | 429 response.headers.add("Sec-WebSocket-Accept", accept); |
| 430 return response.detachSocket() | 430 if (protocol != null && protocol.isNotEmpty) { |
| 431 .then((socket) => new _WebSocketImpl._fromSocket(socket, true)); | 431 response.headers.add("Sec-WebSocket-Protocol", protocol); |
| 432 } |
| 433 response.headers.contentLength = 0; |
| 434 return response.detachSocket() |
| 435 .then((socket) => new _WebSocketImpl._fromSocket( |
| 436 socket, protocol, true)); |
| 437 } |
| 438 |
| 439 var protocols = request.headers['Sec-WebSocket-Protocol']; |
| 440 if (protocols != null && _protocolSelector != null) { |
| 441 // The suggested protocols can be spread over multiple lines, each |
| 442 // consisting of multiple protocols. To unify all of them, first join |
| 443 // the lists with ', ' and then tokenize. |
| 444 protocols = _HttpParser._tokenizeFieldValue(protocols.join(', ')); |
| 445 return new Future(() => _protocolSelector(protocols)) |
| 446 .then((protocol) { |
| 447 if (protocols.indexOf(protocol) < 0) { |
| 448 throw new WebSocketException( |
| 449 "Selected protocol is not in the list of available protocols"); |
| 450 } |
| 451 return protocol; |
| 452 }) |
| 453 .catchError((error) { |
| 454 response.statusCode = HttpStatus.INTERNAL_SERVER_ERROR; |
| 455 response.close(); |
| 456 throw error; |
| 457 }) |
| 458 .then(upgrade); |
| 459 } else { |
| 460 return upgrade(null); |
| 461 } |
| 432 } | 462 } |
| 433 | 463 |
| 434 static bool _isUpgradeRequest(HttpRequest request) { | 464 static bool _isUpgradeRequest(HttpRequest request) { |
| 435 if (request.method != "GET") { | 465 if (request.method != "GET") { |
| 436 return false; | 466 return false; |
| 437 } | 467 } |
| 438 if (request.headers[HttpHeaders.CONNECTION] == null) { | 468 if (request.headers[HttpHeaders.CONNECTION] == null) { |
| 439 return false; | 469 return false; |
| 440 } | 470 } |
| 441 bool isUpgrade = false; | 471 bool isUpgrade = false; |
| (...skipping 275 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 717 | 747 |
| 718 void closeSocket() { | 748 void closeSocket() { |
| 719 _closed = true; | 749 _closed = true; |
| 720 _cancel(); | 750 _cancel(); |
| 721 close(); | 751 close(); |
| 722 } | 752 } |
| 723 } | 753 } |
| 724 | 754 |
| 725 | 755 |
| 726 class _WebSocketImpl extends Stream implements WebSocket { | 756 class _WebSocketImpl extends Stream implements WebSocket { |
| 757 final String protocol; |
| 758 |
| 727 StreamController _controller; | 759 StreamController _controller; |
| 728 StreamSubscription _subscription; | 760 StreamSubscription _subscription; |
| 729 StreamSink _sink; | 761 StreamSink _sink; |
| 730 | 762 |
| 731 final Socket _socket; | 763 final Socket _socket; |
| 732 final bool _serverSide; | 764 final bool _serverSide; |
| 733 int _readyState = WebSocket.CONNECTING; | 765 int _readyState = WebSocket.CONNECTING; |
| 734 bool _writeClosed = false; | 766 bool _writeClosed = false; |
| 735 int _closeCode; | 767 int _closeCode; |
| 736 String _closeReason; | 768 String _closeReason; |
| 737 Duration _pingInterval; | 769 Duration _pingInterval; |
| 738 Timer _pingTimer; | 770 Timer _pingTimer; |
| 739 _WebSocketConsumer _consumer; | 771 _WebSocketConsumer _consumer; |
| 740 | 772 |
| 741 int _outCloseCode; | 773 int _outCloseCode; |
| 742 String _outCloseReason; | 774 String _outCloseReason; |
| 743 | 775 |
| 744 static final HttpClient _httpClient = new HttpClient(); | 776 static final HttpClient _httpClient = new HttpClient(); |
| 745 | 777 |
| 746 static Future<WebSocket> connect(String url, [protocols]) { | 778 static Future<WebSocket> connect(String url, List<String> protocols) { |
| 747 Uri uri = Uri.parse(url); | 779 Uri uri = Uri.parse(url); |
| 748 if (uri.scheme != "ws" && uri.scheme != "wss") { | 780 if (uri.scheme != "ws" && uri.scheme != "wss") { |
| 749 throw new WebSocketException("Unsupported URL scheme '${uri.scheme}'"); | 781 throw new WebSocketException("Unsupported URL scheme '${uri.scheme}'"); |
| 750 } | 782 } |
| 751 if (uri.userInfo != "") { | 783 if (uri.userInfo != "") { |
| 752 throw new WebSocketException("Unsupported user info '${uri.userInfo}'"); | 784 throw new WebSocketException("Unsupported user info '${uri.userInfo}'"); |
| 753 } | 785 } |
| 754 | 786 |
| 755 Random random = new Random(); | 787 Random random = new Random(); |
| 756 // Generate 16 random bytes. | 788 // Generate 16 random bytes. |
| (...skipping 10 matching lines...) Expand all Loading... |
| 767 path: uri.path, | 799 path: uri.path, |
| 768 query: uri.query, | 800 query: uri.query, |
| 769 fragment: uri.fragment); | 801 fragment: uri.fragment); |
| 770 return _httpClient.openUrl("GET", uri) | 802 return _httpClient.openUrl("GET", uri) |
| 771 .then((request) { | 803 .then((request) { |
| 772 // Setup the initial handshake. | 804 // Setup the initial handshake. |
| 773 request.headers.add(HttpHeaders.CONNECTION, "upgrade"); | 805 request.headers.add(HttpHeaders.CONNECTION, "upgrade"); |
| 774 request.headers.set(HttpHeaders.UPGRADE, "websocket"); | 806 request.headers.set(HttpHeaders.UPGRADE, "websocket"); |
| 775 request.headers.set("Sec-WebSocket-Key", nonce); | 807 request.headers.set("Sec-WebSocket-Key", nonce); |
| 776 request.headers.set("Sec-WebSocket-Version", "13"); | 808 request.headers.set("Sec-WebSocket-Version", "13"); |
| 809 if (protocols.isNotEmpty) { |
| 810 request.headers.add("Sec-WebSocket-Protocol", protocols); |
| 811 } |
| 777 return request.close(); | 812 return request.close(); |
| 778 }) | 813 }) |
| 779 .then((response) { | 814 .then((response) { |
| 780 void error(String message) { | 815 void error(String message) { |
| 781 // Flush data. | 816 // Flush data. |
| 782 response.detachSocket().then((socket) { | 817 response.detachSocket().then((socket) { |
| 783 socket.destroy(); | 818 socket.destroy(); |
| 784 }); | 819 }); |
| 785 throw new WebSocketException(message); | 820 throw new WebSocketException(message); |
| 786 } | 821 } |
| (...skipping 14 matching lines...) Expand all Loading... |
| 801 List<int> expectedAccept = sha1.close(); | 836 List<int> expectedAccept = sha1.close(); |
| 802 List<int> receivedAccept = _CryptoUtils.base64StringToBytes(accept); | 837 List<int> receivedAccept = _CryptoUtils.base64StringToBytes(accept); |
| 803 if (expectedAccept.length != receivedAccept.length) { | 838 if (expectedAccept.length != receivedAccept.length) { |
| 804 error("Reasponse header 'Sec-WebSocket-Accept' is the wrong length"); | 839 error("Reasponse header 'Sec-WebSocket-Accept' is the wrong length"); |
| 805 } | 840 } |
| 806 for (int i = 0; i < expectedAccept.length; i++) { | 841 for (int i = 0; i < expectedAccept.length; i++) { |
| 807 if (expectedAccept[i] != receivedAccept[i]) { | 842 if (expectedAccept[i] != receivedAccept[i]) { |
| 808 error("Bad response 'Sec-WebSocket-Accept' header"); | 843 error("Bad response 'Sec-WebSocket-Accept' header"); |
| 809 } | 844 } |
| 810 } | 845 } |
| 846 var protocol = response.headers.value('Sec-WebSocket-Protocol'); |
| 811 return response.detachSocket() | 847 return response.detachSocket() |
| 812 .then((socket) => new _WebSocketImpl._fromSocket(socket)); | 848 .then((socket) => new _WebSocketImpl._fromSocket(socket, protocol)); |
| 813 }); | 849 }); |
| 814 } | 850 } |
| 815 | 851 |
| 816 _WebSocketImpl._fromSocket(Socket this._socket, | 852 _WebSocketImpl._fromSocket(Socket this._socket, |
| 853 String this.protocol, |
| 817 [bool this._serverSide = false]) { | 854 [bool this._serverSide = false]) { |
| 818 _consumer = new _WebSocketConsumer(this, _socket); | 855 _consumer = new _WebSocketConsumer(this, _socket); |
| 819 _sink = new _StreamSinkImpl(_consumer); | 856 _sink = new _StreamSinkImpl(_consumer); |
| 820 _readyState = WebSocket.OPEN; | 857 _readyState = WebSocket.OPEN; |
| 821 | 858 |
| 822 var transformer = new _WebSocketProtocolTransformer(_serverSide); | 859 var transformer = new _WebSocketProtocolTransformer(_serverSide); |
| 823 _subscription = _socket.transform(transformer).listen( | 860 _subscription = _socket.transform(transformer).listen( |
| 824 (data) { | 861 (data) { |
| 825 if (data is _WebSocketPing) { | 862 if (data is _WebSocketPing) { |
| 826 if (!_writeClosed) _consumer.add(new _WebSocketPong(data.payload)); | 863 if (!_writeClosed) _consumer.add(new _WebSocketPong(data.payload)); |
| (...skipping 59 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 886 _pingTimer = new Timer(_pingInterval, () { | 923 _pingTimer = new Timer(_pingInterval, () { |
| 887 // No pong received. | 924 // No pong received. |
| 888 _close(WebSocketStatus.GOING_AWAY); | 925 _close(WebSocketStatus.GOING_AWAY); |
| 889 }); | 926 }); |
| 890 }); | 927 }); |
| 891 } | 928 } |
| 892 | 929 |
| 893 int get readyState => _readyState; | 930 int get readyState => _readyState; |
| 894 | 931 |
| 895 String get extensions => null; | 932 String get extensions => null; |
| 896 String get protocol => null; | |
| 897 int get closeCode => _closeCode; | 933 int get closeCode => _closeCode; |
| 898 String get closeReason => _closeReason; | 934 String get closeReason => _closeReason; |
| 899 | 935 |
| 900 void add(data) => _sink.add(data); | 936 void add(data) => _sink.add(data); |
| 901 void addError(error, [StackTrace stackTrace]) => | 937 void addError(error, [StackTrace stackTrace]) => |
| 902 _sink.addError(error, stackTrace); | 938 _sink.addError(error, stackTrace); |
| 903 Future addStream(Stream stream) => _sink.addStream(stream); | 939 Future addStream(Stream stream) => _sink.addStream(stream); |
| 904 Future get done => _sink.done; | 940 Future get done => _sink.done; |
| 905 | 941 |
| 906 Future close([int code, String reason]) { | 942 Future close([int code, String reason]) { |
| (...skipping 22 matching lines...) Expand all Loading... |
| 929 (code < WebSocketStatus.NORMAL_CLOSURE || | 965 (code < WebSocketStatus.NORMAL_CLOSURE || |
| 930 code == WebSocketStatus.RESERVED_1004 || | 966 code == WebSocketStatus.RESERVED_1004 || |
| 931 code == WebSocketStatus.NO_STATUS_RECEIVED || | 967 code == WebSocketStatus.NO_STATUS_RECEIVED || |
| 932 code == WebSocketStatus.ABNORMAL_CLOSURE || | 968 code == WebSocketStatus.ABNORMAL_CLOSURE || |
| 933 (code > WebSocketStatus.INTERNAL_SERVER_ERROR && | 969 (code > WebSocketStatus.INTERNAL_SERVER_ERROR && |
| 934 code < WebSocketStatus.RESERVED_1015) || | 970 code < WebSocketStatus.RESERVED_1015) || |
| 935 (code >= WebSocketStatus.RESERVED_1015 && | 971 (code >= WebSocketStatus.RESERVED_1015 && |
| 936 code < 3000)); | 972 code < 3000)); |
| 937 } | 973 } |
| 938 } | 974 } |
| OLD | NEW |