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

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

Issue 1208473005: WebSocket Compression (Closed) Base URL: https://github.com/dart-lang/sdk.git
Patch Set: Created 5 years, 6 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
« sdk/lib/io/websocket.dart ('K') | « sdk/lib/io/websocket.dart ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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 // Matches _WebSocketOpcode. 9 // Matches _WebSocketOpcode.
10 class _WebSocketMessageType { 10 class _WebSocketMessageType {
(...skipping 20 matching lines...) Expand all
31 static const int RESERVED_D = 13; 31 static const int RESERVED_D = 13;
32 static const int RESERVED_E = 14; 32 static const int RESERVED_E = 14;
33 static const int RESERVED_F = 15; 33 static const int RESERVED_F = 15;
34 } 34 }
35 35
36 /** 36 /**
37 * The web socket protocol transformer handles the protocol byte stream 37 * The web socket protocol transformer handles the protocol byte stream
38 * which is supplied through the [:handleData:]. As the protocol is processed, 38 * which is supplied through the [:handleData:]. As the protocol is processed,
39 * it'll output frame data as either a List<int> or String. 39 * it'll output frame data as either a List<int> or String.
40 * 40 *
41 * Important infomation about usage: Be sure you use cancelOnError, so the 41 * Important information about usage: Be sure you use cancelOnError, so the
42 * socket will be closed when the processer encounter an error. Not using it 42 * socket will be closed when the processor encounter an error. Not using it
43 * will lead to undefined behaviour. 43 * will lead to undefined behaviour.
44 */ 44 */
45 // TODO(ajohnsen): make this transformer reusable? 45 // TODO(ajohnsen): make this transformer reusable?
46 class _WebSocketProtocolTransformer implements StreamTransformer, EventSink { 46 class _WebSocketProtocolTransformer implements StreamTransformer, EventSink {
47 static const int START = 0; 47 static const int START = 0;
48 static const int LEN_FIRST = 1; 48 static const int LEN_FIRST = 1;
49 static const int LEN_REST = 2; 49 static const int LEN_REST = 2;
50 static const int MASK = 3; 50 static const int MASK = 3;
51 static const int PAYLOAD = 4; 51 static const int PAYLOAD = 4;
52 static const int CLOSED = 5; 52 static const int CLOSED = 5;
53 static const int FAILURE = 6; 53 static const int FAILURE = 6;
54 54
55 int _state = START; 55 int _state = START;
56 bool _fin = false; 56 bool _fin = false;
57 bool _compressed = false;
57 int _opcode = -1; 58 int _opcode = -1;
58 int _len = -1; 59 int _len = -1;
59 bool _masked = false; 60 bool _masked = false;
60 int _remainingLenBytes = -1; 61 int _remainingLenBytes = -1;
61 int _remainingMaskingKeyBytes = 4; 62 int _remainingMaskingKeyBytes = 4;
62 int _remainingPayloadBytes = -1; 63 int _remainingPayloadBytes = -1;
63 int _unmaskingIndex = 0; 64 int _unmaskingIndex = 0;
64 int _currentMessageType = _WebSocketMessageType.NONE; 65 int _currentMessageType = _WebSocketMessageType.NONE;
65 int closeCode = WebSocketStatus.NO_STATUS_RECEIVED; 66 int closeCode = WebSocketStatus.NO_STATUS_RECEIVED;
66 String closeReason = ""; 67 String closeReason = "";
67 68
68 EventSink _eventSink; 69 EventSink _eventSink;
69 70
70 final bool _serverSide; 71 final bool _serverSide;
71 final List _maskingBytes = new List(4); 72 final List _maskingBytes = new List(4);
72 final BytesBuilder _payload = new BytesBuilder(copy: false); 73 final BytesBuilder _payload = new BytesBuilder(copy: false);
73 74
74 _WebSocketProtocolTransformer([this._serverSide = false]); 75 _WebSocketPerMessageDeflate _deflate;
76 _WebSocketProtocolTransformer([this._serverSide = false, this._deflate]);
75 77
76 Stream bind(Stream stream) { 78 Stream bind(Stream stream) {
77 return new Stream.eventTransformed( 79 return new Stream.eventTransformed(
78 stream, 80 stream,
79 (EventSink eventSink) { 81 (EventSink eventSink) {
80 if (_eventSink != null) { 82 if (_eventSink != null) {
81 throw new StateError("WebSocket transformer already used."); 83 throw new StateError("WebSocket transformer already used.");
82 } 84 }
83 _eventSink = eventSink; 85 _eventSink = eventSink;
84 return this; 86 return this;
(...skipping 15 matching lines...) Expand all
100 if (_state == CLOSED) { 102 if (_state == CLOSED) {
101 throw new WebSocketException("Data on closed connection"); 103 throw new WebSocketException("Data on closed connection");
102 } 104 }
103 if (_state == FAILURE) { 105 if (_state == FAILURE) {
104 throw new WebSocketException("Data on failed connection"); 106 throw new WebSocketException("Data on failed connection");
105 } 107 }
106 while ((index < lastIndex) && _state != CLOSED && _state != FAILURE) { 108 while ((index < lastIndex) && _state != CLOSED && _state != FAILURE) {
107 int byte = buffer[index]; 109 int byte = buffer[index];
108 if (_state <= LEN_REST) { 110 if (_state <= LEN_REST) {
109 if (_state == START) { 111 if (_state == START) {
110 _fin = (byte & 0x80) != 0; 112 _fin = (byte & 0x80) != 0;
Søren Gjesse 2015/06/25 15:41:27 Please add constants for FIN, RSV1, RSV2 and RSV3
111 if ((byte & 0x70) != 0) { 113
112 // The RSV1, RSV2 bits RSV3 must be all zero. 114 if ((byte & 0x40) != 0) {
115 _compressed = true;
116 }
117
118 if ((byte & 0x20) != 0 || (byte & 0x10) != 0) {
119 // The RSV2 and RSV3 bits must be all zero.
113 throw new WebSocketException("Protocol error"); 120 throw new WebSocketException("Protocol error");
114 } 121 }
122
115 _opcode = (byte & 0xF); 123 _opcode = (byte & 0xF);
Søren Gjesse 2015/06/25 15:41:27 Please add a constant for the opcode mask.
124
116 if (_opcode <= _WebSocketOpcode.BINARY) { 125 if (_opcode <= _WebSocketOpcode.BINARY) {
117 if (_opcode == _WebSocketOpcode.CONTINUATION) { 126 if (_opcode == _WebSocketOpcode.CONTINUATION) {
118 if (_currentMessageType == _WebSocketMessageType.NONE) { 127 if (_currentMessageType == _WebSocketMessageType.NONE) {
119 throw new WebSocketException("Protocol error"); 128 throw new WebSocketException("Protocol error");
120 } 129 }
121 } else { 130 } else {
122 assert(_opcode == _WebSocketOpcode.TEXT || 131 assert(_opcode == _WebSocketOpcode.TEXT ||
123 _opcode == _WebSocketOpcode.BINARY); 132 _opcode == _WebSocketOpcode.BINARY);
124 if (_currentMessageType != _WebSocketMessageType.NONE) { 133 if (_currentMessageType != _WebSocketMessageType.NONE) {
125 throw new WebSocketException("Protocol error"); 134 throw new WebSocketException("Protocol error");
(...skipping 151 matching lines...) Expand 10 before | Expand all | Expand 10 after
277 } else { 286 } else {
278 _messageFrameEnd(); 287 _messageFrameEnd();
279 } 288 }
280 } else { 289 } else {
281 _state = PAYLOAD; 290 _state = PAYLOAD;
282 } 291 }
283 } 292 }
284 293
285 void _messageFrameEnd() { 294 void _messageFrameEnd() {
286 if (_fin) { 295 if (_fin) {
296 var bytes = _payload.takeBytes();
297 if (_deflate != null && _compressed) {
298 bytes = _deflate.processIncomingMessage(bytes);
299 }
300
287 switch (_currentMessageType) { 301 switch (_currentMessageType) {
288 case _WebSocketMessageType.TEXT: 302 case _WebSocketMessageType.TEXT:
289 _eventSink.add(UTF8.decode(_payload.takeBytes())); 303 _eventSink.add(UTF8.decode(bytes));
290 break; 304 break;
291 case _WebSocketMessageType.BINARY: 305 case _WebSocketMessageType.BINARY:
292 _eventSink.add(_payload.takeBytes()); 306 _eventSink.add(bytes);
293 break; 307 break;
294 } 308 }
295 _currentMessageType = _WebSocketMessageType.NONE; 309 _currentMessageType = _WebSocketMessageType.NONE;
296 } 310 }
297 _prepareForNextFrame(); 311 _prepareForNextFrame();
298 } 312 }
299 313
300 void _controlFrameEnd() { 314 void _controlFrameEnd() {
301 switch (_opcode) { 315 switch (_opcode) {
302 case _WebSocketOpcode.CLOSE: 316 case _WebSocketOpcode.CLOSE:
(...skipping 54 matching lines...) Expand 10 before | Expand all | Expand 10 after
357 class _WebSocketPong { 371 class _WebSocketPong {
358 final List<int> payload; 372 final List<int> payload;
359 _WebSocketPong([this.payload = null]); 373 _WebSocketPong([this.payload = null]);
360 } 374 }
361 375
362 376
363 class _WebSocketTransformerImpl implements WebSocketTransformer { 377 class _WebSocketTransformerImpl implements WebSocketTransformer {
364 final StreamController<WebSocket> _controller = 378 final StreamController<WebSocket> _controller =
365 new StreamController<WebSocket>(sync: true); 379 new StreamController<WebSocket>(sync: true);
366 final Function _protocolSelector; 380 final Function _protocolSelector;
381 final CompressionOptions _compression;
367 382
368 _WebSocketTransformerImpl(this._protocolSelector); 383 _WebSocketTransformerImpl(this._protocolSelector, this._compression);
369 384
370 Stream<WebSocket> bind(Stream<HttpRequest> stream) { 385 Stream<WebSocket> bind(Stream<HttpRequest> stream) {
371 stream.listen((request) { 386 stream.listen((request) {
372 _upgrade(request, _protocolSelector) 387 _upgrade(request, _protocolSelector, _compression)
373 .then((WebSocket webSocket) => _controller.add(webSocket)) 388 .then((WebSocket webSocket) => _controller.add(webSocket))
374 .catchError(_controller.addError); 389 .catchError(_controller.addError);
375 }, onDone: () { 390 }, onDone: () {
376 _controller.close(); 391 _controller.close();
377 }); 392 });
378 393
379 return _controller.stream; 394 return _controller.stream;
380 } 395 }
381 396
382 static Future<WebSocket> _upgrade(HttpRequest request, _protocolSelector) { 397 static Future<WebSocket> _upgrade(HttpRequest request, _protocolSelector,
398 CompressionOptions compression) {
383 var response = request.response; 399 var response = request.response;
384 if (!_isUpgradeRequest(request)) { 400 if (!_isUpgradeRequest(request)) {
385 // Send error response. 401 // Send error response.
386 response 402 response
387 ..statusCode = HttpStatus.BAD_REQUEST 403 ..statusCode = HttpStatus.BAD_REQUEST
388 ..close(); 404 ..close();
389 return new Future.error( 405 return new Future.error(
390 new WebSocketException("Invalid WebSocket upgrade request")); 406 new WebSocketException("Invalid WebSocket upgrade request"));
391 } 407 }
392 408
393 Future upgrade(String protocol) { 409 Future upgrade(String protocol) {
394 // Send the upgrade response. 410 // Send the upgrade response.
395 response 411 response
396 ..statusCode = HttpStatus.SWITCHING_PROTOCOLS 412 ..statusCode = HttpStatus.SWITCHING_PROTOCOLS
397 ..headers.add(HttpHeaders.CONNECTION, "Upgrade") 413 ..headers.add(HttpHeaders.CONNECTION, "Upgrade")
398 ..headers.add(HttpHeaders.UPGRADE, "websocket"); 414 ..headers.add(HttpHeaders.UPGRADE, "websocket");
399 String key = request.headers.value("Sec-WebSocket-Key"); 415 String key = request.headers.value("Sec-WebSocket-Key");
400 _SHA1 sha1 = new _SHA1(); 416 _SHA1 sha1 = new _SHA1();
401 sha1.add("$key$_webSocketGUID".codeUnits); 417 sha1.add("$key$_webSocketGUID".codeUnits);
402 String accept = _CryptoUtils.bytesToBase64(sha1.close()); 418 String accept = _CryptoUtils.bytesToBase64(sha1.close());
403 response.headers.add("Sec-WebSocket-Accept", accept); 419 response.headers.add("Sec-WebSocket-Accept", accept);
404 if (protocol != null) { 420 if (protocol != null) {
405 response.headers.add("Sec-WebSocket-Protocol", protocol); 421 response.headers.add("Sec-WebSocket-Protocol", protocol);
406 } 422 }
423
Søren Gjesse 2015/06/25 15:41:27 Extract server side negotiation into a separate me
424 var extensionHeader = request.headers.value("Sec-WebSocket-Extensions");
425
426 if (extensionHeader == null) {
427 extensionHeader = "";
428 }
429
430 Iterable<List<String>> extensions = extensionHeader.split(",").map((it) => it.split("; "));
431
432 if (compression.enabled && extensions.any((x) => x[0] == "permessage-defla te")) {
433 var opts = extensions.firstWhere((x) => x[0] == "permessage-deflate");
434 response.headers.add("Sec-WebSocket-Extensions", compression._createHead er(opts));
435 }
436
407 response.headers.contentLength = 0; 437 response.headers.contentLength = 0;
408 return response.detachSocket() 438 return response.detachSocket()
409 .then((socket) => new _WebSocketImpl._fromSocket( 439 .then((socket) => new _WebSocketImpl._fromSocket(
Søren Gjesse 2015/06/25 15:41:27 Compression not supported for server (no additiona
410 socket, protocol, true)); 440 socket, protocol, compression, true));
411 } 441 }
412 442
413 var protocols = request.headers['Sec-WebSocket-Protocol']; 443 var protocols = request.headers['Sec-WebSocket-Protocol'];
414 if (protocols != null && _protocolSelector != null) { 444 if (protocols != null && _protocolSelector != null) {
415 // The suggested protocols can be spread over multiple lines, each 445 // The suggested protocols can be spread over multiple lines, each
416 // consisting of multiple protocols. To unify all of them, first join 446 // consisting of multiple protocols. To unify all of them, first join
417 // the lists with ', ' and then tokenize. 447 // the lists with ', ' and then tokenize.
418 protocols = _HttpParser._tokenizeFieldValue(protocols.join(', ')); 448 protocols = _HttpParser._tokenizeFieldValue(protocols.join(', '));
419 return new Future(() => _protocolSelector(protocols)) 449 return new Future(() => _protocolSelector(protocols))
420 .then((protocol) { 450 .then((protocol) {
(...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after
457 return false; 487 return false;
458 } 488 }
459 String key = request.headers.value("Sec-WebSocket-Key"); 489 String key = request.headers.value("Sec-WebSocket-Key");
460 if (key == null) { 490 if (key == null) {
461 return false; 491 return false;
462 } 492 }
463 return true; 493 return true;
464 } 494 }
465 } 495 }
466 496
497 class _WebSocketPerMessageDeflate {
498 bool noContextTakeover;
499 int clientMaxWindowBits;
500 int serverMaxWindowBits;
501 bool serverSide;
502
503 ZLibDecoder decoder;
504 ZLibEncoder encoder;
505
506 _WebSocketPerMessageDeflate({this.clientMaxWindowBits,
Søren Gjesse 2015/06/25 15:41:27 No need to use optional named argument for interna
507 this.serverMaxWindowBits, this.noContextTakeover,
508 this.serverSide: false}) {
509 if (clientMaxWindowBits == null) {
Søren Gjesse 2015/06/25 15:41:27 Make the caller always pass a non null value - it
510 clientMaxWindowBits = 15;
Søren Gjesse 2015/06/25 15:41:27 Create a constant for this (maybe in CompressionOp
511 }
512
513 if (serverMaxWindowBits == null) {
514 serverMaxWindowBits = 15;
515 }
516 }
517
518 void _ensureDecoder() {
519 if (noContextTakeover || decoder == null) {
520 decoder = new ZLibDecoder(windowBits: serverSide ?
521 clientMaxWindowBits : serverMaxWindowBits);
522 }
523 }
524
525 void _ensureEncoder() {
526 if (noContextTakeover || encoder == null) {
527 encoder = new ZLibEncoder(windowBits: serverSide ?
528 serverMaxWindowBits : clientMaxWindowBits);
529 }
530 }
531
532 List<int> processIncomingMessage(List<int> msg) {
533 _ensureDecoder();
534 var builder = new BytesBuilder();
535 builder.add(msg);
536 builder.add(const [0x00, 0x00, 0xff, 0xff]);
Søren Gjesse 2015/06/25 15:41:27 For the noContextTakeover case set decoder to null
537 return decoder.convert(builder.takeBytes());
538 }
539
540 List<int> processOutgoingMessage(List<int> msg) {
541 _ensureEncoder();
542 var c = encoder.convert(msg);
543 c = c.sublist(0, c.length - 4);
Søren Gjesse 2015/06/25 15:41:27 Ditto.
544 return c;
545 }
546 }
467 547
468 // TODO(ajohnsen): Make this transformer reusable. 548 // TODO(ajohnsen): Make this transformer reusable.
469 class _WebSocketOutgoingTransformer implements StreamTransformer, EventSink { 549 class _WebSocketOutgoingTransformer implements StreamTransformer, EventSink {
470 final _WebSocketImpl webSocket; 550 final _WebSocketImpl webSocket;
471 EventSink _eventSink; 551 EventSink _eventSink;
472 552
553 _WebSocketPerMessageDeflate _deflateHelper;
554
473 _WebSocketOutgoingTransformer(this.webSocket); 555 _WebSocketOutgoingTransformer(this.webSocket);
474 556
475 Stream bind(Stream stream) { 557 Stream bind(Stream stream) {
476 return new Stream.eventTransformed( 558 return new Stream.eventTransformed(
477 stream, 559 stream,
478 (EventSink eventSink) { 560 (EventSink eventSink) {
479 if (_eventSink != null) { 561 if (_eventSink != null) {
480 throw new StateError("WebSocket transformer already used"); 562 throw new StateError("WebSocket transformer already used");
481 } 563 }
482 _eventSink = eventSink; 564 _eventSink = eventSink;
483 return this; 565 return this;
484 }); 566 });
485 } 567 }
486 568
487 void add(message) { 569 void add(message) {
488 if (message is _WebSocketPong) { 570 if (message is _WebSocketPong) {
489 addFrame(_WebSocketOpcode.PONG, message.payload); 571 addFrame(_WebSocketOpcode.PONG, message.payload);
490 return; 572 return;
491 } 573 }
492 if (message is _WebSocketPing) { 574 if (message is _WebSocketPing) {
493 addFrame(_WebSocketOpcode.PING, message.payload); 575 addFrame(_WebSocketOpcode.PING, message.payload);
494 return; 576 return;
495 } 577 }
496 List<int> data; 578 List<int> data;
497 int opcode; 579 int opcode;
498 if (message != null) { 580 if (message != null) {
499 if (message is String) { 581 if (message is String) {
500 opcode = _WebSocketOpcode.TEXT; 582 opcode = _WebSocketOpcode.TEXT;
501 data = UTF8.encode(message); 583 data = UTF8.encode(message);
584
585 if (_deflateHelper != null) {
586 data = _deflateHelper.processOutgoingMessage(data);
587 }
502 } else { 588 } else {
503 if (message is !List<int>) { 589 if (message is !List<int>) {
504 throw new ArgumentError(message); 590 throw new ArgumentError(message);
505 } 591 }
506 opcode = _WebSocketOpcode.BINARY; 592 opcode = _WebSocketOpcode.BINARY;
507 data = message; 593 data = message;
594
595 if (_deflateHelper != null) {
596 data = _deflateHelper.processOutgoingMessage(data);
597 }
598 }
599
600 if (_deflateHelper != null) {
601 data = _deflateHelper.processOutgoingMessage(data);
508 } 602 }
509 } else { 603 } else {
510 opcode = _WebSocketOpcode.TEXT; 604 opcode = _WebSocketOpcode.TEXT;
511 } 605 }
512 addFrame(opcode, data); 606 addFrame(opcode, data);
513 } 607 }
514 608
515 void addError(Object error, [StackTrace stackTrace]) => 609 void addError(Object error, [StackTrace stackTrace]) =>
516 _eventSink.addError(error, stackTrace); 610 _eventSink.addError(error, stackTrace);
517 611
518 void close() { 612 void close() {
519 int code = webSocket._outCloseCode; 613 int code = webSocket._outCloseCode;
520 String reason = webSocket._outCloseReason; 614 String reason = webSocket._outCloseReason;
521 List<int> data; 615 List<int> data;
522 if (code != null) { 616 if (code != null) {
523 data = new List<int>(); 617 data = new List<int>();
524 data.add((code >> 8) & 0xFF); 618 data.add((code >> 8) & 0xFF);
525 data.add(code & 0xFF); 619 data.add(code & 0xFF);
526 if (reason != null) { 620 if (reason != null) {
527 data.addAll(UTF8.encode(reason)); 621 data.addAll(UTF8.encode(reason));
528 } 622 }
529 } 623 }
530 addFrame(_WebSocketOpcode.CLOSE, data); 624 addFrame(_WebSocketOpcode.CLOSE, data);
531 _eventSink.close(); 625 _eventSink.close();
532 } 626 }
533 627
534 void addFrame(int opcode, List<int> data) => 628 void addFrame(int opcode, List<int> data) =>
535 createFrame(opcode, data, webSocket._serverSide).forEach(_eventSink.add); 629 createFrame(opcode, data, webSocket._serverSide, webSocket._deflate != null)
630 .forEach((e) {
631 _eventSink.add(e);
632 });
536 633
537 static Iterable createFrame(int opcode, List<int> data, bool serverSide) { 634 static Iterable createFrame(int opcode, List<int> data, bool serverSide, bool compressed) {
538 bool mask = !serverSide; // Masking not implemented for server. 635 bool mask = !serverSide; // Masking not implemented for server.
539 int dataLength = data == null ? 0 : data.length; 636 int dataLength = data == null ? 0 : data.length;
540 // Determine the header size. 637 // Determine the header size.
541 int headerSize = (mask) ? 6 : 2; 638 int headerSize = (mask) ? 6 : 2;
542 if (dataLength > 65535) { 639 if (dataLength > 65535) {
543 headerSize += 8; 640 headerSize += 8;
544 } else if (dataLength > 125) { 641 } else if (dataLength > 125) {
545 headerSize += 2; 642 headerSize += 2;
546 } 643 }
547 Uint8List header = new Uint8List(headerSize); 644 Uint8List header = new Uint8List(headerSize);
548 int index = 0; 645 int index = 0;
549 // Set FIN and opcode. 646 // Set FIN and opcode.
550 header[index++] = 0x80 | opcode; 647 var hoc = 0;
648
649 hoc |= 0x80;
650
651 if (compressed) {
652 hoc |= 0x40;
653 }
654
655 hoc |= opcode & 0xF;
656
657 header[index++] = hoc;
551 // Determine size and position of length field. 658 // Determine size and position of length field.
552 int lengthBytes = 1; 659 int lengthBytes = 1;
553 int firstLengthByte = 1;
554 if (dataLength > 65535) { 660 if (dataLength > 65535) {
555 header[index++] = 127; 661 header[index++] = 127;
556 lengthBytes = 8; 662 lengthBytes = 8;
557 } else if (dataLength > 125) { 663 } else if (dataLength > 125) {
558 header[index++] = 126; 664 header[index++] = 126;
559 lengthBytes = 2; 665 lengthBytes = 2;
560 } 666 }
561 // Write the length in network byte order into the header. 667 // Write the length in network byte order into the header.
562 for (int i = 0; i < lengthBytes; i++) { 668 for (int i = 0; i < lengthBytes; i++) {
563 header[index++] = dataLength >> (((lengthBytes - 1) - i) * 8) & 0xFF; 669 header[index++] = dataLength >> (((lengthBytes - 1) - i) * 8) & 0xFF;
(...skipping 195 matching lines...) Expand 10 before | Expand all | Expand 10 after
759 bool _writeClosed = false; 865 bool _writeClosed = false;
760 int _closeCode; 866 int _closeCode;
761 String _closeReason; 867 String _closeReason;
762 Duration _pingInterval; 868 Duration _pingInterval;
763 Timer _pingTimer; 869 Timer _pingTimer;
764 _WebSocketConsumer _consumer; 870 _WebSocketConsumer _consumer;
765 871
766 int _outCloseCode; 872 int _outCloseCode;
767 String _outCloseReason; 873 String _outCloseReason;
768 Timer _closeTimer; 874 Timer _closeTimer;
875 _WebSocketPerMessageDeflate _deflate;
769 876
770 static final HttpClient _httpClient = new HttpClient(); 877 static final HttpClient _httpClient = new HttpClient();
771 878
772 static Future<WebSocket> connect( 879 static Future<WebSocket> connect(
773 String url, Iterable<String> protocols, Map<String, dynamic> headers) { 880 String url, Iterable<String> protocols, Map<String, dynamic> headers,
881 {CompressionOptions compression: CompressionOptions.DEFAULT}) {
774 Uri uri = Uri.parse(url); 882 Uri uri = Uri.parse(url);
775 if (uri.scheme != "ws" && uri.scheme != "wss") { 883 if (uri.scheme != "ws" && uri.scheme != "wss") {
776 throw new WebSocketException("Unsupported URL scheme '${uri.scheme}'"); 884 throw new WebSocketException("Unsupported URL scheme '${uri.scheme}'");
777 } 885 }
778 886
779 Random random = new Random(); 887 Random random = new Random();
780 // Generate 16 random bytes. 888 // Generate 16 random bytes.
781 Uint8List nonceData = new Uint8List(16); 889 Uint8List nonceData = new Uint8List(16);
782 for (int i = 0; i < 16; i++) { 890 for (int i = 0; i < 16; i++) {
783 nonceData[i] = random.nextInt(256); 891 nonceData[i] = random.nextInt(256);
(...skipping 18 matching lines...) Expand all
802 } 910 }
803 if (headers != null) { 911 if (headers != null) {
804 headers.forEach((field, value) => request.headers.add(field, value)); 912 headers.forEach((field, value) => request.headers.add(field, value));
805 } 913 }
806 // Setup the initial handshake. 914 // Setup the initial handshake.
807 request.headers 915 request.headers
808 ..set(HttpHeaders.CONNECTION, "Upgrade") 916 ..set(HttpHeaders.CONNECTION, "Upgrade")
809 ..set(HttpHeaders.UPGRADE, "websocket") 917 ..set(HttpHeaders.UPGRADE, "websocket")
810 ..set("Sec-WebSocket-Key", nonce) 918 ..set("Sec-WebSocket-Key", nonce)
811 ..set("Cache-Control", "no-cache") 919 ..set("Cache-Control", "no-cache")
812 ..set("Sec-WebSocket-Version", "13"); 920 ..set("Sec-WebSocket-Version", "13")
921 ..set("Sec-WebSocket-Extensions", "permessage-deflate");
Søren Gjesse 2015/06/25 15:41:27 Isn't this already handled by adding Sec-WebSocket
813 if (protocols != null) { 922 if (protocols != null) {
814 request.headers.add("Sec-WebSocket-Protocol", protocols.toList()); 923 request.headers.add("Sec-WebSocket-Protocol", protocols.toList());
815 } 924 }
925
926 request.headers.add("Sec-WebSocket-Extensions", compression._createHeade r());
927
816 return request.close(); 928 return request.close();
817 }) 929 })
818 .then((response) { 930 .then((response) {
819 void error(String message) { 931 void error(String message) {
820 // Flush data. 932 // Flush data.
821 response.detachSocket().then((socket) { 933 response.detachSocket().then((socket) {
822 socket.destroy(); 934 socket.destroy();
823 }); 935 });
824 throw new WebSocketException(message); 936 throw new WebSocketException(message);
825 } 937 }
(...skipping 15 matching lines...) Expand all
841 List<int> receivedAccept = _CryptoUtils.base64StringToBytes(accept); 953 List<int> receivedAccept = _CryptoUtils.base64StringToBytes(accept);
842 if (expectedAccept.length != receivedAccept.length) { 954 if (expectedAccept.length != receivedAccept.length) {
843 error("Reasponse header 'Sec-WebSocket-Accept' is the wrong length"); 955 error("Reasponse header 'Sec-WebSocket-Accept' is the wrong length");
844 } 956 }
845 for (int i = 0; i < expectedAccept.length; i++) { 957 for (int i = 0; i < expectedAccept.length; i++) {
846 if (expectedAccept[i] != receivedAccept[i]) { 958 if (expectedAccept[i] != receivedAccept[i]) {
847 error("Bad response 'Sec-WebSocket-Accept' header"); 959 error("Bad response 'Sec-WebSocket-Accept' header");
848 } 960 }
849 } 961 }
850 var protocol = response.headers.value('Sec-WebSocket-Protocol'); 962 var protocol = response.headers.value('Sec-WebSocket-Protocol');
963
Søren Gjesse 2015/06/25 15:41:27 Please extract this into a separate method called
964 String extensionHeader = response.headers.value('Sec-WebSocket-Extension s');
965
966 if (extensionHeader == null) {
967 extensionHeader = "";
968 }
969
970 Iterable<List<String>> extensions = extensionHeader
971 .split(", ")
972 .map((it) => it.split("; "));
973
974 _WebSocketPerMessageDeflate deflate;
975
976 if (compression.enabled && extensions.any((x) => x[0] == "permessage-def late")) {
977 var opts = extensions.firstWhere((x) => x[0] == "permessage-deflate");
978 var noContextTakeover = opts.contains("client_no_context_takeover");
979
980 int getWindowBits(String type) {
981 var o = opts.firstWhere((x) =>
982 x.startsWith("${type}_max_window_bits="), orElse: () => null);
983
984 if (o == null) {
985 return 15;
986 }
987
988 try {
989 o = o.substring("client_max_window_bits=".length);
Søren Gjesse 2015/06/25 15:41:27 client -> ${type} here as well? Use var paramete
990 o = int.parse(o);
991 } catch (e) {
992 return 15;
993 }
994
995 return o;
996 }
997
998 deflate = new _WebSocketPerMessageDeflate(
999 clientMaxWindowBits: getWindowBits("client"),
1000 serverMaxWindowBits: getWindowBits("server"),
1001 noContextTakeover: noContextTakeover);
1002 }
1003
851 return response.detachSocket() 1004 return response.detachSocket()
852 .then((socket) => new _WebSocketImpl._fromSocket(socket, protocol)); 1005 .then((socket) => new _WebSocketImpl._fromSocket(socket, protocol,
1006 compression, false, deflate));
853 }); 1007 });
854 } 1008 }
855 1009
856 _WebSocketImpl._fromSocket(this._socket, this.protocol, 1010 _WebSocketImpl._fromSocket(this._socket, this.protocol,
857 [this._serverSide = false]) { 1011 CompressionOptions compression, [this._serverSide = false,
1012 _WebSocketPerMessageDeflate deflate]) {
858 _consumer = new _WebSocketConsumer(this, _socket); 1013 _consumer = new _WebSocketConsumer(this, _socket);
859 _sink = new _StreamSinkImpl(_consumer); 1014 _sink = new _StreamSinkImpl(_consumer);
860 _readyState = WebSocket.OPEN; 1015 _readyState = WebSocket.OPEN;
1016 _deflate = deflate;
861 1017
862 var transformer = new _WebSocketProtocolTransformer(_serverSide); 1018 var transformer = new _WebSocketProtocolTransformer(_serverSide, _deflate);
863 _subscription = _socket.transform(transformer).listen( 1019 _subscription = _socket.transform(transformer).listen(
864 (data) { 1020 (data) {
865 if (data is _WebSocketPing) { 1021 if (data is _WebSocketPing) {
866 if (!_writeClosed) _consumer.add(new _WebSocketPong(data.payload)); 1022 if (!_writeClosed) _consumer.add(new _WebSocketPong(data.payload));
867 } else if (data is _WebSocketPong) { 1023 } else if (data is _WebSocketPong) {
868 // Simply set pingInterval, as it'll cancel any timers. 1024 // Simply set pingInterval, as it'll cancel any timers.
869 pingInterval = _pingInterval; 1025 pingInterval = _pingInterval;
870 } else { 1026 } else {
871 _controller.add(data); 1027 _controller.add(data);
872 } 1028 }
(...skipping 153 matching lines...) Expand 10 before | Expand all | Expand 10 after
1026 (code < WebSocketStatus.NORMAL_CLOSURE || 1182 (code < WebSocketStatus.NORMAL_CLOSURE ||
1027 code == WebSocketStatus.RESERVED_1004 || 1183 code == WebSocketStatus.RESERVED_1004 ||
1028 code == WebSocketStatus.NO_STATUS_RECEIVED || 1184 code == WebSocketStatus.NO_STATUS_RECEIVED ||
1029 code == WebSocketStatus.ABNORMAL_CLOSURE || 1185 code == WebSocketStatus.ABNORMAL_CLOSURE ||
1030 (code > WebSocketStatus.INTERNAL_SERVER_ERROR && 1186 (code > WebSocketStatus.INTERNAL_SERVER_ERROR &&
1031 code < WebSocketStatus.RESERVED_1015) || 1187 code < WebSocketStatus.RESERVED_1015) ||
1032 (code >= WebSocketStatus.RESERVED_1015 && 1188 (code >= WebSocketStatus.RESERVED_1015 &&
1033 code < 3000)); 1189 code < 3000));
1034 } 1190 }
1035 } 1191 }
OLDNEW
« sdk/lib/io/websocket.dart ('K') | « sdk/lib/io/websocket.dart ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698