Chromium Code Reviews| 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 } |
|
Søren Gjesse
2013/11/27 10:43:58
Please add a newline before and after the local fu
Anders Johnsen
2013/11/27 11:43:30
Done.
| |
| 419 | 419 Future upgrade(String protocol) { |
| 420 // Send the upgrade response. | 420 // Send the upgrade response. |
| 421 response.statusCode = HttpStatus.SWITCHING_PROTOCOLS; | 421 response.statusCode = HttpStatus.SWITCHING_PROTOCOLS; |
| 422 response.headers.add(HttpHeaders.CONNECTION, "Upgrade"); | 422 response.headers.add(HttpHeaders.CONNECTION, "Upgrade"); |
| 423 response.headers.add(HttpHeaders.UPGRADE, "websocket"); | 423 response.headers.add(HttpHeaders.UPGRADE, "websocket"); |
| 424 String key = request.headers.value("Sec-WebSocket-Key"); | 424 String key = request.headers.value("Sec-WebSocket-Key"); |
| 425 _SHA1 sha1 = new _SHA1(); | 425 _SHA1 sha1 = new _SHA1(); |
| 426 sha1.add("$key$_webSocketGUID".codeUnits); | 426 sha1.add("$key$_webSocketGUID".codeUnits); |
| 427 String accept = _CryptoUtils.bytesToBase64(sha1.close()); | 427 String accept = _CryptoUtils.bytesToBase64(sha1.close()); |
| 428 response.headers.add("Sec-WebSocket-Accept", accept); | 428 response.headers.add("Sec-WebSocket-Accept", accept); |
| 429 response.headers.contentLength = 0; | 429 if (protocol != null && protocol.isNotEmpty) { |
| 430 return response.detachSocket() | 430 response.headers.add("Sec-WebSocket-Protocol", protocol); |
| 431 .then((socket) => new _WebSocketImpl._fromSocket(socket, true)); | 431 } |
| 432 response.headers.contentLength = 0; | |
| 433 return response.detachSocket() | |
| 434 .then((socket) => new _WebSocketImpl._fromSocket( | |
| 435 socket, protocol, true)); | |
| 436 } | |
| 437 var protocols = request.headers['Sec-WebSocket-Protocol']; | |
| 438 if (protocols != null && _protocolSelector != null) { | |
|
Søren Gjesse
2013/11/27 10:43:58
Please explain the handling of
Sec-WebSocket-Prot
Anders Johnsen
2013/11/27 11:43:30
Done.
| |
| 439 protocols = _HttpParser._tokenizeFieldValue(protocols.join(', ')); | |
| 440 return new Future(() => _protocolSelector(protocols)) | |
| 441 .then(upgrade, onError: (error) { | |
| 442 response.statusCode = HttpStatus.INTERNAL_SERVER_ERROR; | |
| 443 response.close(); | |
| 444 throw error; | |
| 445 }); | |
| 446 } else { | |
| 447 return upgrade(null); | |
| 448 } | |
| 432 } | 449 } |
| 433 | 450 |
| 434 static bool _isUpgradeRequest(HttpRequest request) { | 451 static bool _isUpgradeRequest(HttpRequest request) { |
| 435 if (request.method != "GET") { | 452 if (request.method != "GET") { |
| 436 return false; | 453 return false; |
| 437 } | 454 } |
| 438 if (request.headers[HttpHeaders.CONNECTION] == null) { | 455 if (request.headers[HttpHeaders.CONNECTION] == null) { |
| 439 return false; | 456 return false; |
| 440 } | 457 } |
| 441 bool isUpgrade = false; | 458 bool isUpgrade = false; |
| (...skipping 275 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 717 | 734 |
| 718 void closeSocket() { | 735 void closeSocket() { |
| 719 _closed = true; | 736 _closed = true; |
| 720 _cancel(); | 737 _cancel(); |
| 721 close(); | 738 close(); |
| 722 } | 739 } |
| 723 } | 740 } |
| 724 | 741 |
| 725 | 742 |
| 726 class _WebSocketImpl extends Stream implements WebSocket { | 743 class _WebSocketImpl extends Stream implements WebSocket { |
| 744 final String protocol; | |
| 745 | |
| 727 StreamController _controller; | 746 StreamController _controller; |
| 728 StreamSubscription _subscription; | 747 StreamSubscription _subscription; |
| 729 StreamSink _sink; | 748 StreamSink _sink; |
| 730 | 749 |
| 731 final Socket _socket; | 750 final Socket _socket; |
| 732 final bool _serverSide; | 751 final bool _serverSide; |
| 733 int _readyState = WebSocket.CONNECTING; | 752 int _readyState = WebSocket.CONNECTING; |
| 734 bool _writeClosed = false; | 753 bool _writeClosed = false; |
| 735 int _closeCode; | 754 int _closeCode; |
| 736 String _closeReason; | 755 String _closeReason; |
| 737 Duration _pingInterval; | 756 Duration _pingInterval; |
| 738 Timer _pingTimer; | 757 Timer _pingTimer; |
| 739 _WebSocketConsumer _consumer; | 758 _WebSocketConsumer _consumer; |
| 740 | 759 |
| 741 int _outCloseCode; | 760 int _outCloseCode; |
| 742 String _outCloseReason; | 761 String _outCloseReason; |
| 743 | 762 |
| 744 static final HttpClient _httpClient = new HttpClient(); | 763 static final HttpClient _httpClient = new HttpClient(); |
| 745 | 764 |
| 746 static Future<WebSocket> connect(String url, [protocols]) { | 765 static Future<WebSocket> connect(String url, List<String> protocols) { |
| 747 Uri uri = Uri.parse(url); | 766 Uri uri = Uri.parse(url); |
| 748 if (uri.scheme != "ws" && uri.scheme != "wss") { | 767 if (uri.scheme != "ws" && uri.scheme != "wss") { |
| 749 throw new WebSocketException("Unsupported URL scheme '${uri.scheme}'"); | 768 throw new WebSocketException("Unsupported URL scheme '${uri.scheme}'"); |
| 750 } | 769 } |
| 751 if (uri.userInfo != "") { | 770 if (uri.userInfo != "") { |
| 752 throw new WebSocketException("Unsupported user info '${uri.userInfo}'"); | 771 throw new WebSocketException("Unsupported user info '${uri.userInfo}'"); |
| 753 } | 772 } |
| 754 | 773 |
| 755 Random random = new Random(); | 774 Random random = new Random(); |
| 756 // Generate 16 random bytes. | 775 // Generate 16 random bytes. |
| (...skipping 10 matching lines...) Expand all Loading... | |
| 767 path: uri.path, | 786 path: uri.path, |
| 768 query: uri.query, | 787 query: uri.query, |
| 769 fragment: uri.fragment); | 788 fragment: uri.fragment); |
| 770 return _httpClient.openUrl("GET", uri) | 789 return _httpClient.openUrl("GET", uri) |
| 771 .then((request) { | 790 .then((request) { |
| 772 // Setup the initial handshake. | 791 // Setup the initial handshake. |
| 773 request.headers.add(HttpHeaders.CONNECTION, "upgrade"); | 792 request.headers.add(HttpHeaders.CONNECTION, "upgrade"); |
| 774 request.headers.set(HttpHeaders.UPGRADE, "websocket"); | 793 request.headers.set(HttpHeaders.UPGRADE, "websocket"); |
| 775 request.headers.set("Sec-WebSocket-Key", nonce); | 794 request.headers.set("Sec-WebSocket-Key", nonce); |
| 776 request.headers.set("Sec-WebSocket-Version", "13"); | 795 request.headers.set("Sec-WebSocket-Version", "13"); |
| 796 if (protocols.isNotEmpty) { | |
| 797 request.headers.add("Sec-WebSocket-Protocol", protocols); | |
| 798 } | |
| 777 return request.close(); | 799 return request.close(); |
| 778 }) | 800 }) |
| 779 .then((response) { | 801 .then((response) { |
| 780 void error(String message) { | 802 void error(String message) { |
| 781 // Flush data. | 803 // Flush data. |
| 782 response.detachSocket().then((socket) { | 804 response.detachSocket().then((socket) { |
| 783 socket.destroy(); | 805 socket.destroy(); |
| 784 }); | 806 }); |
| 785 throw new WebSocketException(message); | 807 throw new WebSocketException(message); |
| 786 } | 808 } |
| (...skipping 14 matching lines...) Expand all Loading... | |
| 801 List<int> expectedAccept = sha1.close(); | 823 List<int> expectedAccept = sha1.close(); |
| 802 List<int> receivedAccept = _CryptoUtils.base64StringToBytes(accept); | 824 List<int> receivedAccept = _CryptoUtils.base64StringToBytes(accept); |
| 803 if (expectedAccept.length != receivedAccept.length) { | 825 if (expectedAccept.length != receivedAccept.length) { |
| 804 error("Reasponse header 'Sec-WebSocket-Accept' is the wrong length"); | 826 error("Reasponse header 'Sec-WebSocket-Accept' is the wrong length"); |
| 805 } | 827 } |
| 806 for (int i = 0; i < expectedAccept.length; i++) { | 828 for (int i = 0; i < expectedAccept.length; i++) { |
| 807 if (expectedAccept[i] != receivedAccept[i]) { | 829 if (expectedAccept[i] != receivedAccept[i]) { |
| 808 error("Bad response 'Sec-WebSocket-Accept' header"); | 830 error("Bad response 'Sec-WebSocket-Accept' header"); |
| 809 } | 831 } |
| 810 } | 832 } |
| 833 var protocol = response.headers.value('Sec-WebSocket-Protocol'); | |
| 811 return response.detachSocket() | 834 return response.detachSocket() |
| 812 .then((socket) => new _WebSocketImpl._fromSocket(socket)); | 835 .then((socket) => new _WebSocketImpl._fromSocket(socket, protocol)); |
| 813 }); | 836 }); |
| 814 } | 837 } |
| 815 | 838 |
| 816 _WebSocketImpl._fromSocket(Socket this._socket, | 839 _WebSocketImpl._fromSocket(Socket this._socket, |
| 840 String this.protocol, | |
| 817 [bool this._serverSide = false]) { | 841 [bool this._serverSide = false]) { |
| 818 _consumer = new _WebSocketConsumer(this, _socket); | 842 _consumer = new _WebSocketConsumer(this, _socket); |
| 819 _sink = new _StreamSinkImpl(_consumer); | 843 _sink = new _StreamSinkImpl(_consumer); |
| 820 _readyState = WebSocket.OPEN; | 844 _readyState = WebSocket.OPEN; |
| 821 | 845 |
| 822 var transformer = new _WebSocketProtocolTransformer(_serverSide); | 846 var transformer = new _WebSocketProtocolTransformer(_serverSide); |
| 823 _subscription = _socket.transform(transformer).listen( | 847 _subscription = _socket.transform(transformer).listen( |
| 824 (data) { | 848 (data) { |
| 825 if (data is _WebSocketPing) { | 849 if (data is _WebSocketPing) { |
| 826 if (!_writeClosed) _consumer.add(new _WebSocketPong(data.payload)); | 850 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, () { | 910 _pingTimer = new Timer(_pingInterval, () { |
| 887 // No pong received. | 911 // No pong received. |
| 888 _close(WebSocketStatus.GOING_AWAY); | 912 _close(WebSocketStatus.GOING_AWAY); |
| 889 }); | 913 }); |
| 890 }); | 914 }); |
| 891 } | 915 } |
| 892 | 916 |
| 893 int get readyState => _readyState; | 917 int get readyState => _readyState; |
| 894 | 918 |
| 895 String get extensions => null; | 919 String get extensions => null; |
| 896 String get protocol => null; | |
| 897 int get closeCode => _closeCode; | 920 int get closeCode => _closeCode; |
| 898 String get closeReason => _closeReason; | 921 String get closeReason => _closeReason; |
| 899 | 922 |
| 900 void add(data) => _sink.add(data); | 923 void add(data) => _sink.add(data); |
| 901 void addError(error, [StackTrace stackTrace]) => | 924 void addError(error, [StackTrace stackTrace]) => |
| 902 _sink.addError(error, stackTrace); | 925 _sink.addError(error, stackTrace); |
| 903 Future addStream(Stream stream) => _sink.addStream(stream); | 926 Future addStream(Stream stream) => _sink.addStream(stream); |
| 904 Future get done => _sink.done; | 927 Future get done => _sink.done; |
| 905 | 928 |
| 906 Future close([int code, String reason]) { | 929 Future close([int code, String reason]) { |
| (...skipping 22 matching lines...) Expand all Loading... | |
| 929 (code < WebSocketStatus.NORMAL_CLOSURE || | 952 (code < WebSocketStatus.NORMAL_CLOSURE || |
| 930 code == WebSocketStatus.RESERVED_1004 || | 953 code == WebSocketStatus.RESERVED_1004 || |
| 931 code == WebSocketStatus.NO_STATUS_RECEIVED || | 954 code == WebSocketStatus.NO_STATUS_RECEIVED || |
| 932 code == WebSocketStatus.ABNORMAL_CLOSURE || | 955 code == WebSocketStatus.ABNORMAL_CLOSURE || |
| 933 (code > WebSocketStatus.INTERNAL_SERVER_ERROR && | 956 (code > WebSocketStatus.INTERNAL_SERVER_ERROR && |
| 934 code < WebSocketStatus.RESERVED_1015) || | 957 code < WebSocketStatus.RESERVED_1015) || |
| 935 (code >= WebSocketStatus.RESERVED_1015 && | 958 (code >= WebSocketStatus.RESERVED_1015 && |
| 936 code < 3000)); | 959 code < 3000)); |
| 937 } | 960 } |
| 938 } | 961 } |
| OLD | NEW |