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

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

Issue 9589001: Handle closing of HTTP client and server (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Addressed review comments Created 8 years, 9 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 | « no previous file | runtime/bin/socket.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 // Global constants. 5 // Global constants.
6 class _Const { 6 class _Const {
7 // Bytes for "HTTP/1.0". 7 // Bytes for "HTTP/1.0".
8 static final HTTP10 = const [72, 84, 84, 80, 47, 49, 46, 48]; 8 static final HTTP10 = const [72, 84, 84, 80, 47, 49, 46, 48];
9 // Bytes for "HTTP/1.1". 9 // Bytes for "HTTP/1.1".
10 static final HTTP11 = const [72, 84, 84, 80, 47, 49, 46, 49]; 10 static final HTTP11 = const [72, 84, 84, 80, 47, 49, 46, 49];
(...skipping 670 matching lines...) Expand 10 before | Expand all | Expand 10 after
681 _keepAlive = keepAlive; 681 _keepAlive = keepAlive;
682 } 682 }
683 683
684 int get statusCode() => _statusCode; 684 int get statusCode() => _statusCode;
685 void set statusCode(int statusCode) { 685 void set statusCode(int statusCode) {
686 if (_outputStream != null) return new HttpException("Header already sent"); 686 if (_outputStream != null) return new HttpException("Header already sent");
687 _statusCode = statusCode; 687 _statusCode = statusCode;
688 } 688 }
689 689
690 String get reasonPhrase() => _findReasonPhrase(_statusCode); 690 String get reasonPhrase() => _findReasonPhrase(_statusCode);
691 void set reasonPhrase(String reasonPhrase) => _reasonPhrase = reasonPhrase; 691 void set reasonPhrase(String reasonPhrase) {
692 if (_outputStream != null) return new HttpException("Header already sent");
693 _reasonPhrase = reasonPhrase;
694 }
692 695
693 // Set a header on the response. NOTE: If the same header is set 696 // Set a header on the response. NOTE: If the same header is set
694 // more than once only the last one will be part of the response. 697 // more than once only the last one will be part of the response.
695 void setHeader(String name, String value) { 698 void setHeader(String name, String value) {
696 if (_outputStream != null) return new HttpException("Header already sent"); 699 if (_outputStream != null) return new HttpException("Header already sent");
697 _setHeader(name, value); 700 _setHeader(name, value);
698 } 701 }
699 702
700 OutputStream get outputStream() { 703 OutputStream get outputStream() {
701 if (_state == DONE) throw new HttpException("Response closed"); 704 if (_state == DONE) throw new HttpException("Response closed");
(...skipping 16 matching lines...) Expand all
718 // Delegate functions for the HttpOutputStream implementation. 721 // Delegate functions for the HttpOutputStream implementation.
719 bool _streamWrite(List<int> buffer, bool copyBuffer) { 722 bool _streamWrite(List<int> buffer, bool copyBuffer) {
720 return _write(buffer, copyBuffer); 723 return _write(buffer, copyBuffer);
721 } 724 }
722 725
723 bool _streamWriteFrom(List<int> buffer, int offset, int len) { 726 bool _streamWriteFrom(List<int> buffer, int offset, int len) {
724 return _writeList(buffer, offset, len); 727 return _writeList(buffer, offset, len);
725 } 728 }
726 729
727 void _streamClose() { 730 void _streamClose() {
731 _httpConnection._phase = _HttpConnectionBase.PHASE_IDLE;
728 _state = DONE; 732 _state = DONE;
729 // Stop tracking no pending write events. 733 // Stop tracking no pending write events.
730 _httpConnection.outputStream.onNoPendingWrites = null; 734 _httpConnection.outputStream.onNoPendingWrites = null;
731 // Ensure that any trailing data is written. 735 // Ensure that any trailing data is written.
732 _writeDone(); 736 _writeDone();
733 // If the connection is closing then close the output stream to 737 // If the connection is closing then close the output stream to
734 // fully close the socket. 738 // fully close the socket.
735 if (_httpConnection._closing) { 739 if (_httpConnection._closing) {
736 _httpConnection.outputStream.close(); 740 _httpConnection.outputStream.close();
737 } 741 }
(...skipping 172 matching lines...) Expand 10 before | Expand all | Expand 10 after
910 } 914 }
911 915
912 void set onError(void callback()) { 916 void set onError(void callback()) {
913 _requestOrResponse._streamSetErrorHandler(callback); 917 _requestOrResponse._streamSetErrorHandler(callback);
914 } 918 }
915 919
916 _HttpRequestResponseBase _requestOrResponse; 920 _HttpRequestResponseBase _requestOrResponse;
917 } 921 }
918 922
919 923
920 class _HttpConnectionBase { 924 class _HttpConnectionBase implements Hashable {
921 _HttpConnectionBase() : _sendBuffers = new Queue(), 925 static final int PHASE_IDLE = 0;
926 static final int PHASE_REQUEST = 1;
927 static final int PHASE_RESPONSE = 2;
928
929 _HttpConnectionBase() : _phase = PHASE_IDLE,
930 _sendBuffers = new Queue(),
922 _httpParser = new HttpParser(); 931 _httpParser = new HttpParser();
923 932
924 void _connectionEstablished(Socket socket) { 933 void _connectionEstablished(Socket socket) {
925 _socket = socket; 934 _socket = socket;
926 // Register handler for socket events. 935 // Register handler for socket events.
927 _socket.onData = _onData; 936 _socket.onData = _onData;
928 _socket.onClosed = _onClosed; 937 _socket.onClosed = _onClosed;
929 _socket.onError = _onError; 938 _socket.onError = _onError;
930 } 939 }
931 940
(...skipping 12 matching lines...) Expand all
944 if (bytesRead > 0) { 953 if (bytesRead > 0) {
945 int parsed = _httpParser.writeList(buffer, 0, bytesRead); 954 int parsed = _httpParser.writeList(buffer, 0, bytesRead);
946 if (parsed != bytesRead) { 955 if (parsed != bytesRead) {
947 // TODO(sgjesse): Error handling. 956 // TODO(sgjesse): Error handling.
948 _socket.close(); 957 _socket.close();
949 } 958 }
950 } 959 }
951 } 960 }
952 961
953 void _onClosed() { 962 void _onClosed() {
954 // Client closed socket for writing. Socket should still be open 963 if (_phase != PHASE_IDLE) {
955 // for writing the response. 964 // Client closed socket for writing. Socket should still be open
956 _closing = true; 965 // for writing the response.
966 _closing = true;
967 } else {
968 // The connection is currently not used by any request just close it.
969 _socket.close();
970 }
957 if (_onDisconnectCallback != null) _onDisconnectCallback(); 971 if (_onDisconnectCallback != null) _onDisconnectCallback();
958 } 972 }
959 973
960 void _onError() { 974 void _onError() {
961 // If an error occours, treat the socket as closed. 975 // If an error occours, treat the socket as closed.
962 _onClosed(); 976 _onClosed();
963 if (_onErrorCallback != null) { 977 if (_onErrorCallback != null) {
964 _onErrorCallback("Connection closed while sending data to client."); 978 _onErrorCallback("Connection closed while sending data to client.");
965 } 979 }
966 } 980 }
967 981
968 void set onDisconnect(void callback()) { 982 void set onDisconnect(void callback()) {
969 _onDisconnectCallback = callback; 983 _onDisconnectCallback = callback;
970 } 984 }
971 985
972 void set onError(void callback(String errorMessage)) { 986 void set onError(void callback(String errorMessage)) {
973 _onErrorCallback = callback; 987 _onErrorCallback = callback;
974 } 988 }
975 989
990 int hashCode() => _socket.hashCode();
991
992 int _phase;
976 Socket _socket; 993 Socket _socket;
977 bool _closing = false; // Is the socket closed by the client? 994 bool _closing = false; // Is the socket closed by the client?
978 HttpParser _httpParser; 995 HttpParser _httpParser;
979 996
980 Queue _sendBuffers; 997 Queue _sendBuffers;
981 998
982 Function _onDisconnectCallback; 999 Function _onDisconnectCallback;
983 Function _onErrorCallback; 1000 Function _onErrorCallback;
984 } 1001 }
985 1002
986 1003
987 // HTTP server connection over a socket. 1004 // HTTP server connection over a socket.
988 class _HttpConnection extends _HttpConnectionBase { 1005 class _HttpConnection extends _HttpConnectionBase {
989 _HttpConnection() { 1006 _HttpConnection() {
990 // Register HTTP parser callbacks. 1007 // Register HTTP parser callbacks.
991 _httpParser.requestStart = 1008 _httpParser.requestStart =
992 (method, uri) => _onRequestStart(method, uri); 1009 (method, uri) => _onRequestStart(method, uri);
993 _httpParser.responseStart = 1010 _httpParser.responseStart =
994 (statusCode, reasonPhrase) => 1011 (statusCode, reasonPhrase) =>
995 _onResponseStart(statusCode, reasonPhrase); 1012 _onResponseStart(statusCode, reasonPhrase);
996 _httpParser.headerReceived = 1013 _httpParser.headerReceived =
997 (name, value) => _onHeaderReceived(name, value); 1014 (name, value) => _onHeaderReceived(name, value);
998 _httpParser.headersComplete = () => _onHeadersComplete(); 1015 _httpParser.headersComplete = () => _onHeadersComplete();
999 _httpParser.dataReceived = (data) => _onDataReceived(data); 1016 _httpParser.dataReceived = (data) => _onDataReceived(data);
1000 _httpParser.dataEnd = () => _onDataEnd(); 1017 _httpParser.dataEnd = () => _onDataEnd();
1001 } 1018 }
1002 1019
1003 void _onRequestStart(String method, String uri) { 1020 void _onRequestStart(String method, String uri) {
1004 // Create new request and response objects for this request. 1021 // Create new request and response objects for this request.
1022 _phase = PHASE_REQUEST;
1005 _request = new _HttpRequest(this); 1023 _request = new _HttpRequest(this);
1006 _response = new _HttpResponse(this); 1024 _response = new _HttpResponse(this);
1007 _request._onRequestStart(method, uri); 1025 _request._onRequestStart(method, uri);
1008 } 1026 }
1009 1027
1010 void _onResponseStart(int statusCode, String reasonPhrase) { 1028 void _onResponseStart(int statusCode, String reasonPhrase) {
1011 // TODO(sgjesse): Error handling. 1029 // TODO(sgjesse): Error handling.
1012 } 1030 }
1013 1031
1014 void _onHeaderReceived(String name, String value) { 1032 void _onHeaderReceived(String name, String value) {
1015 _request._onHeaderReceived(name, value); 1033 _request._onHeaderReceived(name, value);
1016 } 1034 }
1017 1035
1018 void _onHeadersComplete() { 1036 void _onHeadersComplete() {
1019 _request._onHeadersComplete(); 1037 _request._onHeadersComplete();
1020 _response.keepAlive = _httpParser.keepAlive; 1038 _response.keepAlive = _httpParser.keepAlive;
1021 if (requestReceived != null) { 1039 if (requestReceived != null) {
1022 requestReceived(_request, _response); 1040 requestReceived(_request, _response);
1023 } 1041 }
1024 } 1042 }
1025 1043
1026 void _onDataReceived(List<int> data) { 1044 void _onDataReceived(List<int> data) {
1027 _request._onDataReceived(data); 1045 _request._onDataReceived(data);
1028 } 1046 }
1029 1047
1030 void _onDataEnd() { 1048 void _onDataEnd() {
1049 // Phase might already have gone to PHASE_IDLE if the response is
1050 // sent without waiting for request body.
1051 if (_phase == PHASE_REQUEST) {
1052 _phase = PHASE_RESPONSE;
1053 }
1031 _request._onDataEnd(); 1054 _request._onDataEnd();
1032 } 1055 }
1033 1056
1034 HttpRequest _request; 1057 HttpRequest _request;
1035 HttpResponse _response; 1058 HttpResponse _response;
1036 1059
1037 // Callbacks. 1060 // Callbacks.
1038 var requestReceived; 1061 var requestReceived;
1039 } 1062 }
1040 1063
1041 1064
1042 // HTTP server waiting for socket connections. The connections are 1065 // HTTP server waiting for socket connections. The connections are
1043 // managed by the server and as requests are received the request. 1066 // managed by the server and as requests are received the request.
1044 class _HttpServer implements HttpServer { 1067 class _HttpServer implements HttpServer {
1045 void listen(String host, int port, [int backlog = 5]) { 1068 void listen(String host, int port, [int backlog = 5]) {
1046 1069
1047 void onConnection(Socket socket) { 1070 void onConnection(Socket socket) {
1048 // Accept the client connection. 1071 // Accept the client connection.
1049 _HttpConnection connection = new _HttpConnection(); 1072 _HttpConnection connection = new _HttpConnection();
1050 connection._connectionEstablished(socket); 1073 connection._connectionEstablished(socket);
1051 connection.requestReceived = _onRequest; 1074 connection.requestReceived = _onRequest;
1052 _connections.add(connection); 1075 _connections.add(connection);
1053 void onDisconnect() { 1076 void onDisconnect() {
1054 for (int i = 0; i < _connections.length; i++) { 1077 _connections.remove(connection);
1055 if (_connections[i] == connection) {
1056 _connections.removeRange(i, 1);
1057 break;
1058 }
1059 }
1060 } 1078 }
1061 connection.onDisconnect = onDisconnect; 1079 connection.onDisconnect = onDisconnect;
1062 void onError(String errorMessage) { 1080 void onError(String errorMessage) {
1063 if (_onError != null) _onError(errorMessage); 1081 if (_onError != null) _onError(errorMessage);
1064 } 1082 }
1065 connection.onError = onError; 1083 connection.onError = onError;
1066 } 1084 }
1067 1085
1068 // TODO(ajohnsen): Use Set once Socket is Hashable. 1086 _connections = new Set<_HttpConnection>();
1069 _connections = new List<_HttpConnection>();
1070 _server = new ServerSocket(host, port, backlog); 1087 _server = new ServerSocket(host, port, backlog);
1071 _server.onConnection = onConnection; 1088 _server.onConnection = onConnection;
1072 } 1089 }
1073 1090
1074 void close() => _server.close(); 1091 void close() => _server.close();
1075 int get port() => _server.port; 1092 int get port() => _server.port;
1076 1093
1077 void set onError(void handler(String errorMessage)) { 1094 void set onError(void handler(String errorMessage)) {
1078 _onError = handler; 1095 _onError = handler;
1079 } 1096 }
1080 1097
1081 void set onRequest(void handler(HttpRequest, HttpResponse)) { 1098 void set onRequest(void handler(HttpRequest, HttpResponse)) {
1082 _onRequest = handler; 1099 _onRequest = handler;
1083 } 1100 }
1084 1101
1085 ServerSocket _server; // The server listen socket. 1102 ServerSocket _server; // The server listen socket.
1086 List<_HttpConnection> _connections; // List of currently connected clients. 1103 Set<_HttpConnection> _connections; // Set of currently connected clients.
1087 Function _onRequest; 1104 Function _onRequest;
1088 Function _onError; 1105 Function _onError;
1089 } 1106 }
1090 1107
1091 1108
1092 class _HttpClientRequest 1109 class _HttpClientRequest
1093 extends _HttpRequestResponseBase implements HttpClientRequest { 1110 extends _HttpRequestResponseBase implements HttpClientRequest {
1094 static final int START = 0; 1111 static final int START = 0;
1095 static final int HEADERS_SENT = 1; 1112 static final int HEADERS_SENT = 1;
1096 static final int DONE = 2; 1113 static final int DONE = 2;
(...skipping 268 matching lines...) Expand 10 before | Expand all | Expand 10 after
1365 1382
1366 void _markReturned() { 1383 void _markReturned() {
1367 _socket.onData = null; 1384 _socket.onData = null;
1368 _socket.onClosed = null; 1385 _socket.onClosed = null;
1369 _socket.onError = null; 1386 _socket.onError = null;
1370 _returnTime = new Date.now(); 1387 _returnTime = new Date.now();
1371 } 1388 }
1372 1389
1373 Duration _idleTime(Date now) => now.difference(_returnTime); 1390 Duration _idleTime(Date now) => now.difference(_returnTime);
1374 1391
1392 int hashCode() => _socket.hashCode();
1393
1375 String _host; 1394 String _host;
1376 int _port; 1395 int _port;
1377 Socket _socket; 1396 Socket _socket;
1378 Date _returnTime; 1397 Date _returnTime;
1379 } 1398 }
1380 1399
1381 1400
1382 class _HttpClient implements HttpClient { 1401 class _HttpClient implements HttpClient {
1383 static final int DEFAULT_EVICTION_TIMEOUT = 60000; 1402 static final int DEFAULT_EVICTION_TIMEOUT = 60000;
1384 1403
1385 _HttpClient() : _openSockets = new Map(), _shutdown = false; 1404 _HttpClient() : _openSockets = new Map(),
1405 _activeSockets = new Set(),
1406 _shutdown = false;
1386 1407
1387 HttpClientConnection open( 1408 HttpClientConnection open(
1388 String method, String host, int port, String path) { 1409 String method, String host, int port, String path) {
1389 if (_shutdown) throw new HttpException("HttpClient shutdown"); 1410 if (_shutdown) throw new HttpException("HttpClient shutdown");
1390 return _prepareHttpClientConnection(host, port, method, path); 1411 return _prepareHttpClientConnection(host, port, method, path);
1391 } 1412 }
1392 1413
1393 HttpClientConnection get(String host, int port, String path) { 1414 HttpClientConnection get(String host, int port, String path) {
1394 return open("GET", host, port, path); 1415 return open("GET", host, port, path);
1395 } 1416 }
1396 1417
1397 HttpClientConnection post(String host, int port, String path) { 1418 HttpClientConnection post(String host, int port, String path) {
1398 return open("POST", host, port, path); 1419 return open("POST", host, port, path);
1399 } 1420 }
1400 1421
1401 void shutdown() { 1422 void shutdown() {
1402 _openSockets.forEach( 1423 _openSockets.forEach((String key, Queue<_SocketConnection> connections) {
1403 void _(String key, Queue<_SocketConnection> connections) { 1424 while (!connections.isEmpty()) {
1404 while (!connections.isEmpty()) { 1425 _SocketConnection socketConn = connections.removeFirst();
1405 var socketConn = connections.removeFirst(); 1426 socketConn._socket.close();
1406 socketConn._socket.close(); 1427 }
1407 } 1428 });
1408 }); 1429 _activeSockets.forEach((_SocketConnection socketConn) {
1430 socketConn._socket.close();
1431 });
1409 if (_evictionTimer != null) { 1432 if (_evictionTimer != null) {
1410 _evictionTimer.cancel(); 1433 _evictionTimer.cancel();
1411 } 1434 }
1412 _shutdown = true; 1435 _shutdown = true;
1413 } 1436 }
1414 1437
1415 String _connectionKey(String host, int port) { 1438 String _connectionKey(String host, int port) {
1416 return "$host:$port"; 1439 return "$host:$port";
1417 } 1440 }
1418 1441
(...skipping 15 matching lines...) Expand all
1434 1457
1435 // If there are active connections for this key get the first one 1458 // If there are active connections for this key get the first one
1436 // otherwise create a new one. 1459 // otherwise create a new one.
1437 Queue socketConnections = _openSockets[_connectionKey(host, port)]; 1460 Queue socketConnections = _openSockets[_connectionKey(host, port)];
1438 if (socketConnections == null || socketConnections.isEmpty()) { 1461 if (socketConnections == null || socketConnections.isEmpty()) {
1439 Socket socket = new Socket(host, port); 1462 Socket socket = new Socket(host, port);
1440 socket.onConnect = () { 1463 socket.onConnect = () {
1441 socket.onError = null; 1464 socket.onError = null;
1442 _SocketConnection socketConn = 1465 _SocketConnection socketConn =
1443 new _SocketConnection(host, port, socket); 1466 new _SocketConnection(host, port, socket);
1467 _activeSockets.add(socketConn);
1444 _connectionOpened(socketConn, connection); 1468 _connectionOpened(socketConn, connection);
1445 }; 1469 };
1446 socket.onError = () { 1470 socket.onError = () {
1447 if (_onError !== null) { 1471 if (_onError !== null) {
1448 _onError(HttpStatus.NETWORK_CONNECT_TIMEOUT_ERROR); 1472 _onError(HttpStatus.NETWORK_CONNECT_TIMEOUT_ERROR);
1449 } 1473 }
1450 }; 1474 };
1451 } else { 1475 } else {
1452 _SocketConnection socketConn = socketConnections.removeFirst(); 1476 _SocketConnection socketConn = socketConnections.removeFirst();
1477 _activeSockets.add(socketConn);
1453 new Timer((ignored) => _connectionOpened(socketConn, connection), 0); 1478 new Timer((ignored) => _connectionOpened(socketConn, connection), 0);
1454 1479
1455 // Get rid of eviction timer if there are no more active connections. 1480 // Get rid of eviction timer if there are no more active connections.
1456 if (socketConnections.isEmpty()) { 1481 if (socketConnections.isEmpty()) {
1457 _evictionTimer.cancel(); 1482 _evictionTimer.cancel();
1458 _evictionTimer = null; 1483 _evictionTimer = null;
1459 } 1484 }
1460 } 1485 }
1461 1486
1462 return connection; 1487 return connection;
(...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after
1494 } else { 1519 } else {
1495 break; 1520 break;
1496 } 1521 }
1497 } 1522 }
1498 }); 1523 });
1499 } 1524 }
1500 _evictionTimer = new Timer.repeating(_handleEviction, 10000); 1525 _evictionTimer = new Timer.repeating(_handleEviction, 10000);
1501 } 1526 }
1502 1527
1503 // Return connection. 1528 // Return connection.
1529 _activeSockets.remove(socketConn);
1504 sockets.addFirst(socketConn); 1530 sockets.addFirst(socketConn);
1505 socketConn._markReturned(); 1531 socketConn._markReturned();
1506 } 1532 }
1507 1533
1508 void set onError(void callback(int status)) { 1534 void set onError(void callback(int status)) {
1509 _onError = callback; 1535 _onError = callback;
1510 } 1536 }
1511 1537
1512 Function _onOpen; 1538 Function _onOpen;
1513 Function _onError; 1539 Function _onError;
1514 Map<String, Queue<_SocketConnection>> _openSockets; 1540 Map<String, Queue<_SocketConnection>> _openSockets;
1541 Set<_SocketConnection> _activeSockets;
1515 Timer _evictionTimer; 1542 Timer _evictionTimer;
1516 bool _shutdown; // Has this HTTP client been shutdown? 1543 bool _shutdown; // Has this HTTP client been shutdown?
1517 } 1544 }
1518 1545
1519 1546
1520 class HttpUtil { 1547 class HttpUtil {
1521 static String decodeUrlEncodedString(String urlEncoded) { 1548 static String decodeUrlEncodedString(String urlEncoded) {
1522 void invalidEscape() { 1549 void invalidEscape() {
1523 // TODO(sgjesse): Handle the error. 1550 // TODO(sgjesse): Handle the error.
1524 } 1551 }
(...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after
1568 } else { 1595 } else {
1569 value = queryString.substring(currentPosition, position); 1596 value = queryString.substring(currentPosition, position);
1570 currentPosition = position + 1; 1597 currentPosition = position + 1;
1571 } 1598 }
1572 result[HttpUtil.decodeUrlEncodedString(name)] = 1599 result[HttpUtil.decodeUrlEncodedString(name)] =
1573 HttpUtil.decodeUrlEncodedString(value); 1600 HttpUtil.decodeUrlEncodedString(value);
1574 } 1601 }
1575 return result; 1602 return result;
1576 } 1603 }
1577 } 1604 }
OLDNEW
« no previous file with comments | « no previous file | runtime/bin/socket.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698