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 // Matches _WebSocketOpcode. | 9 // Matches _WebSocketOpcode. |
| 10 class _WebSocketMessageType { | 10 class _WebSocketMessageType { |
| (...skipping 20 matching lines...) Expand all Loading... | |
| 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 Loading... | |
| 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 Loading... | |
| 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 Loading... | |
| 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 Loading... | |
| 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 Loading... | |
| 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 Loading... | |
| 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 Loading... | |
| 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 Loading... | |
| 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 } |
| OLD | NEW |