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 |