| OLD | NEW |
| 1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2014, 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 library service_common; | 5 library service_common; |
| 6 | 6 |
| 7 import 'dart:async'; | 7 import 'dart:async'; |
| 8 import 'dart:convert'; | 8 import 'dart:convert'; |
| 9 import 'dart:typed_data'; | 9 import 'dart:typed_data'; |
| 10 | 10 |
| (...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 58 final String method; | 58 final String method; |
| 59 final Map params; | 59 final Map params; |
| 60 final Completer<Map> completer; | 60 final Completer<Map> completer; |
| 61 | 61 |
| 62 _WebSocketRequest(this.method, this.params) | 62 _WebSocketRequest(this.method, this.params) |
| 63 : completer = new Completer<Map>(); | 63 : completer = new Completer<Map>(); |
| 64 } | 64 } |
| 65 | 65 |
| 66 /// Minimal common interface for 'WebSocket' in [dart:io] and [dart:html]. | 66 /// Minimal common interface for 'WebSocket' in [dart:io] and [dart:html]. |
| 67 abstract class CommonWebSocket { | 67 abstract class CommonWebSocket { |
| 68 void connect(String address, | 68 void connect(String address, void onOpen(), void onMessage(dynamic data), |
| 69 void onOpen(), | 69 void onError(), void onClose()); |
| 70 void onMessage(dynamic data), | |
| 71 void onError(), | |
| 72 void onClose()); | |
| 73 bool get isOpen; | 70 bool get isOpen; |
| 74 void send(dynamic data); | 71 void send(dynamic data); |
| 75 void close(); | 72 void close(); |
| 76 Future<ByteData> nonStringToByteData(dynamic data); | 73 Future<ByteData> nonStringToByteData(dynamic data); |
| 77 } | 74 } |
| 78 | 75 |
| 79 /// A [CommonWebSocketVM] communicates with a Dart VM over a CommonWebSocket. | 76 /// A [CommonWebSocketVM] communicates with a Dart VM over a CommonWebSocket. |
| 80 /// The Dart VM can be embedded in Chromium or standalone. | 77 /// The Dart VM can be embedded in Chromium or standalone. |
| 81 abstract class CommonWebSocketVM extends VM { | 78 abstract class CommonWebSocketVM extends VM { |
| 82 final Completer _connected = new Completer(); | 79 final Completer _connected = new Completer(); |
| 83 final Completer _disconnected = new Completer<String>(); | 80 final Completer _disconnected = new Completer<String>(); |
| 84 final WebSocketVMTarget target; | 81 final WebSocketVMTarget target; |
| 85 final Map<String, _WebSocketRequest> _delayedRequests = | 82 final Map<String, _WebSocketRequest> _delayedRequests = |
| 86 new Map<String, _WebSocketRequest>(); | 83 new Map<String, _WebSocketRequest>(); |
| 87 final Map<String, _WebSocketRequest> _pendingRequests = | 84 final Map<String, _WebSocketRequest> _pendingRequests = |
| 88 new Map<String, _WebSocketRequest>(); | 85 new Map<String, _WebSocketRequest>(); |
| 89 int _requestSerial = 0; | 86 int _requestSerial = 0; |
| 90 bool _hasInitiatedConnect = false; | 87 bool _hasInitiatedConnect = false; |
| 91 Utf8Decoder _utf8Decoder = const Utf8Decoder(); | 88 Utf8Decoder _utf8Decoder = const Utf8Decoder(); |
| 92 | 89 |
| 93 String get displayName => '${name}@${target.name}'; | 90 String get displayName => '${name}@${target.name}'; |
| 94 | 91 |
| 95 CommonWebSocket _webSocket; | 92 CommonWebSocket _webSocket; |
| 96 | 93 |
| 97 CommonWebSocketVM(this.target, this._webSocket) { | 94 CommonWebSocketVM(this.target, this._webSocket) { |
| 98 assert(target != null); | 95 assert(target != null); |
| 99 } | 96 } |
| 100 | 97 |
| 101 void _notifyConnect() { | 98 void _notifyConnect() { |
| 102 if (!_connected.isCompleted) { | 99 if (!_connected.isCompleted) { |
| 103 Logger.root.info('WebSocketVM connection opened: ${target.networkAddress}'
); | 100 Logger.root |
| 101 .info('WebSocketVM connection opened: ${target.networkAddress}'); |
| 104 _connected.complete(this); | 102 _connected.complete(this); |
| 105 } | 103 } |
| 106 } | 104 } |
| 105 |
| 107 Future get onConnect => _connected.future; | 106 Future get onConnect => _connected.future; |
| 108 bool get wasOrIsConnected => _connected.isCompleted; | 107 bool get wasOrIsConnected => _connected.isCompleted; |
| 109 bool get isConnected => wasOrIsConnected && !isDisconnected; | 108 bool get isConnected => wasOrIsConnected && !isDisconnected; |
| 110 void _notifyDisconnect(String reason) { | 109 void _notifyDisconnect(String reason) { |
| 111 if (!_disconnected.isCompleted) { | 110 if (!_disconnected.isCompleted) { |
| 112 Logger.root.info('WebSocketVM connection error: ${target.networkAddress}')
; | 111 Logger.root |
| 112 .info('WebSocketVM connection error: ${target.networkAddress}'); |
| 113 _disconnected.complete(reason); | 113 _disconnected.complete(reason); |
| 114 } | 114 } |
| 115 } | 115 } |
| 116 |
| 116 Future get onDisconnect => _disconnected.future; | 117 Future get onDisconnect => _disconnected.future; |
| 117 bool get isDisconnected => _disconnected.isCompleted; | 118 bool get isDisconnected => _disconnected.isCompleted; |
| 118 | 119 |
| 119 void disconnect({String reason : 'WebSocket closed'}) { | 120 void disconnect({String reason: 'WebSocket closed'}) { |
| 120 if (_hasInitiatedConnect) { | 121 if (_hasInitiatedConnect) { |
| 121 if (_webSocket != null) { | 122 if (_webSocket != null) { |
| 122 _webSocket.close(); | 123 _webSocket.close(); |
| 123 } | 124 } |
| 124 } | 125 } |
| 125 // We don't need to cancel requests and notify here. These | 126 // We don't need to cancel requests and notify here. These |
| 126 // functions will be called again when the onClose callback | 127 // functions will be called again when the onClose callback |
| 127 // fires. However, we may have a better 'reason' string now, so | 128 // fires. However, we may have a better 'reason' string now, so |
| 128 // let's take care of business. | 129 // let's take care of business. |
| 129 _cancelAllRequests(reason); | 130 _cancelAllRequests(reason); |
| (...skipping 48 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 178 _sendAllDelayedRequests(); | 179 _sendAllDelayedRequests(); |
| 179 _notifyConnect(); | 180 _notifyConnect(); |
| 180 } | 181 } |
| 181 | 182 |
| 182 Map _parseJSON(String message) { | 183 Map _parseJSON(String message) { |
| 183 var map; | 184 var map; |
| 184 try { | 185 try { |
| 185 map = JSON.decode(message); | 186 map = JSON.decode(message); |
| 186 } catch (e, st) { | 187 } catch (e, st) { |
| 187 Logger.root.severe('Disconnecting: Error decoding message: $e\n$st'); | 188 Logger.root.severe('Disconnecting: Error decoding message: $e\n$st'); |
| 188 disconnect(reason:'Connection saw corrupt JSON message: $e'); | 189 disconnect(reason: 'Connection saw corrupt JSON message: $e'); |
| 189 return null; | 190 return null; |
| 190 } | 191 } |
| 191 if (map == null) { | 192 if (map == null) { |
| 192 Logger.root.severe("Disconnecting: Unable to decode 'null' message"); | 193 Logger.root.severe("Disconnecting: Unable to decode 'null' message"); |
| 193 disconnect(reason:"Connection saw 'null' message"); | 194 disconnect(reason: "Connection saw 'null' message"); |
| 194 return null; | 195 return null; |
| 195 } | 196 } |
| 196 return map; | 197 return map; |
| 197 } | 198 } |
| 198 | 199 |
| 199 void _onBinaryMessage(dynamic data) { | 200 void _onBinaryMessage(dynamic data) { |
| 200 _webSocket.nonStringToByteData(data).then((ByteData bytes) { | 201 _webSocket.nonStringToByteData(data).then((ByteData bytes) { |
| 201 // See format spec. in VMs Service::SendEvent. | 202 // See format spec. in VMs Service::SendEvent. |
| 202 int offset = 0; | 203 int offset = 0; |
| 203 // Dart2JS workaround (no getUint64). Limit to 4 GB metadata. | 204 // Dart2JS workaround (no getUint64). Limit to 4 GB metadata. |
| 204 assert(bytes.getUint32(offset, Endianness.BIG_ENDIAN) == 0); | 205 assert(bytes.getUint32(offset, Endianness.BIG_ENDIAN) == 0); |
| 205 int metaSize = bytes.getUint32(offset + 4, Endianness.BIG_ENDIAN); | 206 int metaSize = bytes.getUint32(offset + 4, Endianness.BIG_ENDIAN); |
| 206 offset += 8; | 207 offset += 8; |
| 207 var meta = _utf8Decoder.convert(new Uint8List.view( | 208 var meta = _utf8Decoder.convert(new Uint8List.view( |
| 208 bytes.buffer, bytes.offsetInBytes + offset, metaSize)); | 209 bytes.buffer, bytes.offsetInBytes + offset, metaSize)); |
| 209 offset += metaSize; | 210 offset += metaSize; |
| 210 var data = new ByteData.view( | 211 var data = new ByteData.view(bytes.buffer, bytes.offsetInBytes + offset, |
| 211 bytes.buffer, | |
| 212 bytes.offsetInBytes + offset, | |
| 213 bytes.lengthInBytes - offset); | 212 bytes.lengthInBytes - offset); |
| 214 var map = _parseJSON(meta); | 213 var map = _parseJSON(meta); |
| 215 if (map == null || map['method'] != 'streamNotify') { | 214 if (map == null || map['method'] != 'streamNotify') { |
| 216 return; | 215 return; |
| 217 } | 216 } |
| 218 var event = map['params']['event']; | 217 var event = map['params']['event']; |
| 219 var streamId = map['params']['streamId']; | 218 var streamId = map['params']['streamId']; |
| 220 scheduleMicrotask(() { postServiceEvent(streamId, event, data); }); | 219 scheduleMicrotask(() { |
| 220 postServiceEvent(streamId, event, data); |
| 221 }); |
| 221 }); | 222 }); |
| 222 } | 223 } |
| 223 | 224 |
| 224 void _onStringMessage(String data) { | 225 void _onStringMessage(String data) { |
| 225 var map = _parseJSON(data); | 226 var map = _parseJSON(data); |
| 226 if (map == null) { | 227 if (map == null) { |
| 227 return; | 228 return; |
| 228 } | 229 } |
| 229 | 230 |
| 230 if (map['method'] == 'streamNotify') { | 231 if (map['method'] == 'streamNotify') { |
| 231 var event = map['params']['event']; | 232 var event = map['params']['event']; |
| 232 var streamId = map['params']['streamId']; | 233 var streamId = map['params']['streamId']; |
| 233 scheduleMicrotask(() { postServiceEvent(streamId, event, null); }); | 234 scheduleMicrotask(() { |
| 235 postServiceEvent(streamId, event, null); |
| 236 }); |
| 234 return; | 237 return; |
| 235 } | 238 } |
| 236 | 239 |
| 237 // Extract serial and result. | 240 // Extract serial and result. |
| 238 var serial = map['id']; | 241 var serial = map['id']; |
| 239 | 242 |
| 240 // Complete request. | 243 // Complete request. |
| 241 var request = _pendingRequests.remove(serial); | 244 var request = _pendingRequests.remove(serial); |
| 242 if (request == null) { | 245 if (request == null) { |
| 243 Logger.root.severe('Received unexpected message: ${map}'); | 246 Logger.root.severe('Received unexpected message: ${map}'); |
| 244 return; | 247 return; |
| 245 } | 248 } |
| 246 if (request.method != 'getTagProfile' && | 249 if (request.method != 'getTagProfile' && |
| 247 request.method != 'getIsolateMetric' && | 250 request.method != 'getIsolateMetric' && |
| 248 request.method != 'getVMMetric') { | 251 request.method != 'getVMMetric') { |
| 249 Logger.root.info( | 252 Logger.root.info('RESPONSE [${serial}] ${request.method}'); |
| 250 'RESPONSE [${serial}] ${request.method}'); | |
| 251 } | 253 } |
| 252 | 254 |
| 253 var result = map['result']; | 255 var result = map['result']; |
| 254 if (result != null) { | 256 if (result != null) { |
| 255 request.completer.complete(result); | 257 request.completer.complete(result); |
| 256 } else { | 258 } else { |
| 257 var exception = new ServerRpcException.fromMap(map['error']); | 259 var exception = new ServerRpcException.fromMap(map['error']); |
| 258 request.completer.completeError(exception); | 260 request.completer.completeError(exception); |
| 259 } | 261 } |
| 260 } | 262 } |
| 261 | 263 |
| 262 // WebSocket message event handler. | 264 // WebSocket message event handler. |
| 263 void _onMessage(dynamic data) { | 265 void _onMessage(dynamic data) { |
| 264 if (data is! String) { | 266 if (data is! String) { |
| 265 _onBinaryMessage(data); | 267 _onBinaryMessage(data); |
| 266 } else { | 268 } else { |
| 267 _onStringMessage(data); | 269 _onStringMessage(data); |
| 268 } | 270 } |
| 269 } | 271 } |
| 270 | 272 |
| 271 void _cancelRequests(Map<String,_WebSocketRequest> requests, | 273 void _cancelRequests( |
| 272 String message) { | 274 Map<String, _WebSocketRequest> requests, String message) { |
| 273 requests.forEach((String serial, _WebSocketRequest request) { | 275 requests.forEach((String serial, _WebSocketRequest request) { |
| 274 var exception = new NetworkRpcException(message + | 276 var exception = new NetworkRpcException(message + |
| 275 '(id: $serial method: ${request.method} params: ${request.params})'); | 277 '(id: $serial method: ${request.method} params: ${request.params})'); |
| 276 request.completer.completeError(exception); | 278 request.completer.completeError(exception); |
| 277 }); | 279 }); |
| 278 requests.clear(); | 280 requests.clear(); |
| 279 } | 281 } |
| 280 | 282 |
| 281 /// Cancel all pending and delayed requests by completing them with an error. | 283 /// Cancel all pending and delayed requests by completing them with an error. |
| 282 void _cancelAllRequests(String reason) { | 284 void _cancelAllRequests(String reason) { |
| (...skipping 16 matching lines...) Expand all Loading... |
| 299 } | 301 } |
| 300 Logger.root.info('Sending all delayed requests.'); | 302 Logger.root.info('Sending all delayed requests.'); |
| 301 // Send all delayed requests. | 303 // Send all delayed requests. |
| 302 _delayedRequests.forEach(_sendRequest); | 304 _delayedRequests.forEach(_sendRequest); |
| 303 // Clear all delayed requests. | 305 // Clear all delayed requests. |
| 304 _delayedRequests.clear(); | 306 _delayedRequests.clear(); |
| 305 } | 307 } |
| 306 | 308 |
| 307 /// Send the request over WebSocket. | 309 /// Send the request over WebSocket. |
| 308 void _sendRequest(String serial, _WebSocketRequest request) { | 310 void _sendRequest(String serial, _WebSocketRequest request) { |
| 309 assert (_webSocket.isOpen); | 311 assert(_webSocket.isOpen); |
| 310 // Mark request as pending. | 312 // Mark request as pending. |
| 311 assert(_pendingRequests.containsKey(serial) == false); | 313 assert(_pendingRequests.containsKey(serial) == false); |
| 312 _pendingRequests[serial] = request; | 314 _pendingRequests[serial] = request; |
| 313 var message; | 315 var message; |
| 314 // Encode message. | 316 // Encode message. |
| 315 if (target.chrome) { | 317 if (target.chrome) { |
| 316 message = JSON.encode({ | 318 message = JSON.encode({ |
| 317 'id': int.parse(serial), | 319 'id': int.parse(serial), |
| 318 'method': 'Dart.observatoryQuery', | 320 'method': 'Dart.observatoryQuery', |
| 319 'params': { | 321 'params': {'id': serial, 'query': request.method} |
| 320 'id': serial, | |
| 321 'query': request.method | |
| 322 } | |
| 323 }); | 322 }); |
| 324 } else { | 323 } else { |
| 325 message = JSON.encode({'id': serial, | 324 message = JSON.encode( |
| 326 'method': request.method, | 325 {'id': serial, 'method': request.method, 'params': request.params}); |
| 327 'params': request.params}); | |
| 328 } | 326 } |
| 329 if (request.method != 'getTagProfile' && | 327 if (request.method != 'getTagProfile' && |
| 330 request.method != 'getIsolateMetric' && | 328 request.method != 'getIsolateMetric' && |
| 331 request.method != 'getVMMetric') { | 329 request.method != 'getVMMetric') { |
| 332 Logger.root.info( | 330 Logger.root.info( |
| 333 'GET [${serial}] ${request.method}(${request.params}) from ${target.ne
tworkAddress}'); | 331 'GET [${serial}] ${request.method}(${request.params}) from ${target.ne
tworkAddress}'); |
| 334 } | 332 } |
| 335 // Send message. | 333 // Send message. |
| 336 _webSocket.send(message); | 334 _webSocket.send(message); |
| 337 } | 335 } |
| 338 | 336 |
| 339 String toString() => displayName; | 337 String toString() => displayName; |
| 340 } | 338 } |
| OLD | NEW |