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

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: Addressed review comments 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
« no previous file with comments | « runtime/bin/websocket.dart ('k') | tests/standalone/io/web_socket_test.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) 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(DetachedSocket detached) {
350 assert(detached.socket != null);
351 _socket = detached.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 (detached.unparsedData != null) {
359 processor.update(detached.unparsedData, 0, detached.unparsedData.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;
385 } 390 }
386 391
387 void set onError(void callback(e)) { 392 void set onError(void callback(e)) {
388 _onError = callback; 393 _onError = callback;
389 } 394 }
390 395
391 send(Object message) { 396 send(message) {
392 if (_closeSent) { 397 if (_closeSent) {
393 throw new WebSocketException("Connection closed"); 398 throw new WebSocketException("Connection closed");
394 } 399 }
395 List<int> data; 400 List<int> data;
396 int opcode; 401 int opcode;
397 if (message != null) { 402 if (message != null) {
398 if (message is String) { 403 if (message is String) {
399 opcode = _WebSocketOpcode.TEXT; 404 opcode = _WebSocketOpcode.TEXT;
400 data = _StringEncoders.encoder(Encoding.UTF_8).encodeString(message); 405 data = _StringEncoders.encoder(Encoding.UTF_8).encodeString(message);
401 } else { 406 } else {
(...skipping 19 matching lines...) Expand all
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
561 extends _WebSocketConnectionBase implements WebSocketConnection {
562 _WebSocketConnection(DetachedSocket detached) {
563 _socketReady(detached);
564 }
565 }
566
567
546 class _WebSocketHandler implements WebSocketHandler { 568 class _WebSocketHandler implements WebSocketHandler {
547 void onRequest(HttpRequest request, HttpResponse response) { 569 void onRequest(HttpRequest request, HttpResponse response) {
548 // Check that this is a web socket upgrade. 570 // Check that this is a web socket upgrade.
549 if (!_isWebSocketUpgrade(request)) { 571 if (!_isWebSocketUpgrade(request)) {
550 response.statusCode = HttpStatus.BAD_REQUEST; 572 response.statusCode = HttpStatus.BAD_REQUEST;
573 response.outputStream.close();
551 return; 574 return;
552 } 575 }
553 576
554 // Send the upgrade response. 577 // Send the upgrade response.
555 response.statusCode = HttpStatus.SWITCHING_PROTOCOLS; 578 response.statusCode = HttpStatus.SWITCHING_PROTOCOLS;
556 response.headers.add(HttpHeaders.CONNECTION, "Upgrade"); 579 response.headers.add(HttpHeaders.CONNECTION, "Upgrade");
557 response.headers.add(HttpHeaders.UPGRADE, "websocket"); 580 response.headers.add(HttpHeaders.UPGRADE, "websocket");
558 String x = request.headers.value("Sec-WebSocket-Key"); 581 String key = request.headers.value("Sec-WebSocket-Key");
559 String y = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; 582 String accept =
560 String z = _Base64._encode(_Sha1._hash("$x$y".charCodes())); 583 _Base64._encode(_Sha1._hash("$key$_webSocketGUID".charCodes()));
561 response.headers.add("Sec-WebSocket-Accept", z); 584 response.headers.add("Sec-WebSocket-Accept", accept);
562 response.contentLength = 0; 585 response.contentLength = 0;
563 586
564 // Upgrade the connection and get the underlying socket. 587 // Upgrade the connection and get the underlying socket.
565 Socket socket = response.detachSocket(); 588 WebSocketConnection conn =
566 WebSocketConnection conn = new _WebSocketConnection(socket); 589 new _WebSocketConnection(response.detachSocket());
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
629 extends _WebSocketConnectionBase implements WebSocketClientConnection {
630 _WebSocketClientConnection(HttpClientConnection this._conn,
631 [List<String> protocols]) {
632 _conn.onRequest = _onHttpClientRequest;
633 _conn.onResponse = _onHttpClientResponse;
634 _conn.onError = (e) => _reportError(e);
635 }
636
637 void set onRequest(void callback(HttpClientRequest request)) {
638 _onRequest = callback;
639 }
640
641 void set onOpen(void callback()) {
642 _onOpen = callback;
643 }
644
645 void set onNoUpgrade(void callback(HttpClientResponse request)) {
646 _onNoUpgrade = callback;
647 }
648
649 void _onHttpClientRequest(HttpClientRequest request) {
650 if (_onRequest != null) {
651 _onRequest(request);
652 }
653 // Setup the initial handshake.
654 _generateNonce();
655 request.headers.add(HttpHeaders.CONNECTION, "upgrade");
656 request.headers.set(HttpHeaders.UPGRADE, "websocket");
657 request.headers.set("Sec-WebSocket-Key", _nonce);
658 request.headers.set("Sec-WebSocket-Version", "13");
659 request.contentLength = 0;
660 request.outputStream.close();
661 }
662
663 void _onHttpClientResponse(HttpClientResponse response) {
664 if (response.statusCode != HttpStatus.SWITCHING_PROTOCOLS) {
665 if (_onNoUpgrade != null) {
666 _onNoUpgrade(response);
667 } else {
668 _conn.detachSocket().socket.close();
669 throw new WebSocketException("Protocol upgrade refused");
670 }
671 return;
672 }
673
674 if (!_isWebSocketUpgrade(response)) {
675 _conn.detachSocket().socket.close();
676 throw new WebSocketException("Protocol upgrade failed");
677 return;
678 }
679
680 // Connection upgrade successful.
681 _socketReady(_conn.detachSocket());
682 if (_onOpen != null) _onOpen();
683 }
684
685 void _generateNonce() {
686 assert(_nonce == null);
687 void intToBigEndianBytes(int value, List<int> bytes, int offset) {
688 bytes[offset] = (value >> 24) & 0xFF;
689 bytes[offset + 1] = (value >> 16) & 0xFF;
690 bytes[offset + 2] = (value >> 8) & 0xFF;
691 bytes[offset + 3] = value & 0xFF;
692 }
693
694 // Generate 16 random bytes.
695 List<int> nonce = new List<int>(16);
696 for (int i = 0; i < 4; i++) {
697 int r = (Math.random() * 0x100000000).toInt();
698 intToBigEndianBytes(r, nonce, i * 4);
699 }
700 _nonce = _Base64._encode(nonce);
701 }
702
703 bool _isWebSocketUpgrade(HttpClientResponse response) {
704 if (response.headers[HttpHeaders.CONNECTION] == null) {
705 return false;
706 }
707 bool isUpgrade = false;
708 response.headers[HttpHeaders.CONNECTION].forEach((String value) {
709 if (value.toLowerCase() == "upgrade") isUpgrade = true;
710 });
711 if (!isUpgrade) return false;
712 String upgrade = response.headers.value(HttpHeaders.UPGRADE);
713 if (upgrade == null || upgrade.toLowerCase() != "websocket") {
714 return false;
715 }
716 String accept = response.headers.value("Sec-WebSocket-Accept");
717 if (accept == null) {
718 return false;
719 }
720 List<int> expectedAccept =
721 _Sha1._hash("$_nonce$_webSocketGUID".charCodes());
722 List<int> receivedAccept = _Base64._decode(accept);
723 if (expectedAccept.length != receivedAccept.length) return false;
724 for (int i = 0; i < expectedAccept.length; i++) {
725 if (expectedAccept[i] != receivedAccept[i]) return false;
726 }
727 return true;
728 }
729
730 Function _onRequest;
731 Function _onOpen;
732 Function _onNoUpgrade;
733 HttpClientConnection _conn;
734 String _nonce;
735 }
OLDNEW
« no previous file with comments | « runtime/bin/websocket.dart ('k') | tests/standalone/io/web_socket_test.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698