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 |