OLD | NEW |
1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2015, 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 dart._vmservice; | 5 library dart._vmservice; |
6 | 6 |
7 import 'dart:async'; | 7 import 'dart:async'; |
8 import 'dart:collection'; | 8 import 'dart:collection'; |
9 import 'dart:convert'; | 9 import 'dart:convert'; |
10 import 'dart:developer' show ServiceProtocolInfo; | 10 import 'dart:developer' show ServiceProtocolInfo; |
(...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
44 // TODO(johnmccutchan): Enable the auth token and drop the origin check. | 44 // TODO(johnmccutchan): Enable the auth token and drop the origin check. |
45 final bool useAuthToken = const bool.fromEnvironment('DART_SERVICE_USE_AUTH'); | 45 final bool useAuthToken = const bool.fromEnvironment('DART_SERVICE_USE_AUTH'); |
46 | 46 |
47 // This is for use by the embedder. It is a map from the isolateId to | 47 // This is for use by the embedder. It is a map from the isolateId to |
48 // anything implementing IsolateEmbedderData. When an isolate goes away, | 48 // anything implementing IsolateEmbedderData. When an isolate goes away, |
49 // the cleanup method will be invoked after being removed from the map. | 49 // the cleanup method will be invoked after being removed from the map. |
50 final Map<int, IsolateEmbedderData> isolateEmbedderData = | 50 final Map<int, IsolateEmbedderData> isolateEmbedderData = |
51 new Map<int, IsolateEmbedderData>(); | 51 new Map<int, IsolateEmbedderData>(); |
52 | 52 |
53 // These must be kept in sync with the declarations in vm/json_stream.h. | 53 // These must be kept in sync with the declarations in vm/json_stream.h. |
54 const kInvalidParams = -32602; | 54 const kInvalidParams = -32602; |
55 const kInternalError = -32603; | 55 const kInternalError = -32603; |
56 const kFeatureDisabled = 100; | 56 const kFeatureDisabled = 100; |
57 const kStreamAlreadySubscribed = 103; | 57 const kStreamAlreadySubscribed = 103; |
58 const kStreamNotSubscribed = 104; | 58 const kStreamNotSubscribed = 104; |
59 const kFileSystemAlreadyExists = 1001; | 59 const kFileSystemAlreadyExists = 1001; |
60 const kFileSystemDoesNotExist = 1002; | 60 const kFileSystemDoesNotExist = 1002; |
61 const kFileDoesNotExist = 1003; | 61 const kFileDoesNotExist = 1003; |
62 | 62 |
63 var _errorMessages = { | 63 var _errorMessages = { |
64 kInvalidParams: 'Invalid params', | 64 kInvalidParams: 'Invalid params', |
65 kInternalError: 'Internal error', | 65 kInternalError: 'Internal error', |
66 kFeatureDisabled: 'Feature is disabled', | 66 kFeatureDisabled: 'Feature is disabled', |
67 kStreamAlreadySubscribed: 'Stream already subscribed', | 67 kStreamAlreadySubscribed: 'Stream already subscribed', |
68 kStreamNotSubscribed: 'Stream not subscribed', | 68 kStreamNotSubscribed: 'Stream not subscribed', |
69 kFileSystemAlreadyExists: 'File system already exists', | 69 kFileSystemAlreadyExists: 'File system already exists', |
70 kFileSystemDoesNotExist: 'File system does not exist', | 70 kFileSystemDoesNotExist: 'File system does not exist', |
71 kFileDoesNotExist: 'File does not exist', | 71 kFileDoesNotExist: 'File does not exist', |
72 }; | 72 }; |
73 | 73 |
74 String encodeRpcError(Message message, int code, {String details}) { | 74 String encodeRpcError(Message message, int code, {String details}) { |
75 var response = { | 75 var response = { |
76 'jsonrpc': '2.0', | 76 'jsonrpc': '2.0', |
77 'id' : message.serial, | 77 'id': message.serial, |
78 'error' : { | 78 'error': { |
79 'code': code, | 79 'code': code, |
80 'message': _errorMessages[code], | 80 'message': _errorMessages[code], |
81 }, | 81 }, |
82 }; | 82 }; |
83 if (details != null) { | 83 if (details != null) { |
84 response['error']['data'] = { | 84 response['error']['data'] = { |
85 'details': details, | 85 'details': details, |
86 }; | 86 }; |
87 } | 87 } |
88 return JSON.encode(response); | 88 return JSON.encode(response); |
89 } | 89 } |
90 | 90 |
91 String encodeMissingParamError(Message message, String param) { | 91 String encodeMissingParamError(Message message, String param) { |
92 return encodeRpcError( | 92 return encodeRpcError(message, kInvalidParams, |
93 message, kInvalidParams, | |
94 details: "${message.method} expects the '${param}' parameter"); | 93 details: "${message.method} expects the '${param}' parameter"); |
95 } | 94 } |
96 | 95 |
97 String encodeInvalidParamError(Message message, String param) { | 96 String encodeInvalidParamError(Message message, String param) { |
98 var value = message.params[param]; | 97 var value = message.params[param]; |
99 return encodeRpcError( | 98 return encodeRpcError(message, kInvalidParams, |
100 message, kInvalidParams, | |
101 details: "${message.method}: invalid '${param}' parameter: ${value}"); | 99 details: "${message.method}: invalid '${param}' parameter: ${value}"); |
102 } | 100 } |
103 | 101 |
104 String encodeResult(Message message, Map result) { | 102 String encodeResult(Message message, Map result) { |
105 var response = { | 103 var response = { |
106 'jsonrpc': '2.0', | 104 'jsonrpc': '2.0', |
107 'id' : message.serial, | 105 'id': message.serial, |
108 'result' : result, | 106 'result': result, |
109 }; | 107 }; |
110 return JSON.encode(response); | 108 return JSON.encode(response); |
111 } | 109 } |
112 | 110 |
113 String encodeSuccess(Message message) { | 111 String encodeSuccess(Message message) { |
114 return encodeResult(message, { 'type': 'Success' }); | 112 return encodeResult(message, {'type': 'Success'}); |
115 } | 113 } |
116 | 114 |
117 const shortDelay = const Duration(milliseconds: 10); | 115 const shortDelay = const Duration(milliseconds: 10); |
118 | 116 |
119 /// Called when the server should be started. | 117 /// Called when the server should be started. |
120 typedef Future ServerStartCallback(); | 118 typedef Future ServerStartCallback(); |
121 | 119 |
122 /// Called when the server should be stopped. | 120 /// Called when the server should be stopped. |
123 typedef Future ServerStopCallback(); | 121 typedef Future ServerStopCallback(); |
124 | 122 |
125 /// Called when the service is exiting. | 123 /// Called when the service is exiting. |
126 typedef Future CleanupCallback(); | 124 typedef Future CleanupCallback(); |
127 | 125 |
128 /// Called to create a temporary directory | 126 /// Called to create a temporary directory |
129 typedef Future<Uri> CreateTempDirCallback(String base); | 127 typedef Future<Uri> CreateTempDirCallback(String base); |
130 | 128 |
131 /// Called to delete a directory | 129 /// Called to delete a directory |
132 typedef Future DeleteDirCallback(Uri path); | 130 typedef Future DeleteDirCallback(Uri path); |
133 | 131 |
134 /// Called to write a file. | 132 /// Called to write a file. |
135 typedef Future WriteFileCallback(Uri path, List<int> bytes); | 133 typedef Future WriteFileCallback(Uri path, List<int> bytes); |
136 | 134 |
137 /// Called to write a stream into a file. | 135 /// Called to write a stream into a file. |
138 typedef Future WriteStreamFileCallback(Uri path, Stream<List<int>> bytes); | 136 typedef Future WriteStreamFileCallback(Uri path, Stream<List<int>> bytes); |
139 | 137 |
140 /// Called to read a file. | 138 /// Called to read a file. |
141 typedef Future<List<int>> ReadFileCallback(Uri path); | 139 typedef Future<List<int>> ReadFileCallback(Uri path); |
142 | 140 |
143 /// Called to list all files under some path. | 141 /// Called to list all files under some path. |
144 typedef Future<List<Map<String,String>>> ListFilesCallback(Uri path); | 142 typedef Future<List<Map<String, String>>> ListFilesCallback(Uri path); |
145 | 143 |
146 /// Called when we need information about the server. | 144 /// Called when we need information about the server. |
147 typedef Future<Uri> ServerInformationCallback(); | 145 typedef Future<Uri> ServerInformationCallback(); |
148 | 146 |
149 /// Called when we want to [enable] or disable the web server. | 147 /// Called when we want to [enable] or disable the web server. |
150 typedef Future<Uri> WebServerControlCallback(bool enable); | 148 typedef Future<Uri> WebServerControlCallback(bool enable); |
151 | 149 |
152 /// Hooks that are setup by the embedder. | 150 /// Hooks that are setup by the embedder. |
153 class VMServiceEmbedderHooks { | 151 class VMServiceEmbedderHooks { |
154 static ServerStartCallback serverStart; | 152 static ServerStartCallback serverStart; |
(...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
195 void _eventMessageHandler(List eventMessage) { | 193 void _eventMessageHandler(List eventMessage) { |
196 var streamId = eventMessage[0]; | 194 var streamId = eventMessage[0]; |
197 var event = eventMessage[1]; | 195 var event = eventMessage[1]; |
198 for (var client in clients) { | 196 for (var client in clients) { |
199 if (client.sendEvents && client.streams.contains(streamId)) { | 197 if (client.sendEvents && client.streams.contains(streamId)) { |
200 client.post(event); | 198 client.post(event); |
201 } | 199 } |
202 } | 200 } |
203 } | 201 } |
204 | 202 |
205 void _controlMessageHandler(int code, | 203 void _controlMessageHandler(int code, int portId, SendPort sp, String name) { |
206 int portId, | |
207 SendPort sp, | |
208 String name) { | |
209 switch (code) { | 204 switch (code) { |
210 case Constants.ISOLATE_STARTUP_MESSAGE_ID: | 205 case Constants.ISOLATE_STARTUP_MESSAGE_ID: |
211 runningIsolates.isolateStartup(portId, sp, name); | 206 runningIsolates.isolateStartup(portId, sp, name); |
212 break; | 207 break; |
213 case Constants.ISOLATE_SHUTDOWN_MESSAGE_ID: | 208 case Constants.ISOLATE_SHUTDOWN_MESSAGE_ID: |
214 runningIsolates.isolateShutdown(portId, sp); | 209 runningIsolates.isolateShutdown(portId, sp); |
215 IsolateEmbedderData ied = isolateEmbedderData.remove(portId); | 210 IsolateEmbedderData ied = isolateEmbedderData.remove(portId); |
216 if (ied != null) { | 211 if (ied != null) { |
217 ied.cleanup(); | 212 ied.cleanup(); |
218 } | 213 } |
219 break; | 214 break; |
220 } | 215 } |
221 } | 216 } |
222 | 217 |
223 Future<Null> _serverMessageHandler(int code, SendPort sp, bool enable) async { | 218 Future<Null> _serverMessageHandler(int code, SendPort sp, bool enable) async { |
224 switch (code) { | 219 switch (code) { |
225 case Constants.WEB_SERVER_CONTROL_MESSAGE_ID: | 220 case Constants.WEB_SERVER_CONTROL_MESSAGE_ID: |
226 if (VMServiceEmbedderHooks.webServerControl == null) { | 221 if (VMServiceEmbedderHooks.webServerControl == null) { |
227 sp.send(null); | 222 sp.send(null); |
228 return; | 223 return; |
229 } | 224 } |
230 Uri uri = await VMServiceEmbedderHooks.webServerControl(enable); | 225 Uri uri = await VMServiceEmbedderHooks.webServerControl(enable); |
231 sp.send(uri); | 226 sp.send(uri); |
232 break; | 227 break; |
233 case Constants.SERVER_INFO_MESSAGE_ID: | 228 case Constants.SERVER_INFO_MESSAGE_ID: |
234 if (VMServiceEmbedderHooks.serverInformation == null) { | 229 if (VMServiceEmbedderHooks.serverInformation == null) { |
235 sp.send(null); | 230 sp.send(null); |
236 return; | 231 return; |
237 } | 232 } |
238 Uri uri = await VMServiceEmbedderHooks.serverInformation(); | 233 Uri uri = await VMServiceEmbedderHooks.serverInformation(); |
239 sp.send(uri); | 234 sp.send(uri); |
240 break; | 235 break; |
241 } | 236 } |
242 } | 237 } |
243 | 238 |
244 Future _exit() async { | 239 Future _exit() async { |
245 // Stop the server. | 240 // Stop the server. |
246 if (VMServiceEmbedderHooks.serverStop != null) { | 241 if (VMServiceEmbedderHooks.serverStop != null) { |
247 await VMServiceEmbedderHooks.serverStop(); | 242 await VMServiceEmbedderHooks.serverStop(); |
248 } | 243 } |
249 | 244 |
250 // Close receive ports. | 245 // Close receive ports. |
(...skipping 25 matching lines...) Expand all Loading... |
276 } | 271 } |
277 if (message.length == 1) { | 272 if (message.length == 1) { |
278 // This is a control message directing the vm service to exit. | 273 // This is a control message directing the vm service to exit. |
279 assert(message[0] == Constants.SERVICE_EXIT_MESSAGE_ID); | 274 assert(message[0] == Constants.SERVICE_EXIT_MESSAGE_ID); |
280 _exit(); | 275 _exit(); |
281 return; | 276 return; |
282 } | 277 } |
283 if (message.length == 3) { | 278 if (message.length == 3) { |
284 // This is a message interacting with the web server. | 279 // This is a message interacting with the web server. |
285 assert((message[0] == Constants.WEB_SERVER_CONTROL_MESSAGE_ID) || | 280 assert((message[0] == Constants.WEB_SERVER_CONTROL_MESSAGE_ID) || |
286 (message[0] == Constants.SERVER_INFO_MESSAGE_ID)); | 281 (message[0] == Constants.SERVER_INFO_MESSAGE_ID)); |
287 _serverMessageHandler(message[0], message[1], message[2]); | 282 _serverMessageHandler(message[0], message[1], message[2]); |
288 return; | 283 return; |
289 } | 284 } |
290 if (message.length == 4) { | 285 if (message.length == 4) { |
291 // This is a message informing us of the birth or death of an | 286 // This is a message informing us of the birth or death of an |
292 // isolate. | 287 // isolate. |
293 _controlMessageHandler(message[0], message[1], message[2], message[3]); | 288 _controlMessageHandler(message[0], message[1], message[2], message[3]); |
294 return; | 289 return; |
295 } | 290 } |
296 } | 291 } |
297 print('Internal vm-service error: ignoring illegal message: $message'); | 292 print('Internal vm-service error: ignoring illegal message: $message'); |
298 } | 293 } |
299 | 294 |
300 VMService._internal() | 295 VMService._internal() : eventPort = isolateControlPort { |
301 : eventPort = isolateControlPort { | |
302 eventPort.handler = messageHandler; | 296 eventPort.handler = messageHandler; |
303 } | 297 } |
304 | 298 |
305 factory VMService() { | 299 factory VMService() { |
306 if (VMService._instance == null) { | 300 if (VMService._instance == null) { |
307 VMService._instance = new VMService._internal(); | 301 VMService._instance = new VMService._internal(); |
308 _onStart(); | 302 _onStart(); |
309 } | 303 } |
310 return _instance; | 304 return _instance; |
311 } | 305 } |
312 | 306 |
313 bool _isAnyClientSubscribed(String streamId) { | 307 bool _isAnyClientSubscribed(String streamId) { |
314 for (var client in clients) { | 308 for (var client in clients) { |
315 if (client.streams.contains(streamId)) { | 309 if (client.streams.contains(streamId)) { |
316 return true; | 310 return true; |
317 } | 311 } |
318 } | 312 } |
319 return false; | 313 return false; |
320 } | 314 } |
321 | 315 |
322 Future<String> _streamListen(Message message) async { | 316 Future<String> _streamListen(Message message) async { |
323 var client = message.client; | 317 var client = message.client; |
324 var streamId = message.params['streamId']; | 318 var streamId = message.params['streamId']; |
325 | 319 |
326 if (client.streams.contains(streamId)) { | 320 if (client.streams.contains(streamId)) { |
327 return encodeRpcError(message, kStreamAlreadySubscribed); | 321 return encodeRpcError(message, kStreamAlreadySubscribed); |
328 } | 322 } |
329 if (!_isAnyClientSubscribed(streamId)) { | 323 if (!_isAnyClientSubscribed(streamId)) { |
330 if (!_vmListenStream(streamId)) { | 324 if (!_vmListenStream(streamId)) { |
331 return encodeRpcError( | 325 return encodeRpcError(message, kInvalidParams, |
332 message, kInvalidParams, | 326 details: "streamListen: invalid 'streamId' parameter: ${streamId}"); |
333 details:"streamListen: invalid 'streamId' parameter: ${streamId}"); | |
334 } | 327 } |
335 } | 328 } |
336 client.streams.add(streamId); | 329 client.streams.add(streamId); |
337 | 330 |
338 return encodeSuccess(message); | 331 return encodeSuccess(message); |
339 } | 332 } |
340 | 333 |
341 Future<String> _streamCancel(Message message) async { | 334 Future<String> _streamCancel(Message message) async { |
342 var client = message.client; | 335 var client = message.client; |
343 var streamId = message.params['streamId']; | 336 var streamId = message.params['streamId']; |
(...skipping 18 matching lines...) Expand all Loading... |
362 return encodeInvalidParamError(message, 'token'); | 355 return encodeInvalidParamError(message, 'token'); |
363 } | 356 } |
364 var uri = message.params['uri']; | 357 var uri = message.params['uri']; |
365 if (uri == null) { | 358 if (uri == null) { |
366 return encodeMissingParamError(message, 'uri'); | 359 return encodeMissingParamError(message, 'uri'); |
367 } | 360 } |
368 if (uri is! String) { | 361 if (uri is! String) { |
369 return encodeInvalidParamError(message, 'uri'); | 362 return encodeInvalidParamError(message, 'uri'); |
370 } | 363 } |
371 var args = message.params['args']; | 364 var args = message.params['args']; |
372 if (args != null && | 365 if (args != null && args is! List<String>) { |
373 args is! List<String>) { | |
374 return encodeInvalidParamError(message, 'args'); | 366 return encodeInvalidParamError(message, 'args'); |
375 } | 367 } |
376 var msg = message.params['message']; | 368 var msg = message.params['message']; |
377 | 369 |
378 Isolate.spawnUri(Uri.parse(uri), args, msg).then((isolate) { | 370 Isolate.spawnUri(Uri.parse(uri), args, msg).then((isolate) { |
379 _spawnUriNotify(isolate.controlPort, token); | 371 _spawnUriNotify(isolate.controlPort, token); |
380 }).catchError((e) { | 372 }).catchError((e) { |
381 _spawnUriNotify(e.toString(), token); | 373 _spawnUriNotify(e.toString(), token); |
382 }); | 374 }); |
383 | 375 |
384 return encodeSuccess(message); | 376 return encodeSuccess(message); |
385 } | 377 } |
386 | 378 |
387 static responseAsJson(portResponse) { | 379 static responseAsJson(portResponse) { |
388 if (portResponse is String) { | 380 if (portResponse is String) { |
389 return JSON.decode(portResponse); | 381 return JSON.decode(portResponse); |
390 } else { | 382 } else { |
391 var cstring = portResponse[0]; | 383 var cstring = portResponse[0]; |
392 return JSON.fuse(UTF8).decode(cstring); | 384 return JSON.fuse(UTF8).decode(cstring); |
393 } | 385 } |
394 } | 386 } |
395 | 387 |
396 // TODO(johnmccutchan): Turn this into a command line tool that uses the | 388 // TODO(johnmccutchan): Turn this into a command line tool that uses the |
397 // service library. | 389 // service library. |
398 Future<String> _getCrashDump(Message message) async { | 390 Future<String> _getCrashDump(Message message) async { |
399 var client = message.client; | 391 var client = message.client; |
400 final perIsolateRequests = [ | 392 final perIsolateRequests = [ |
401 // ?isolateId=<isolate id> will be appended to each of these requests. | 393 // ?isolateId=<isolate id> will be appended to each of these requests. |
402 // Isolate information. | 394 // Isolate information. |
403 Uri.parse('getIsolate'), | 395 Uri.parse('getIsolate'), |
404 // State of heap. | 396 // State of heap. |
405 Uri.parse('_getAllocationProfile'), | 397 Uri.parse('_getAllocationProfile'), |
406 // Call stack + local variables. | 398 // Call stack + local variables. |
407 Uri.parse('getStack?_full=true'), | 399 Uri.parse('getStack?_full=true'), |
408 ]; | 400 ]; |
409 | 401 |
410 // Snapshot of running isolates. | 402 // Snapshot of running isolates. |
411 var isolates = runningIsolates.isolates.values.toList(); | 403 var isolates = runningIsolates.isolates.values.toList(); |
412 | 404 |
413 // Collect the mapping from request uris to responses. | 405 // Collect the mapping from request uris to responses. |
414 var responses = { | 406 var responses = {}; |
415 }; | |
416 | 407 |
417 // Request VM. | 408 // Request VM. |
418 var getVM = Uri.parse('getVM'); | 409 var getVM = Uri.parse('getVM'); |
419 var getVmResponse = responseAsJson( | 410 var getVmResponse = |
420 await new Message.fromUri(client, getVM).sendToVM()); | 411 responseAsJson(await new Message.fromUri(client, getVM).sendToVM()); |
421 responses[getVM.toString()] = getVmResponse['result']; | 412 responses[getVM.toString()] = getVmResponse['result']; |
422 | 413 |
423 // Request command line flags. | 414 // Request command line flags. |
424 var getFlagList = Uri.parse('getFlagList'); | 415 var getFlagList = Uri.parse('getFlagList'); |
425 var getFlagListResponse = responseAsJson( | 416 var getFlagListResponse = responseAsJson( |
426 await new Message.fromUri(client, getFlagList).sendToVM()); | 417 await new Message.fromUri(client, getFlagList).sendToVM()); |
427 responses[getFlagList.toString()] = getFlagListResponse['result']; | 418 responses[getFlagList.toString()] = getFlagListResponse['result']; |
428 | 419 |
429 // Make requests to each isolate. | 420 // Make requests to each isolate. |
430 for (var isolate in isolates) { | 421 for (var isolate in isolates) { |
(...skipping 70 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
501 external bool _vmListenStream(String streamId); | 492 external bool _vmListenStream(String streamId); |
502 | 493 |
503 /// Cancel a subscription to a service stream. | 494 /// Cancel a subscription to a service stream. |
504 external void _vmCancelStream(String streamId); | 495 external void _vmCancelStream(String streamId); |
505 | 496 |
506 /// Get the bytes to the tar archive. | 497 /// Get the bytes to the tar archive. |
507 external Uint8List _requestAssets(); | 498 external Uint8List _requestAssets(); |
508 | 499 |
509 /// Notify the vm service that an isolate has been spawned via rpc. | 500 /// Notify the vm service that an isolate has been spawned via rpc. |
510 external void _spawnUriNotify(obj, String token); | 501 external void _spawnUriNotify(obj, String token); |
OLD | NEW |