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

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

Issue 12316036: Merge IO v2 branch to bleeding edge (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Rebased to r18818 Created 7 years, 10 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
« no previous file with comments | « sdk/lib/io/websocket.dart ('k') | sdk/lib/utf/utf.dart » ('j') | 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) 2012, 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;
11 static const int BINARY = 1; 11 static const int BINARY = 1;
(...skipping 23 matching lines...) Expand all
35 /** 35 /**
36 * The web socket protocol processor handles the protocol byte stream 36 * The web socket protocol processor handles the protocol byte stream
37 * which is supplied through the [:update:] and [:closed:] 37 * which is supplied through the [:update:] and [:closed:]
38 * methods. As the protocol is processed the following callbacks are 38 * methods. As the protocol is processed the following callbacks are
39 * called: 39 * called:
40 * 40 *
41 * [:onMessageStart:] 41 * [:onMessageStart:]
42 * [:onMessageData:] 42 * [:onMessageData:]
43 * [:onMessageEnd:] 43 * [:onMessageEnd:]
44 * [:onClosed:] 44 * [:onClosed:]
45 * [:onError:]
46 * 45 *
47 */ 46 */
48 class _WebSocketProtocolProcessor { 47 class _WebSocketProtocolProcessor {
49 static const int START = 0; 48 static const int START = 0;
50 static const int LEN_FIRST = 1; 49 static const int LEN_FIRST = 1;
51 static const int LEN_REST = 2; 50 static const int LEN_REST = 2;
52 static const int MASK = 3; 51 static const int MASK = 3;
53 static const int PAYLOAD = 4; 52 static const int PAYLOAD = 4;
54 static const int CLOSED = 5; 53 static const int CLOSED = 5;
55 static const int FAILURE = 6; 54 static const int FAILURE = 6;
56 55
57 _WebSocketProtocolProcessor() { 56 _WebSocketProtocolProcessor() {
58 _prepareForNextFrame(); 57 _prepareForNextFrame();
59 _currentMessageType = _WebSocketMessageType.NONE; 58 _currentMessageType = _WebSocketMessageType.NONE;
60 } 59 }
61 60
62 /** 61 /**
63 * Process data received from the underlying communication channel. 62 * Process data received from the underlying communication channel.
64 */ 63 */
65 void update(List<int> buffer) { 64 void update(List<int> buffer, int offset, int count) {
66 int index = 0; 65 int index = offset;
67 int lastIndex = buffer.length; 66 int lastIndex = offset + count;
68 try { 67 try {
69 if (_state == CLOSED) { 68 if (_state == CLOSED) {
70 throw new WebSocketException("Data on closed connection"); 69 throw new WebSocketException("Data on closed connection");
71 } 70 }
72 if (_state == FAILURE) { 71 if (_state == FAILURE) {
73 throw new WebSocketException("Data on failed connection"); 72 throw new WebSocketException("Data on failed connection");
74 } 73 }
75 while ((index < lastIndex) && _state != CLOSED && _state != FAILURE) { 74 while ((index < lastIndex) && _state != CLOSED && _state != FAILURE) {
76 int byte = buffer[index]; 75 int byte = buffer[index];
77 switch (_state) { 76 switch (_state) {
(...skipping 211 matching lines...) Expand 10 before | Expand all | Expand 10 after
289 String reason = ""; 288 String reason = "";
290 if (_controlPayload.length > 0) { 289 if (_controlPayload.length > 0) {
291 if (_controlPayload.length == 1) { 290 if (_controlPayload.length == 1) {
292 throw new WebSocketException("Protocol error"); 291 throw new WebSocketException("Protocol error");
293 } 292 }
294 status = _controlPayload[0] << 8 | _controlPayload[1]; 293 status = _controlPayload[0] << 8 | _controlPayload[1];
295 if (status == WebSocketStatus.NO_STATUS_RECEIVED) { 294 if (status == WebSocketStatus.NO_STATUS_RECEIVED) {
296 throw new WebSocketException("Protocol error"); 295 throw new WebSocketException("Protocol error");
297 } 296 }
298 if (_controlPayload.length > 2) { 297 if (_controlPayload.length > 2) {
299 var decoder = _StringDecoders.decoder(Encoding.UTF_8); 298 reason = _decodeString(
300 decoder.write(
301 _controlPayload.getRange(2, _controlPayload.length - 2)); 299 _controlPayload.getRange(2, _controlPayload.length - 2));
302 reason = decoder.decoded();
303 } 300 }
304 } 301 }
305 if (onClosed != null) onClosed(status, reason); 302 if (onClosed != null) onClosed(status, reason);
306 _state = CLOSED; 303 _state = CLOSED;
307 break; 304 break;
308 305
309 case _WebSocketOpcode.PING: 306 case _WebSocketOpcode.PING:
310 if (onPing != null) onPing(_controlPayload); 307 if (onPing != null) onPing(_controlPayload);
311 break; 308 break;
312 309
(...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after
353 350
354 Function onMessageStart; 351 Function onMessageStart;
355 Function onMessageData; 352 Function onMessageData;
356 Function onMessageEnd; 353 Function onMessageEnd;
357 Function onPing; 354 Function onPing;
358 Function onPong; 355 Function onPong;
359 Function onClosed; 356 Function onClosed;
360 } 357 }
361 358
362 359
363 class _WebSocketConnectionBase { 360 class _WebSocketTransformerImpl implements WebSocketTransformer {
364 void _socketConnected(Socket socket) { 361 final StreamController<WebSocket> _controller =
365 _socket = socket; 362 new StreamController<WebSocket>();
366 _socket.onError = (e) => _socket.close(); 363
367 } 364 Stream<WebSocket> bind(Stream<HttpRequest> stream) {
368 365 stream.listen((request) {
369 void _startProcessing(List<int> unparsedData) { 366 var response = request.response;
370 _WebSocketProtocolProcessor processor = new _WebSocketProtocolProcessor(); 367 if (!_isWebSocketUpgrade(request)) {
371 processor.onMessageStart = _onWebSocketMessageStart; 368 _controller.signalError(
372 processor.onMessageData = _onWebSocketMessageData; 369 new AsyncError(
373 processor.onMessageEnd = _onWebSocketMessageEnd; 370 new WebSocketException("Invalid WebSocket upgrade request")));
374 processor.onPing = _onWebSocketPing; 371 request.listen((_) {}, onDone: () {
375 processor.onPong = _onWebSocketPong; 372 response.statusCode = HttpStatus.BAD_REQUEST;
376 processor.onClosed = _onWebSocketClosed; 373 response.contentLength = 0;
377 if (unparsedData != null) { 374 response.close();
378 processor.update(unparsedData); 375 });
379 } 376 return;
380 _socket.onData = () { 377 }
381 processor.update(_socket.read()); 378 // Send the upgrade response.
382 }; 379 response.statusCode = HttpStatus.SWITCHING_PROTOCOLS;
383 _socket.onClosed = () { 380 response.headers.add(HttpHeaders.CONNECTION, "Upgrade");
384 processor.closed(); 381 response.headers.add(HttpHeaders.UPGRADE, "websocket");
385 if (_closeSent) { 382 String key = request.headers.value("Sec-WebSocket-Key");
386 // Got socket close in response to close frame. Don't treat 383 SHA1 sha1 = new SHA1();
387 // that as an error. 384 sha1.add("$key$_webSocketGUID".charCodes);
388 if (_closeTimer != null) _closeTimer.cancel(); 385 String accept = _Base64._encode(sha1.close());
389 } else { 386 response.headers.add("Sec-WebSocket-Accept", accept);
390 if (_onClosed != null) _onClosed(WebSocketStatus.ABNORMAL_CLOSURE, 387 response.headers.contentLength = 0;
391 "Unexpected close"); 388 response.detachSocket()
392 } 389 .then((socket) {
390 _controller.add(new _WebSocketImpl._fromSocket(socket));
391 }, onError: (error) {
392 _controller.signalError(error);
393 });
394 });
395
396 return _controller.stream;
397 }
398
399 bool _isWebSocketUpgrade(HttpRequest request) {
400 if (request.method != "GET") {
401 return false;
402 }
403 if (request.headers[HttpHeaders.CONNECTION] == null) {
404 return false;
405 }
406 bool isUpgrade = false;
407 request.headers[HttpHeaders.CONNECTION].forEach((String value) {
408 if (value.toLowerCase() == "upgrade") isUpgrade = true;
409 });
410 if (!isUpgrade) return false;
411 String upgrade = request.headers.value(HttpHeaders.UPGRADE);
412 if (upgrade == null || upgrade.toLowerCase() != "websocket") {
413 return false;
414 }
415 String version = request.headers.value("Sec-WebSocket-Version");
416 if (version == null || version != "13") {
417 return false;
418 }
419 String key = request.headers.value("Sec-WebSocket-Key");
420 if (key == null) {
421 return false;
422 }
423 return true;
424 }
425 }
426
427
428 class _WebSocketImpl extends Stream<Event> implements WebSocket {
429 final StreamController<Event> _controller = new StreamController<Event>();
430
431 final _WebSocketProtocolProcessor _processor =
432 new _WebSocketProtocolProcessor();
433
434 final Socket _socket;
435 int _readyState = WebSocket.CONNECTING;
436
437 static final HttpClient _httpClient = new HttpClient();
438
439 static Future<WebSocket> connect(String url, [protocols]) {
440 Uri uri = Uri.parse(url);
441 if (uri.scheme != "ws" && uri.scheme != "wss") {
442 throw new WebSocketException("Unsupported URL scheme '${uri.scheme}'");
443 }
444 if (uri.userInfo != "") {
445 throw new WebSocketException("Unsupported user info '${uri.userInfo}'");
446 }
447
448 Random random = new Random();
449 // Generate 16 random bytes.
450 List<int> nonceData = new List<int>.fixedLength(16);
451 for (int i = 0; i < 16; i++) {
452 nonceData[i] = random.nextInt(256);
453 }
454 String nonce = _Base64._encode(nonceData);
455
456 uri = new Uri.fromComponents(scheme: uri.scheme == "wss" ? "https" : "http",
457 userInfo: uri.userInfo,
458 domain: uri.domain,
459 port: uri.port,
460 path: uri.path,
461 query: uri.query,
462 fragment: uri.fragment);
463 return _httpClient.openUrl("GET", uri)
464 .then((request) {
465 // Setup the initial handshake.
466 request.headers.add(HttpHeaders.CONNECTION, "upgrade");
467 request.headers.set(HttpHeaders.UPGRADE, "websocket");
468 request.headers.set("Sec-WebSocket-Key", nonce);
469 request.headers.set("Sec-WebSocket-Version", "13");
470 return request.close();
471 })
472 .then((response) {
473 void error(String message) {
474 // Flush data.
475 response.detachSocket().then((socket) {
476 socket.destroy();
477 });
478 throw new WebSocketException(message);
479 }
480 if (response.statusCode != HttpStatus.SWITCHING_PROTOCOLS ||
481 response.headers[HttpHeaders.CONNECTION] == null ||
482 !response.headers[HttpHeaders.CONNECTION].any(
483 (value) => value.toLowerCase() == "upgrade") ||
484 response.headers.value(HttpHeaders.UPGRADE).toLowerCase() !=
485 "websocket") {
486 error("Connection to '$uri' was not upgraded to websocket");
487 }
488 String accept = response.headers.value("Sec-WebSocket-Accept");
489 if (accept == null) {
490 error("Response did not contain a 'Sec-WebSocket-Accept' header");
491 }
492 SHA1 sha1 = new SHA1();
493 sha1.add("$nonce$_webSocketGUID".charCodes);
494 List<int> expectedAccept = sha1.close();
495 List<int> receivedAccept = _Base64._decode(accept);
496 if (expectedAccept.length != receivedAccept.length) {
497 error("Reasponse header 'Sec-WebSocket-Accept' is the wrong length");
498 }
499 for (int i = 0; i < expectedAccept.length; i++) {
500 if (expectedAccept[i] != receivedAccept[i]) {
501 error("Bad response 'Sec-WebSocket-Accept' header");
502 }
503 }
504 return response.detachSocket()
505 .then((socket) => new _WebSocketImpl._fromSocket(socket));
506 });
507 }
508
509 _WebSocketImpl._fromSocket(Socket this._socket) {
510 _readyState = WebSocket.OPEN;
511
512 int type;
513 var data;
514 _processor.onMessageStart = (int t) {
515 type = t;
516 if (type == _WebSocketMessageType.TEXT) {
517 data = new StringBuffer();
518 } else {
519 data = [];
520 }
521 };
522 _processor.onMessageData = (buffer, offset, count) {
523 if (type == _WebSocketMessageType.TEXT) {
524 data.add(_decodeString(buffer.getRange(offset, count)));
525 } else {
526 data.addAll(buffer.getRange(offset, count));
527 }
528 };
529 _processor.onMessageEnd = () {
530 if (type == _WebSocketMessageType.TEXT) {
531 _controller.add(new _WebSocketMessageEvent(data.toString()));
532 } else {
533 _controller.add(new _WebSocketMessageEvent(data));
534 }
535 };
536 _processor.onClosed = (code, reason) {
537 bool clean = true;
538 if (_readyState == WebSocket.OPEN) {
539 _readyState = WebSocket.CLOSING;
540 if (code != WebSocketStatus.NO_STATUS_RECEIVED) {
541 _close(code);
542 } else {
543 _close();
544 clean = false;
545 }
546 _readyState = WebSocket.CLOSED;
547 }
548 _controller.add(new _WebSocketCloseEvent(clean, code, reason));
549 _controller.close();
550 };
551
552 _socket.listen(
553 (data) => _processor.update(data, 0, data.length),
554 onDone: () => _processor.closed(),
555 onError: (error) => _controller.signalError(error));
556 }
557
558 StreamSubscription<Event> listen(void onData(Event event),
559 {void onError(AsyncError error),
560 void onDone(),
561 bool unsubscribeOnError}) {
562 return _controller.stream.listen(onData,
563 onError: onError,
564 onDone: onDone,
565 unsubscribeOnError: unsubscribeOnError);
566 }
567
568 int get readyState => _readyState;
569 int get bufferedAmount => 0;
570
571 String get extensions => null;
572 String get protocol => null;
573
574 void close([int code, String reason]) {
575 if (_readyState < WebSocket.CLOSING) _readyState = WebSocket.CLOSING;
576 if (code == WebSocketStatus.RESERVED_1004 ||
577 code == WebSocketStatus.NO_STATUS_RECEIVED ||
578 code == WebSocketStatus.RESERVED_1015) {
579 throw new WebSocketException("Reserved status code $code");
580 }
581 _close(code, reason);
582 }
583
584 void _close([int code, String reason]) {
585 List<int> data;
586 if (code != null) {
587 data = new List<int>();
588 data.add((code >> 8) & 0xFF);
589 data.add(code & 0xFF);
590 if (reason != null) {
591 data.addAll(_encodeString(reason));
592 }
593 }
594 _sendFrame(_WebSocketOpcode.CLOSE, data);
595
596 if (_readyState == WebSocket.CLOSED) {
597 // Close the socket when the close frame has been sent - if it
598 // does not take too long.
599 // TODO(ajohnsen): Honor comment.
600 _socket.destroy();
601 } else {
602 // Half close the socket and expect a close frame in response
603 // before closing the socket. If a close frame does not arrive
604 // within a reasonable amount of time just close the socket.
605 // TODO(ajohnsen): Honor comment.
393 _socket.close(); 606 _socket.close();
394 }; 607 }
395 } 608 }
396 609
397 void set onMessage(void callback(Object message)) { 610 void send(message) {
398 _onMessage = callback; 611 if (readyState != WebSocket.OPEN) {
399 } 612 throw new StateError("Connection not open");
400
401 void set onClosed(void callback(int status, String reason)) {
402 _onClosed = callback;
403 }
404
405 send(message) {
406 if (_closeSent) {
407 throw new WebSocketException("Connection closed");
408 } 613 }
409 List<int> data; 614 List<int> data;
410 int opcode; 615 int opcode;
411 if (message != null) { 616 if (message != null) {
412 if (message is String) { 617 if (message is String) {
413 opcode = _WebSocketOpcode.TEXT; 618 opcode = _WebSocketOpcode.TEXT;
414 data = _StringEncoders.encoder(Encoding.UTF_8).encodeString(message); 619 data = _encodeString(message);
415 } else { 620 } else {
416 if (message is !List<int>) { 621 if (message is !List<int>) {
417 throw new ArgumentError(message); 622 throw new ArgumentError(message);
418 } 623 }
419 opcode = _WebSocketOpcode.BINARY; 624 opcode = _WebSocketOpcode.BINARY;
420 data = message; 625 data = message;
421 } 626 }
422 } else { 627 } else {
423 opcode = _WebSocketOpcode.TEXT; 628 opcode = _WebSocketOpcode.TEXT;
424 } 629 }
425 _sendFrame(opcode, data); 630 _sendFrame(opcode, data);
426 } 631 }
427 632
428 close([int status, String reason]) { 633 void _sendFrame(int opcode, [List<int> data]) {
429 if (status == WebSocketStatus.RESERVED_1004 ||
430 status == WebSocketStatus.NO_STATUS_RECEIVED ||
431 status == WebSocketStatus.RESERVED_1015) {
432 throw new WebSocketException("Reserved status code $status");
433 }
434
435 if (_closeSent) return;
436 List<int> data;
437 if (status != null) {
438 data = new List<int>();
439 data.add((status >> 8) & 0xFF);
440 data.add(status & 0xFF);
441 if (reason != null) {
442 data.addAll(
443 _StringEncoders.encoder(Encoding.UTF_8).encodeString(reason));
444 }
445 }
446 _sendFrame(_WebSocketOpcode.CLOSE, data);
447
448 if (_closeReceived) {
449 // Close the socket when the close frame has been sent - if it
450 // does not take too long.
451 _socket.outputStream.close();
452 _socket.outputStream.onClosed = () {
453 if (_closeTimer != null) _closeTimer.cancel();
454 _socket.close();
455 };
456 _closeTimer = new Timer(const Duration(seconds: 5), _socket.close);
457 } else {
458 // Half close the socket and expect a close frame in response
459 // before closing the socket. If a close frame does not arrive
460 // within a reasonable amount of time just close the socket.
461 _socket.outputStream.close();
462 _closeTimer = new Timer(const Duration(seconds: 5), _socket.close);
463 }
464 _closeSent = true;
465 }
466
467 int get hashCode => _hash;
468
469 _onWebSocketMessageStart(int type) {
470 _currentMessageType = type;
471 if (_currentMessageType == _WebSocketMessageType.TEXT) {
472 _decoder = _StringDecoders.decoder(Encoding.UTF_8);
473 } else {
474 _outputStream = new ListOutputStream();
475 }
476 }
477
478 _onWebSocketMessageData(List<int> buffer, int offset, int count) {
479 if (_currentMessageType == _WebSocketMessageType.TEXT) {
480 _decoder.write(buffer.getRange(offset, count));
481 } else {
482 _outputStream.write(buffer.getRange(offset, count));
483 }
484 }
485
486 _onWebSocketMessageEnd() {
487 if (_onMessage != null) {
488 if (_currentMessageType == _WebSocketMessageType.TEXT) {
489 _onMessage(_decoder.decoded());
490 } else {
491 _onMessage(_outputStream.read());
492 }
493 }
494 _decoder = null;
495 _outputStream = null;
496 }
497
498 _onWebSocketPing(List<int> payload) {
499 _sendFrame(_WebSocketOpcode.PONG, payload);
500 }
501
502 _onWebSocketPong(List<int> payload) {
503 // Currently pong messages are ignored.
504 }
505
506 _onWebSocketClosed(int status, String reason) {
507 _closeReceived = true;
508 if (_onClosed != null) _onClosed(status, reason);
509 if (_closeSent) {
510 // Got close frame in response to close frame. Now close the socket.
511 if (_closeTimer != null) _closeTimer.cancel();
512 _socket.close();
513 } else {
514 if (status != WebSocketStatus.NO_STATUS_RECEIVED) {
515 close(status);
516 } else {
517 close();
518 }
519 }
520 }
521
522 _sendFrame(int opcode, [List<int> data]) {
523 bool mask = false; // Masking not implemented for server. 634 bool mask = false; // Masking not implemented for server.
524 int dataLength = data == null ? 0 : data.length; 635 int dataLength = data == null ? 0 : data.length;
525 // Determine the header size. 636 // Determine the header size.
526 int headerSize = (mask) ? 6 : 2; 637 int headerSize = (mask) ? 6 : 2;
527 if (dataLength > 65535) { 638 if (dataLength > 65535) {
528 headerSize += 8; 639 headerSize += 8;
529 } else if (dataLength > 125) { 640 } else if (dataLength > 125) {
530 headerSize += 2; 641 headerSize += 2;
531 } 642 }
532 List<int> header = new List<int>.fixedLength(headerSize); 643 List<int> header = new List<int>.fixedLength(headerSize);
533 int index = 0; 644 int index = 0;
534 // Set FIN and opcode. 645 // Set FIN and opcode.
535 header[index++] = 0x80 | opcode; 646 header[index++] = 0x80 | opcode;
536 // Determine size and position of length field. 647 // Determine size and position of length field.
537 int lengthBytes = 1; 648 int lengthBytes = 1;
538 int firstLengthByte = 1; 649 int firstLengthByte = 1;
539 if (dataLength > 65535) { 650 if (dataLength > 65535) {
540 header[index++] = 127; 651 header[index++] = 127;
541 lengthBytes = 8; 652 lengthBytes = 8;
542 } else if (dataLength > 125) { 653 } else if (dataLength > 125) {
543 header[index++] = 126; 654 header[index++] = 126;
544 lengthBytes = 2; 655 lengthBytes = 2;
545 } 656 }
546 // Write the length in network byte order into the header. 657 // Write the length in network byte order into the header.
547 for (int i = 0; i < lengthBytes; i++) { 658 for (int i = 0; i < lengthBytes; i++) {
548 header[index++] = dataLength >> (((lengthBytes - 1) - i) * 8) & 0xFF; 659 header[index++] = dataLength >> (((lengthBytes - 1) - i) * 8) & 0xFF;
549 } 660 }
550 assert(index == headerSize); 661 assert(index == headerSize);
551 _socket.outputStream.write(header); 662 _socket.add(header);
552 if (data != null) { 663 if (data != null) {
553 _socket.outputStream.write(data); 664 _socket.add(data);
554 } 665 }
555 } 666 }
556
557 Socket _socket;
558 Timer _closeTimer;
559 int _hash;
560
561 Function _onMessage;
562 Function _onClosed;
563
564 int _currentMessageType = _WebSocketMessageType.NONE;
565 _StringDecoder _decoder;
566 ListOutputStream _outputStream;
567 bool _closeReceived = false;
568 bool _closeSent = false;
569 }
570
571
572 class _WebSocketConnection
573 extends _WebSocketConnectionBase implements WebSocketConnection {
574 _WebSocketConnection(DetachedSocket detached) {
575 _hash = detached.socket.hashCode;
576 _socketConnected(detached.socket);
577 _startProcessing(detached.unparsedData);
578 }
579 } 667 }
580 668
581 669
582 class _WebSocketHandler implements WebSocketHandler {
583 void onRequest(HttpRequest request, HttpResponse response) {
584 // Check that this is a web socket upgrade.
585 if (!_isWebSocketUpgrade(request)) {
586 response.statusCode = HttpStatus.BAD_REQUEST;
587 response.outputStream.close();
588 return;
589 }
590
591 // Send the upgrade response.
592 response.statusCode = HttpStatus.SWITCHING_PROTOCOLS;
593 response.headers.add(HttpHeaders.CONNECTION, "Upgrade");
594 response.headers.add(HttpHeaders.UPGRADE, "websocket");
595 String key = request.headers.value("Sec-WebSocket-Key");
596 SHA1 sha1 = new SHA1();
597 sha1.add("$key$_webSocketGUID".charCodes);
598 String accept = _Base64._encode(sha1.close());
599 response.headers.add("Sec-WebSocket-Accept", accept);
600 response.contentLength = 0;
601
602 // Upgrade the connection and get the underlying socket.
603 WebSocketConnection conn =
604 new _WebSocketConnection(response.detachSocket());
605 if (_onOpen != null) _onOpen(conn);
606 }
607
608 void set onOpen(callback(WebSocketConnection connection)) {
609 _onOpen = callback;
610 }
611
612 bool _isWebSocketUpgrade(HttpRequest request) {
613 if (request.method != "GET") {
614 return false;
615 }
616 if (request.headers[HttpHeaders.CONNECTION] == null) {
617 return false;
618 }
619 bool isUpgrade = false;
620 request.headers[HttpHeaders.CONNECTION].forEach((String value) {
621 if (value.toLowerCase() == "upgrade") isUpgrade = true;
622 });
623 if (!isUpgrade) return false;
624 String upgrade = request.headers.value(HttpHeaders.UPGRADE);
625 if (upgrade == null || upgrade.toLowerCase() != "websocket") {
626 return false;
627 }
628 String version = request.headers.value("Sec-WebSocket-Version");
629 if (version == null || version != "13") {
630 return false;
631 }
632 String key = request.headers.value("Sec-WebSocket-Key");
633 if (key == null) {
634 return false;
635 }
636 return true;
637 }
638
639 Function _onOpen;
640 }
641
642
643 class _WebSocketClientConnection
644 extends _WebSocketConnectionBase implements WebSocketClientConnection {
645 _WebSocketClientConnection(HttpClientConnection this._conn,
646 [List<String> protocols]) {
647 _conn.onRequest = _onHttpClientRequest;
648 _conn.onResponse = _onHttpClientResponse;
649 _conn.onError = (e) {
650 if (_onClosed != null) {
651 _onClosed(WebSocketStatus.ABNORMAL_CLOSURE, "$e");
652 }
653 };
654
655 // Generate the nonce now as it is also used to set the hash code.
656 _generateNonceAndHash();
657 }
658
659 void set onRequest(void callback(HttpClientRequest request)) {
660 _onRequest = callback;
661 }
662
663 void set onOpen(void callback()) {
664 _onOpen = callback;
665 }
666
667 void set onNoUpgrade(void callback(HttpClientResponse request)) {
668 _onNoUpgrade = callback;
669 }
670
671 void _onHttpClientRequest(HttpClientRequest request) {
672 if (_onRequest != null) {
673 _onRequest(request);
674 }
675 // Setup the initial handshake.
676 request.headers.add(HttpHeaders.CONNECTION, "upgrade");
677 request.headers.set(HttpHeaders.UPGRADE, "websocket");
678 request.headers.set("Sec-WebSocket-Key", _nonce);
679 request.headers.set("Sec-WebSocket-Version", "13");
680 request.contentLength = 0;
681 request.outputStream.close();
682 }
683
684 void _onHttpClientResponse(HttpClientResponse response) {
685 if (response.statusCode != HttpStatus.SWITCHING_PROTOCOLS) {
686 if (_onNoUpgrade != null) {
687 _onNoUpgrade(response);
688 } else {
689 _conn.detachSocket().socket.close();
690 throw new WebSocketException("Protocol upgrade refused");
691 }
692 return;
693 }
694
695 if (!_isWebSocketUpgrade(response)) {
696 _conn.detachSocket().socket.close();
697 throw new WebSocketException("Protocol upgrade failed");
698 }
699
700 // Connection upgrade successful.
701 DetachedSocket detached = _conn.detachSocket();
702 _socketConnected(detached.socket);
703 if (_onOpen != null) _onOpen();
704 _startProcessing(detached.unparsedData);
705 }
706
707 void _generateNonceAndHash() {
708 Random random = new Random();
709 assert(_nonce == null);
710 void intToBigEndianBytes(int value, List<int> bytes, int offset) {
711 bytes[offset] = (value >> 24) & 0xFF;
712 bytes[offset + 1] = (value >> 16) & 0xFF;
713 bytes[offset + 2] = (value >> 8) & 0xFF;
714 bytes[offset + 3] = value & 0xFF;
715 }
716
717 // Generate 16 random bytes. Use the last four bytes for the hash code.
718 List<int> nonce = new List<int>.fixedLength(16);
719 for (int i = 0; i < 4; i++) {
720 int r = random.nextInt(0x100000000);
721 intToBigEndianBytes(r, nonce, i * 4);
722 }
723 _nonce = _Base64._encode(nonce);
724 _hash = random.nextInt(0x100000000);
725 }
726
727 bool _isWebSocketUpgrade(HttpClientResponse response) {
728 if (response.headers[HttpHeaders.CONNECTION] == null) {
729 return false;
730 }
731 bool isUpgrade = false;
732 response.headers[HttpHeaders.CONNECTION].forEach((String value) {
733 if (value.toLowerCase() == "upgrade") isUpgrade = true;
734 });
735 if (!isUpgrade) return false;
736 String upgrade = response.headers.value(HttpHeaders.UPGRADE);
737 if (upgrade == null || upgrade.toLowerCase() != "websocket") {
738 return false;
739 }
740 String accept = response.headers.value("Sec-WebSocket-Accept");
741 if (accept == null) {
742 return false;
743 }
744 SHA1 sha1 = new SHA1();
745 sha1.add("$_nonce$_webSocketGUID".charCodes);
746 List<int> expectedAccept = sha1.close();
747 List<int> receivedAccept = _Base64._decode(accept);
748 if (expectedAccept.length != receivedAccept.length) return false;
749 for (int i = 0; i < expectedAccept.length; i++) {
750 if (expectedAccept[i] != receivedAccept[i]) return false;
751 }
752 return true;
753 }
754
755 Function _onRequest;
756 Function _onOpen;
757 Function _onNoUpgrade;
758 HttpClientConnection _conn;
759 String _nonce;
760 }
761
762
763 class _WebSocket implements WebSocket {
764 _WebSocket(String url, [protocols]) {
765 Uri uri = Uri.parse(url);
766 if (uri.scheme != "ws" && uri.scheme != "wss") {
767 throw new WebSocketException("Unsupported URL scheme ${uri.scheme}");
768 }
769 if (uri.userInfo != "") {
770 throw new WebSocketException("Unsupported user info ${uri.userInfo}");
771 }
772 int port = uri.port == 0 ? HttpClient.DEFAULT_HTTP_PORT : uri.port;
773 String path = uri.path;
774 if (path.length == 0) path = "/";
775 if (uri.query != "") {
776 if (uri.fragment != "") {
777 path = "${path}?${uri.query}#${uri.fragment}";
778 } else {
779 path = "${path}?${uri.query}";
780 }
781 }
782
783 HttpClient client = new HttpClient();
784 bool secure = (uri.scheme == 'wss');
785 HttpClientConnection conn = client.openUrl("GET",
786 new Uri.fromComponents(scheme: secure ? "https" : "http",
787 domain: uri.domain,
788 port: port,
789 path: path));
790 if (protocols is String) protocols = [protocols];
791 _wsconn = new WebSocketClientConnection(conn, protocols);
792 _wsconn.onOpen = () {
793 // HTTP client not needed after socket have been detached.
794 client.shutdown();
795 client = null;
796 _readyState = WebSocket.OPEN;
797 if (_onopen != null) _onopen();
798 };
799 _wsconn.onMessage = (message) {
800 if (_onmessage != null) {
801 _onmessage(new _WebSocketMessageEvent(message));
802 }
803 };
804 _wsconn.onClosed = (status, reason) {
805 _readyState = WebSocket.CLOSED;
806 if (_onclose != null) {
807 _onclose(new _WebSocketCloseEvent(true, status, reason));
808 }
809 };
810 _wsconn.onNoUpgrade = (response) {
811 if (_onclose != null) {
812 _onclose(
813 new _WebSocketCloseEvent(true,
814 WebSocketStatus.ABNORMAL_CLOSURE,
815 "Connection not upgraded"));
816 }
817 };
818 }
819
820 int get readyState => _readyState;
821 int get bufferedAmount => 0;
822
823 void set onopen(Function callback) {
824 _onopen = callback;
825 }
826
827 void set onerror(Function callback) {}
828
829 void set onclose(Function callback) {
830 _onclose = callback;
831 }
832
833 String get extensions => null;
834 String get protocol => null;
835
836 void close(int code, String reason) {
837 if (_readyState < WebSocket.CLOSING) _readyState = WebSocket.CLOSING;
838 _wsconn.close(code, reason);
839 }
840
841 void set onmessage(Function callback) {
842 _onmessage = callback;
843 }
844
845 void send(data) {
846 _wsconn.send(data);
847 }
848
849 WebSocketClientConnection _wsconn;
850 int _readyState = WebSocket.CONNECTING;
851 Function _onopen;
852 Function _onclose;
853 Function _onmessage;
854 }
855
856
857 class _WebSocketMessageEvent implements MessageEvent { 670 class _WebSocketMessageEvent implements MessageEvent {
858 _WebSocketMessageEvent(this._data); 671 _WebSocketMessageEvent(this._data);
859 get data => _data; 672 get data => _data;
860 var _data; 673 var _data;
861 } 674 }
862 675
863 676
864 class _WebSocketCloseEvent implements CloseEvent { 677 class _WebSocketCloseEvent implements CloseEvent {
865 _WebSocketCloseEvent(this._wasClean, this._code, this._reason); 678 _WebSocketCloseEvent(this._wasClean, this._code, this._reason);
866 bool get wasClean => _wasClean; 679 bool get wasClean => _wasClean;
867 int get code => _code; 680 int get code => _code;
868 String get reason => _reason; 681 String get reason => _reason;
869 bool _wasClean; 682 bool _wasClean;
870 int _code; 683 int _code;
871 String _reason; 684 String _reason;
872 } 685 }
OLDNEW
« no previous file with comments | « sdk/lib/io/websocket.dart ('k') | sdk/lib/utf/utf.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698