Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(241)

Side by Side Diff: sdk/lib/io/websocket_impl.dart

Issue 14208007: Improve WebSocket interface by making it implement StreamSink. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 7 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
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
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 sendFrame(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 sendFrame(_WebSocketOpcode.CLOSE, data, sink);
473 sink.close();
474 }
475
476 void sendFrame(int opcode, List<int> data, EventSink<List<int>> sink) {
Søren Gjesse 2013/04/16 12:41:31 Rename to addFrame.
Anders Johnsen 2013/04/16 13:46:22 Done.
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 53 matching lines...) Expand 10 before | Expand all | Expand 10 after
508 if (expectedAccept[i] != receivedAccept[i]) { 678 if (expectedAccept[i] != receivedAccept[i]) {
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]) {
Søren Gjesse 2013/04/16 12:41:31 Disable TCP_NODELAY here as well.
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 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698