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

Side by Side Diff: runtime/bin/websocket_impl.dart

Issue 10262031: Add a web socket client (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 8 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 // Copyright (c) 2012, 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 final String _webSocketGUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
6
5 class _WebSocketMessageType { 7 class _WebSocketMessageType {
6 static final int NONE = 0; 8 static final int NONE = 0;
7 static final int BINARY = 1; 9 static final int BINARY = 1;
8 static final int TEXT = 2; 10 static final int TEXT = 2;
9 static final int CLOSE = 3; 11 static final int CLOSE = 3;
10 } 12 }
11 13
12 14
13 class _WebSocketOpcode { 15 class _WebSocketOpcode {
14 static final int CONTINUATION = 0; 16 static final int CONTINUATION = 0;
(...skipping 230 matching lines...) Expand 10 before | Expand all | Expand 10 after
245 } catch (var e) { 247 } catch (var e) {
246 _reportError(e); 248 _reportError(e);
247 } 249 }
248 } 250 }
249 251
250 /** 252 /**
251 * Indicate that the underlying communication channel has been closed. 253 * Indicate that the underlying communication channel has been closed.
252 */ 254 */
253 void closed() { 255 void closed() {
254 if (_state == START || _state == CLOSED || _state == FAILURE) return; 256 if (_state == START || _state == CLOSED || _state == FAILURE) return;
255 _reportError(new WebSocketException("Protocol error")); 257 _reportError(new WebSocketException("Protocol error $_state"));
256 _state = CLOSED; 258 _state = CLOSED;
257 } 259 }
258 260
259 void _lengthDone() { 261 void _lengthDone() {
260 if (_masked) { 262 if (_masked) {
261 _state = MASK; 263 _state = MASK;
262 _remainingMaskingKeyBytes = 4; 264 _remainingMaskingKeyBytes = 4;
263 } else { 265 } else {
264 _remainingPayloadBytes = _len; 266 _remainingPayloadBytes = _len;
265 _startPayload(); 267 _startPayload();
266 } 268 }
267 } 269 }
268 270
269 void _maskDone() { 271 void _maskDone() {
270 _remainingPayloadBytes = _len; 272 _remainingPayloadBytes = _len;
271 _startPayload(); 273 _startPayload();
272 } 274 }
273 275
274 void _startPayload() { 276 void _startPayload() {
275 // Check whether there is any payload. If not indicate empty 277 // Check whether there is any payload. If not indicate empty
276 // message or close without state and reason. 278 // message or close without state and reason.
277 if (_remainingPayloadBytes == 0) { 279 if (_remainingPayloadBytes == 0) {
278 if (_currentMessageType ==_WebSocketMessageType.CLOSE) { 280 if (_currentMessageType ==_WebSocketMessageType.CLOSE) {
279 if (onClosed != null) onClosed(null, null); 281 if (onClosed != null) onClosed(null, null);
282 _state = CLOSED;
280 } else { 283 } else {
281 _frameEnd(); 284 _frameEnd();
282 } 285 }
283 } else { 286 } else {
284 _state = PAYLOAD; 287 _state = PAYLOAD;
285 } 288 }
286 } 289 }
287 290
288 void _frameEnd() { 291 void _frameEnd() {
289 if (_remainingPayloadBytes != 0) { 292 if (_remainingPayloadBytes != 0) {
(...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after
335 List<int> _closePayload; 338 List<int> _closePayload;
336 339
337 Function onMessageStart; 340 Function onMessageStart;
338 Function onMessageData; 341 Function onMessageData;
339 Function onMessageEnd; 342 Function onMessageEnd;
340 Function onClosed; 343 Function onClosed;
341 Function onError; 344 Function onError;
342 } 345 }
343 346
344 347
345 class _WebSocketConnection implements WebSocketConnection { 348 class _WebSocketConnectionBase {
346 _WebSocketConnection(Socket this._socket) { 349 void _socketReady(Socket socket, List<int> data) {
350 assert(_socket == null);
351 _socket = socket;
347 _WebSocketProtocolProcessor processor = new _WebSocketProtocolProcessor(); 352 _WebSocketProtocolProcessor processor = new _WebSocketProtocolProcessor();
348 processor.onMessageStart = _onWebSocketMessageStart; 353 processor.onMessageStart = _onWebSocketMessageStart;
349 processor.onMessageData = _onWebSocketMessageData; 354 processor.onMessageData = _onWebSocketMessageData;
350 processor.onMessageEnd = _onWebSocketMessageEnd; 355 processor.onMessageEnd = _onWebSocketMessageEnd;
351 processor.onClosed = _onWebSocketClosed; 356 processor.onClosed = _onWebSocketClosed;
352 processor.onError = _onWebSocketError; 357 processor.onError = _onWebSocketError;
353 358 if (data != null) {
359 processor.update(data, 0, data.length);
360 }
354 _socket.onData = () { 361 _socket.onData = () {
355 int available = _socket.available(); 362 int available = _socket.available();
356 List<int> data = new List<int>(available); 363 List<int> data = new List<int>(available);
357 int read = _socket.readList(data, 0, available); 364 int read = _socket.readList(data, 0, available);
358 processor.update(data, 0, read); 365 processor.update(data, 0, read);
359 }; 366 };
360 _socket.onClosed = () { 367 _socket.onClosed = () {
361 processor.closed(); 368 processor.closed();
362 if (_closeSent) { 369 if (_closeSent) {
363 // Got socket close in response to close frame. Don't treat 370 // Got socket close in response to close frame. Don't treat
364 // that as an error. 371 // that as an error.
365 if (_closeTimer != null) _closeTimer.cancel(); 372 if (_closeTimer != null) _closeTimer.cancel();
366 } else { 373 } else {
367 if (_onError != null) { 374 _reportError(new WebSocketException("Unexpected close"));
368 _onError(new WebSocketException("Unexpected close"));
369 }
370 } 375 }
371 _socket.close(); 376 _socket.close();
372 }; 377 };
373 _socket.onError = (e) { 378 _socket.onError = (e) {
374 if (_onError != null) _onError(e); 379 _reportError(e);
375 _socket.close(); 380 _socket.close();
376 }; 381 };
377 } 382 }
378 383
379 void set onMessage(void callback(Object message)) { 384 void set onMessage(void callback(Object message)) {
380 _onMessage = callback; 385 _onMessage = callback;
381 } 386 }
382 387
383 void set onClosed(void callback(int status, String reason)) { 388 void set onClosed(void callback(int status, String reason)) {
384 _onClosed = callback; 389 _onClosed = callback;
(...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after
421 if (reason != null) { 426 if (reason != null) {
422 data.addAll( 427 data.addAll(
423 _StringEncoders.encoder(Encoding.UTF_8).encodeString(reason)); 428 _StringEncoders.encoder(Encoding.UTF_8).encodeString(reason));
424 } 429 }
425 } 430 }
426 _sendFrame(_WebSocketOpcode.CLOSE, data); 431 _sendFrame(_WebSocketOpcode.CLOSE, data);
427 432
428 if (_closeReceived) { 433 if (_closeReceived) {
429 // Close the socket when the close frame has been sent - if it 434 // Close the socket when the close frame has been sent - if it
430 // does not take too long. 435 // does not take too long.
436 _socket.close(true);
431 _socket.outputStream.onNoPendingWrites = () { 437 _socket.outputStream.onNoPendingWrites = () {
432 if (_closeTimer != null) _closeTimer.cancel(); 438 if (_closeTimer != null) _closeTimer.cancel();
433 _socket.close(); 439 _socket.close();
434 }; 440 };
435 _closeTimer = new Timer(5000, (t) { 441 _closeTimer = new Timer(5000, (t) {
436 _socket.close(); 442 _socket.close();
437 }); 443 });
438 } else { 444 } else {
439 // Half close the socket and expect a close frame in response 445 // Half close the socket and expect a close frame in response
440 // before closing the socket. If a close frame does not arrive 446 // before closing the socket. If a close frame does not arrive
(...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after
482 if (_closeSent) { 488 if (_closeSent) {
483 // Got close frame in response to close frame. Now close the socket. 489 // Got close frame in response to close frame. Now close the socket.
484 if (_closeTimer != null) _closeTimer.cancel(); 490 if (_closeTimer != null) _closeTimer.cancel();
485 _socket.close(); 491 _socket.close();
486 } else { 492 } else {
487 close(status); 493 close(status);
488 } 494 }
489 } 495 }
490 496
491 _onWebSocketError(e) { 497 _onWebSocketError(e) {
492 if (_onError != null) _onError(e); 498 _reportError(e);
493 _socket.close(); 499 _socket.close();
494 } 500 }
495 501
496 _sendFrame(int opcode, List<int> data) { 502 _sendFrame(int opcode, List<int> data) {
497 bool mask = false; // Masking not implemented for server. 503 bool mask = false; // Masking not implemented for server.
498 int dataLength = data == null ? 0 : data.length; 504 int dataLength = data == null ? 0 : data.length;
499 // Determine the header size. 505 // Determine the header size.
500 int headerSize = (mask) ? 6 : 2; 506 int headerSize = (mask) ? 6 : 2;
501 if (dataLength > 65535) { 507 if (dataLength > 65535) {
502 headerSize += 8; 508 headerSize += 8;
(...skipping 18 matching lines...) Expand all
521 for (int i = 0; i < lengthBytes; i++) { 527 for (int i = 0; i < lengthBytes; i++) {
522 header[index++] = dataLength >> (((lengthBytes - 1) - i) * 8) & 0xFF; 528 header[index++] = dataLength >> (((lengthBytes - 1) - i) * 8) & 0xFF;
523 } 529 }
524 assert(index == headerSize); 530 assert(index == headerSize);
525 _socket.outputStream.write(header); 531 _socket.outputStream.write(header);
526 if (data != null) { 532 if (data != null) {
527 _socket.outputStream.write(data); 533 _socket.outputStream.write(data);
528 } 534 }
529 } 535 }
530 536
537 void _reportError(e) {
538 if (_onError != null) {
539 _onError(e);
540 } else {
541 throw e;
542 }
543 }
544
531 Socket _socket; 545 Socket _socket;
532 Timer _closeTimer; 546 Timer _closeTimer;
533 547
534 Function _onMessage; 548 Function _onMessage;
535 Function _onClosed; 549 Function _onClosed;
536 Function _onError; 550 Function _onError;
537 551
538 int _currentMessageType = _WebSocketMessageType.NONE; 552 int _currentMessageType = _WebSocketMessageType.NONE;
539 _StringDecoder _decoder; 553 _StringDecoder _decoder;
540 ListOutputStream _outputStream; 554 ListOutputStream _outputStream;
541 bool _closeReceived = false; 555 bool _closeReceived = false;
542 bool _closeSent = false; 556 bool _closeSent = false;
543 } 557 }
544 558
545 559
560 class _WebSocketConnection extends _WebSocketConnectionBase implements WebSocket Connection {
Mads Ager (google) 2012/05/02 08:18:17 Long line. What is the reason to have both _WebSo
Søren Gjesse 2012/05/02 10:22:45 The WebSocketConnectionBase is also used as a base
561 _WebSocketConnection(Socket socket, List<int> data) {
562 _socketReady(socket, data);
563 }
564 }
565
566
546 class _WebSocketHandler implements WebSocketHandler { 567 class _WebSocketHandler implements WebSocketHandler {
547 void onRequest(HttpRequest request, HttpResponse response) { 568 void onRequest(HttpRequest request, HttpResponse response) {
548 // Check that this is a web socket upgrade. 569 // Check that this is a web socket upgrade.
549 if (!_isWebSocketUpgrade(request)) { 570 if (!_isWebSocketUpgrade(request)) {
550 response.statusCode = HttpStatus.BAD_REQUEST; 571 response.statusCode = HttpStatus.BAD_REQUEST;
572 response.outputStream.close();
551 return; 573 return;
552 } 574 }
553 575
554 // Send the upgrade response. 576 // Send the upgrade response.
555 response.statusCode = HttpStatus.SWITCHING_PROTOCOLS; 577 response.statusCode = HttpStatus.SWITCHING_PROTOCOLS;
556 response.headers.add(HttpHeaders.CONNECTION, "Upgrade"); 578 response.headers.add(HttpHeaders.CONNECTION, "Upgrade");
557 response.headers.add(HttpHeaders.UPGRADE, "websocket"); 579 response.headers.add(HttpHeaders.UPGRADE, "websocket");
558 String x = request.headers.value("Sec-WebSocket-Key"); 580 String key = request.headers.value("Sec-WebSocket-Key");
559 String y = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; 581 String accept =
560 String z = _Base64._encode(_Sha1._hash("$x$y".charCodes())); 582 _Base64._encode(_Sha1._hash("$key$_webSocketGUID".charCodes()));
561 response.headers.add("Sec-WebSocket-Accept", z); 583 response.headers.add("Sec-WebSocket-Accept", accept);
562 response.contentLength = 0; 584 response.contentLength = 0;
563 585
564 // Upgrade the connection and get the underlying socket. 586 // Upgrade the connection and get the underlying socket.
565 Socket socket = response.detachSocket(); 587 DetachedSocket detached = response.detachSocket();
566 WebSocketConnection conn = new _WebSocketConnection(socket); 588 WebSocketConnection conn =
589 new _WebSocketConnection(detached.socket, detached.unparsedData);
567 if (_onOpen != null) _onOpen(conn); 590 if (_onOpen != null) _onOpen(conn);
568 } 591 }
569 592
570 void set onOpen(callback(WebSocketConnection connection)) { 593 void set onOpen(callback(WebSocketConnection connection)) {
571 _onOpen = callback; 594 _onOpen = callback;
572 } 595 }
573 596
574 bool _isWebSocketUpgrade(HttpRequest request) { 597 bool _isWebSocketUpgrade(HttpRequest request) {
598 if (request.method != "GET") {
599 return false;
600 }
575 if (request.headers[HttpHeaders.CONNECTION] == null) { 601 if (request.headers[HttpHeaders.CONNECTION] == null) {
576 return false; 602 return false;
577 } 603 }
578 bool isUpgrade = false; 604 bool isUpgrade = false;
579 request.headers[HttpHeaders.CONNECTION].forEach((String value) { 605 request.headers[HttpHeaders.CONNECTION].forEach((String value) {
580 if (value.toLowerCase() == "upgrade") isUpgrade = true; 606 if (value.toLowerCase() == "upgrade") isUpgrade = true;
581 }); 607 });
582 if (!isUpgrade) return false; 608 if (!isUpgrade) return false;
583 String upgrade = request.headers.value(HttpHeaders.UPGRADE); 609 String upgrade = request.headers.value(HttpHeaders.UPGRADE);
584 if (upgrade == null || upgrade.toLowerCase() != "websocket") { 610 if (upgrade == null || upgrade.toLowerCase() != "websocket") {
585 return false; 611 return false;
586 } 612 }
587 String version = request.headers.value("Sec-WebSocket-Version"); 613 String version = request.headers.value("Sec-WebSocket-Version");
588 if (version == null || version != "13") { 614 if (version == null || version != "13") {
589 return false; 615 return false;
590 } 616 }
591 String key = request.headers.value("Sec-WebSocket-Key"); 617 String key = request.headers.value("Sec-WebSocket-Key");
592 if (key == null) { 618 if (key == null) {
593 return false; 619 return false;
594 } 620 }
595 return true; 621 return true;
596 } 622 }
597 623
598 Function _onOpen; 624 Function _onOpen;
599 } 625 }
626
627
628 class _WebSocketClientConnection extends _WebSocketConnectionBase implements Web SocketClientConnection {
Mads Ager (google) 2012/05/02 08:18:17 Long line.
Søren Gjesse 2012/05/02 10:22:45 Done.
629 _WebSocketClientConnection(HttpClientConnection this._conn,
630 [List<String> protocols]) {
631 _conn.onRequest = _onHttpClientRequest;
632 _conn.onResponse = _onHttpClientResponse;
633 _conn.onError = (e) => _reportError(e);
634 }
635
636 void set onRequest(void callback(HttpClientRequest request)) {
637 _onRequest = callback;
638 }
639
640 void set onOpen(void callback()) {
641 _onOpen = callback;
642 }
643
644 void set onNoUpgrade(void callback(HttpClientResponse request)) {
645 _onNoUpgrade = callback;
646 }
647
648 void _onHttpClientRequest(HttpClientRequest request) {
649 if (_onRequest != null) {
650 _onRequest(request);
651 }
652 // Setup the initial handshake.
653 request.headers.add(HttpHeaders.CONNECTION, "upgrade");
654 request.headers.set(HttpHeaders.UPGRADE, "websocket");
655 request.headers.set("Sec-WebSocket-Key", _generateNonce());
Anders Johnsen 2012/05/02 07:51:57 Even though it's not the case, this looks like the
Mads Ager (google) 2012/05/02 08:18:17 Or have a getter that calculates on first access a
Søren Gjesse 2012/05/02 10:22:45 Done.
Søren Gjesse 2012/05/02 10:22:45 Changed as Anders suggested.
656 request.headers.set("Sec-WebSocket-Version", "13");
657 request.contentLength = 0;
658 request.outputStream.close();
659 }
660
661 void _onHttpClientResponse(HttpClientResponse response) {
662 if (response.statusCode != HttpStatus.SWITCHING_PROTOCOLS) {
663 if (_onNoUpgrade != null) {
664 _onNoUpgrade(response);
665 } else {
666 _conn.detachSocket().socket.close();
667 throw new WebSocketException("Protocol upgrade refused");
668 }
669 return;
670 }
671
672 if (!_isWebSocketUpgrade(response)) {
673 _conn.detachSocket().socket.close();
674 throw new WebSocketException("Protocol upgrade failed");
675 return;
676 }
677
678 // Connection upgrade successful.
679 DetachedSocket detached = _conn.detachSocket();
680 _socketReady(detached.socket, detached.unparsedData);
Mads Ager (google) 2012/05/02 08:18:17 Instead of picking out the socket and the unparsed
Søren Gjesse 2012/05/02 10:22:45 Done.
681 if (_onOpen != null) _onOpen();
682 }
683
684 String _generateNonce() {
685 void intToBigEndianBytes(int value, List<int> bytes, int offset) {
686 bytes[offset] = (value >> 24) & 0xFF;
687 bytes[offset + 1] = (value >> 16) & 0xFF;
688 bytes[offset + 2] = (value >> 8) & 0xFF;
689 bytes[offset + 3] = value & 0xFF;
690 }
691
692 // Generate 16 random bytes.
693 List<int> nonce = new List<int>(16);
694 for (int i = 0; i < 4; i++) {
695 int r = (Math.random() * 0x100000000).toInt();
696 intToBigEndianBytes(r, nonce, i * 4);
697 }
698 _nonce = _Base64._encode(nonce);
699 return _nonce;
700 }
701
702 bool _isWebSocketUpgrade(HttpResponse response) {
703 if (response.headers[HttpHeaders.CONNECTION] == null) {
704 return false;
705 }
706 bool isUpgrade = false;
707 response.headers[HttpHeaders.CONNECTION].forEach((String value) {
708 if (value.toLowerCase() == "upgrade") isUpgrade = true;
709 });
710 if (!isUpgrade) return false;
711 String upgrade = response.headers.value(HttpHeaders.UPGRADE);
712 if (upgrade == null || upgrade.toLowerCase() != "websocket") {
713 return false;
714 }
715 String accept = response.headers.value("Sec-WebSocket-Accept");
716 if (accept == null) {
717 return false;
718 }
719 List<int> expectedAccept =
720 _Sha1._hash("$_nonce$_webSocketGUID".charCodes());
721 List<int> receivedAccept = _Base64._decode(accept);
722 if (expectedAccept.length != receivedAccept.length) return false;
723 for (int i = 0; i < expectedAccept.length; i++) {
724 if (expectedAccept[i] != receivedAccept[i]) return false;
725 }
726 return true;
727 }
728
729 Function _onRequest;
730 Function _onOpen;
731 Function _onNoUpgrade;
732 HttpClientConnection _conn;
733 String _nonce;
734 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698