Provide an API to dart:developer to control the web server hosting the Service Protocol

- [x] Add `ServiceProtocolnfo` class to dart:developer.
- [x] Add `Service` class to dart:developer.
- [x] Add `Service.getInfo` static method to dart:developer.
- [x] Add `Service.controlWebServer` static method to dart:developer.

API:

```dart

/// Information about the service protocol.
class ServiceProtocolInfo {
  /// The major version of the protocol.
  final int majorVersion;
  /// The minor version of the protocol.
  final int minorVersion;
  /// The Uri to access the service. If the web server is not running, this
  /// will be null.
  final Uri serverUri;
}

/// Access information about the service protocol and control the web server.
class Service {
  /// Get information about the service protocol.
  static Future<ServiceProtocolInfo> getInfo();

  /// Control the web server that the service protocol is accessed through.
  static Future<ServiceProtocolInfo> controlWebServer({bool enable: false});
}
```

... and add a randomly generated authentication token path prefix that must be passed in to access the service protocol.

Old base url:

Observatory listening on http://127.0.0.1:54804/

New base url:

Observatory listening on http://127.0.0.1:54804/<token>/

For example:

Observatory listening on http://127.0.0.1:54804/PTwjm8Ii8qg=/

Many tools will need to be updated.

Fixes #23320

BUG=
R=asiva@google.com, rmacnak@google.com

Review URL: https://codereview.chromium.org/2438613002 .
This commit is contained in:
John McCutchan 2016-10-31 12:32:23 -07:00
parent b1dfa2aee1
commit 63e4f69e5e
25 changed files with 495 additions and 179 deletions

View file

@ -1,12 +1,16 @@
## 1.21.0
### Core library changes
* `dart:core`: `Set.difference` now takes a `Set<Object>` as argument.
### Language
* Allow `=` as well as `:` as separator for named parameter default values.
### Core library changes
* `dart:core`: `Set.difference` now takes a `Set<Object>` as argument.
* `dart:developer`:
* The service protocol http server can now be controlled from Dart code.
## 1.20.1 - 2016-10-13
Patch release, resolves one issue:

View file

@ -126,12 +126,23 @@ class Server {
final bool _originCheckDisabled;
HttpServer _server;
bool get running => _server != null;
bool _displayMessages = false;
Server(this._service, this._ip, this._port, this._originCheckDisabled) {
_displayMessages = (_ip != '127.0.0.1' || _port != 8181);
/// Returns the server address including the auth token.
Uri get serverAddress {
if (!running) {
return null;
}
var ip = _server.address.address;
var port = _server.port;
if (useAuthToken) {
return Uri.parse('http://$ip:$port/$serviceAuthToken/');
} else {
return Uri.parse('http://$ip:$port/');
}
}
Server(this._service, this._ip, this._port, this._originCheckDisabled);
bool _isAllowedOrigin(String origin) {
Uri uri;
try {
@ -179,6 +190,28 @@ class Server {
return false;
}
/// Checks the [requestUri] for the service auth token and returns the path.
/// If the service auth token check fails, returns null.
String _checkAuthTokenAndGetPath(Uri requestUri) {
if (!useAuthToken) {
return requestUri.path == '/' ? ROOT_REDIRECT_PATH : requestUri.path;
}
final List<String> requestPathSegments = requestUri.pathSegments;
if (requestPathSegments.length < 2) {
// Malformed.
return null;
}
// Check that we were given the auth token.
final String authToken = requestPathSegments[0];
if (authToken != serviceAuthToken) {
// Malformed.
return null;
}
// Construct the actual request path by chopping off the auth token.
return (requestPathSegments[1] == '') ?
ROOT_REDIRECT_PATH : '/${requestPathSegments.sublist(1).join('/')}';
}
Future _requestHandler(HttpRequest request) async {
if (!_originCheck(request)) {
// This is a cross origin attempt to connect
@ -231,8 +264,12 @@ class Server {
return;
}
final String path =
request.uri.path == '/' ? ROOT_REDIRECT_PATH : request.uri.path;
final String path = _checkAuthTokenAndGetPath(request.uri);
if (path == null) {
// Malformed.
request.response.close();
return;
}
if (path == WEBSOCKET_PATH) {
WebSocketTransformer.upgrade(request).then(
@ -274,18 +311,14 @@ class Server {
return HttpServer.bind(address, _port).then((s) {
_server = s;
_server.listen(_requestHandler, cancelOnError: true);
var ip = _server.address.address;
var port = _server.port;
if (_displayMessages) {
print('Observatory listening on http://$ip:$port');
}
print('Observatory listening on $serverAddress');
// Server is up and running.
_notifyServerState(ip, _server.port);
onServerAddressChange('http://$ip:$port');
_notifyServerState(serverAddress.toString());
onServerAddressChange('$serverAddress');
return this;
}).catchError((e, st) {
print('Could not start Observatory HTTP server:\n$e\n$st\n');
_notifyServerState("", 0);
_notifyServerState("");
onServerAddressChange(null);
return this;
});
@ -304,24 +337,18 @@ class Server {
return new Future.value(this);
}
// Force displaying of status messages if we are forcibly shutdown.
_displayMessages = _displayMessages || forced;
// Shutdown HTTP server and subscription.
var ip = _server.address.address.toString();
var port = _server.port.toString();
String oldServerAddress = serverAddress;
return cleanup(forced).then((_) {
if (_displayMessages) {
print('Observatory no longer listening on http://$ip:$port');
}
print('Observatory no longer listening on $oldServerAddress');
_server = null;
_notifyServerState("", 0);
_notifyServerState("");
onServerAddressChange(null);
return this;
}).catchError((e, st) {
_server = null;
print('Could not shutdown Observatory HTTP server:\n$e\n$st\n');
_notifyServerState("", 0);
_notifyServerState("");
onServerAddressChange(null);
return this;
});
@ -329,5 +356,5 @@ class Server {
}
void _notifyServerState(String ip, int port)
void _notifyServerState(String uri)
native "VMServiceIO_NotifyServerState";

View file

@ -162,6 +162,27 @@ Future<List<Map<String,String>>> listFilesCallback(Uri dirPath) async {
return result;
}
Future<Uri> serverInformationCallback() async {
_lazyServerBoot();
return server.serverAddress;
}
Future<Uri> webServerControlCallback(bool enable) async {
_lazyServerBoot();
if (server.running == enable) {
// No change.
return server.serverAddress;
}
if (enable) {
await server.startup();
return server.serverAddress;
} else {
await server.shutdown(true);
return server.serverAddress;
}
}
_clearFuture(_) {
serverFuture = null;
}
@ -204,6 +225,8 @@ main() {
VMServiceEmbedderHooks.writeStreamFile = writeStreamFileCallback;
VMServiceEmbedderHooks.readFile = readFileCallback;
VMServiceEmbedderHooks.listFiles = listFilesCallback;
VMServiceEmbedderHooks.serverInformation = serverInformationCallback;
VMServiceEmbedderHooks.webServerControl = webServerControlCallback;
// Always instantiate the vmservice object so that the exit message
// can be delivered and waiting loaders can be cancelled.
var service = new VMService();

View file

@ -77,13 +77,8 @@ Dart_Isolate VmServiceServer::CreateIsolate(const uint8_t* snapshot_buffer) {
}
const char* VmServiceServer::GetServerIP() {
return VmService::GetServerIP();
}
intptr_t VmServiceServer::GetServerPort() {
return VmService::GetServerPort();
const char* VmServiceServer::GetServerAddress() {
return VmService::GetServerAddress();
}

View file

@ -18,8 +18,7 @@ class VmServiceServer {
static void Bootstrap();
static Dart_Isolate CreateIsolate(const uint8_t* snapshot_buffer);
static const char* GetServerIP();
static intptr_t GetServerPort();
static const char* GetServerAddress();
static void DecompressAssets(const uint8_t* input, unsigned int input_len,
uint8_t** output, unsigned int* output_length);

View file

@ -91,27 +91,20 @@ class Resources {
void NotifyServerState(Dart_NativeArguments args) {
Dart_EnterScope();
const char* ip_chars;
Dart_Handle ip_arg = Dart_GetNativeArgument(args, 0);
if (Dart_IsError(ip_arg)) {
VmService::SetServerIPAndPort("", 0);
const char* uri_chars;
Dart_Handle uri_arg = Dart_GetNativeArgument(args, 0);
if (Dart_IsError(uri_arg)) {
VmService::SetServerAddress("");
Dart_ExitScope();
return;
}
Dart_Handle result = Dart_StringToCString(ip_arg, &ip_chars);
Dart_Handle result = Dart_StringToCString(uri_arg, &uri_chars);
if (Dart_IsError(result)) {
VmService::SetServerIPAndPort("", 0);
VmService::SetServerAddress("");
Dart_ExitScope();
return;
}
Dart_Handle port_arg = Dart_GetNativeArgument(args, 1);
if (Dart_IsError(port_arg)) {
VmService::SetServerIPAndPort("", 0);
Dart_ExitScope();
return;
}
int64_t port = DartUtils::GetInt64ValueCheckRange(port_arg, 0, 65535);
VmService::SetServerIPAndPort(ip_chars, port);
VmService::SetServerAddress(uri_chars);
Dart_ExitScope();
}
@ -129,7 +122,7 @@ struct VmServiceIONativeEntry {
static VmServiceIONativeEntry _VmServiceIONativeEntries[] = {
{"VMServiceIO_NotifyServerState", 2, NotifyServerState},
{"VMServiceIO_NotifyServerState", 1, NotifyServerState},
{"VMServiceIO_Shutdown", 0, Shutdown },
};
@ -156,8 +149,7 @@ static Dart_NativeFunction VmServiceIONativeResolver(Dart_Handle name,
const char* VmService::error_msg_ = NULL;
char VmService::server_ip_[kServerIpStringBufferSize];
intptr_t VmService::server_port_ = 0;
char VmService::server_uri_[kServerUriStringBufferSize];
bool VmService::LoadForGenPrecompiled() {
@ -180,7 +172,7 @@ bool VmService::Setup(const char* server_ip,
bool dev_mode_server) {
Dart_Isolate isolate = Dart_CurrentIsolate();
ASSERT(isolate != NULL);
SetServerIPAndPort("", 0);
SetServerAddress("");
Dart_Handle result;
@ -290,13 +282,16 @@ const char* VmService::GetErrorMessage() {
}
void VmService::SetServerIPAndPort(const char* ip, intptr_t port) {
if (ip == NULL) {
ip = "";
void VmService::SetServerAddress(const char* server_uri) {
if (server_uri == NULL) {
server_uri = "";
}
strncpy(server_ip_, ip, kServerIpStringBufferSize);
server_ip_[kServerIpStringBufferSize - 1] = '\0';
server_port_ = port;
const intptr_t server_uri_len = strlen(server_uri);
if (server_uri_len >= (kServerUriStringBufferSize - 1)) {
FATAL1("vm-service: Server URI exceeded length: %s\n", server_uri);
}
strncpy(server_uri_, server_uri, kServerUriStringBufferSize);
server_uri_[kServerUriStringBufferSize - 1] = '\0';
}

View file

@ -24,21 +24,16 @@ class VmService {
// Error message if startup failed.
static const char* GetErrorMessage();
// HTTP server's IP.
static const char* GetServerIP() {
return &server_ip_[0];
}
// HTTP server's port.
static intptr_t GetServerPort() {
return server_port_;
// HTTP Server's address.
static const char* GetServerAddress() {
return &server_uri_[0];
}
private:
static const intptr_t kServerIpStringBufferSize = 256;
static const intptr_t kServerUriStringBufferSize = 1024;
friend void NotifyServerState(Dart_NativeArguments args);
static void SetServerIPAndPort(const char* ip, intptr_t port);
static void SetServerAddress(const char* server_uri_);
static Dart_Handle GetSource(const char* name);
static Dart_Handle LoadScript(const char* name);
static Dart_Handle LoadLibrary(const char* name);
@ -49,8 +44,7 @@ class VmService {
Dart_Handle url);
static const char* error_msg_;
static char server_ip_[kServerIpStringBufferSize];
static intptr_t server_port_;
static char server_uri_[kServerUriStringBufferSize];
DISALLOW_ALLOCATION();
DISALLOW_IMPLICIT_CONSTRUCTORS(VmService);

View file

@ -9,6 +9,7 @@
#include "vm/debugger.h"
#include "vm/exceptions.h"
#include "vm/flags.h"
#include "vm/message.h"
#include "vm/native_entry.h"
#include "vm/object.h"
#include "vm/object_store.h"
@ -120,4 +121,61 @@ DEFINE_NATIVE_ENTRY(Developer_registerExtension, 2) {
#endif // PRODUCT
}
DEFINE_NATIVE_ENTRY(Developer_getServiceMajorVersion, 0) {
#if defined(PRODUCT)
return Smi::New(0);
#else
return Smi::New(SERVICE_PROTOCOL_MAJOR_VERSION);
#endif
}
DEFINE_NATIVE_ENTRY(Developer_getServiceMinorVersion, 0) {
#if defined(PRODUCT)
return Smi::New(0);
#else
return Smi::New(SERVICE_PROTOCOL_MINOR_VERSION);
#endif
}
static void SendNull(const SendPort& port) {
const Dart_Port destination_port_id = port.Id();
PortMap::PostMessage(new Message(
destination_port_id, Object::null(), Message::kNormalPriority));
}
DEFINE_NATIVE_ENTRY(Developer_getServerInfo, 1) {
GET_NON_NULL_NATIVE_ARGUMENT(SendPort, port, arguments->NativeArgAt(0));
#if defined(PRODUCT)
SendNull(port);
return Object::null();
#else
if (!ServiceIsolate::IsRunning()) {
SendNull(port);
} else {
ServiceIsolate::RequestServerInfo(port);
}
return Object::null();
#endif
}
DEFINE_NATIVE_ENTRY(Developer_webServerControl, 2) {
GET_NON_NULL_NATIVE_ARGUMENT(SendPort, port, arguments->NativeArgAt(0));
#if defined(PRODUCT)
SendNull(port);
return Object::null();
#else
GET_NON_NULL_NATIVE_ARGUMENT(Bool, enabled, arguments->NativeArgAt(1));
if (!ServiceIsolate::IsRunning()) {
SendNull(port);
} else {
ServiceIsolate::ControlWebServer(port, enabled.value());
}
return Object::null();
#endif
}
} // namespace dart

View file

@ -149,3 +149,12 @@ _postResponse(SendPort replyPort,
}
replyPort.send(sb.toString());
}
@patch int _getServiceMajorVersion() native "Developer_getServiceMajorVersion";
@patch int _getServiceMinorVersion() native "Developer_getServiceMinorVersion";
@patch void _getServerInfo(SendPort sp) native "Developer_getServerInfo";
@patch void _webServerControl(SendPort sp, bool enable)
native "Developer_webServerControl";

View file

@ -239,6 +239,7 @@ class ObservatoryApplication {
final bool currentTargetConnected = (_vm != null) && !_vm.isDisconnected;
if (!currentTarget || !currentTargetConnected) {
_switchVM(new WebSocketVM(targets.current));
app.locationManager.go(Uris.vm());
}
});

View file

@ -12,6 +12,7 @@ import 'package:observatory/app.dart';
import 'package:observatory/src/elements/helpers/tag.dart';
import 'package:observatory/src/elements/helpers/rendering_scheduler.dart';
import 'package:observatory/src/elements/helpers/nav_bar.dart';
import 'package:observatory/src/elements/helpers/uris.dart';
import 'package:observatory/src/elements/nav/notify.dart';
import 'package:observatory/src/elements/nav/top_menu.dart';
import 'package:observatory/src/elements/view_footer.dart';
@ -119,7 +120,7 @@ class VMConnectElement extends HtmlElement implements Renderable {
..text = 'Connect'
..onClick.listen((e) {
e.preventDefault();
_create();
_createAndConnect();
}),
],
new BRElement(),
@ -150,11 +151,11 @@ class VMConnectElement extends HtmlElement implements Renderable {
TextInputElement _createAddressBox() {
var textbox = new TextInputElement()
..classes = ['textbox']
..placeholder = '127.0.0.1:8181'
..placeholder = 'http://127.0.0.1:8181/...'
..value = _address
..onKeyUp.where((e) => e.key == '\n').listen((e) {
e.preventDefault();
_create();
_createAndConnect();
});
textbox.onInput.listen((e) {
_address = textbox.value;
@ -176,9 +177,14 @@ class VMConnectElement extends HtmlElement implements Renderable {
return e;
}
void _create() {
void _createAndConnect() {
if (_address == null || _address.isEmpty) return;
_targets.add(_normalizeStandaloneAddress(_address));
String normalizedNetworkAddress = _normalizeStandaloneAddress(_address);
_targets.add(normalizedNetworkAddress);
var target = _targets.find(normalizedNetworkAddress);
assert(target != null);
_targets.setCurrent(target);
ObservatoryApplication.app.locationManager.go(Uris.vm());
}
void _connect(TargetEvent e) {
@ -194,8 +200,7 @@ class VMConnectElement extends HtmlElement implements Renderable {
}
try {
Uri uri = Uri.parse(networkAddress);
print('returning ${uri.host} ${uri.port}');
return 'ws://${uri.host}:${uri.port}/ws';
return 'ws://${uri.authority}${uri.path}ws';
} catch (e) {
print('caught exception with: $networkAddress -- $e');
return networkAddress;

View file

@ -29,15 +29,15 @@ class TargetRepository implements M.TargetRepository {
TargetRepository._(this._onChange, this.onChange) {
_restore();
// Add the default address if it doesn't already exist.
if (_find(_networkAddressOfDefaultTarget()) == null) {
if (find(_networkAddressOfDefaultTarget()) == null) {
add(_networkAddressOfDefaultTarget());
}
// Set the current target to the default target.
current = _find(_networkAddressOfDefaultTarget());
current = find(_networkAddressOfDefaultTarget());
}
void add(String address) {
if (_find(address) != null) {
if (find(address) != null) {
return;
}
_list.insert(0, new SC.WebSocketVMTarget(address));
@ -91,7 +91,7 @@ class TargetRepository implements M.TargetRepository {
}
/// Find by networkAddress.
SC.WebSocketVMTarget _find(String networkAddress) {
SC.WebSocketVMTarget find(String networkAddress) {
for (SC.WebSocketVMTarget item in _list) {
if (item.networkAddress == networkAddress) {
return item;
@ -101,14 +101,7 @@ class TargetRepository implements M.TargetRepository {
}
static String _networkAddressOfDefaultTarget() {
if (Utils.runningInJavaScript()) {
// We are running as JavaScript, use the same host that Observatory has
// been loaded from.
return 'ws://${window.location.host}/ws';
} else {
// Otherwise, assume we are running from Dart Editor and want to connect
// to the default host.
return 'ws://localhost:8181/ws';
}
Uri serverAddress = Uri.parse(window.location.toString());
return 'ws://${serverAddress.authority}${serverAddress.path}ws';
}
}

View file

@ -0,0 +1,34 @@
// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
// VMOptions=--error_on_bad_type --error_on_bad_override
import 'dart:async';
import 'dart:developer';
import 'package:observatory/service_io.dart';
import 'package:unittest/unittest.dart';
import 'test_helper.dart';
Future<Null> testeeBefore() async {
print('testee before');
print(await Service.getInfo());
// Start the web server.
ServiceProtocolInfo info = await Service.controlWebServer(enable: true);
print(info);
}
var tests = [
(Isolate isolate) async {
await isolate.reload();
// Just getting here means that the testee enabled the service protocol
// web server.
expect(true, true);
}
];
main(args) => runIsolateTests(args,
tests,
testeeBefore: testeeBefore,
// the testee is responsible for starting the
// web server.
testeeControlsServer: true);

View file

@ -91,25 +91,29 @@ class _ServiceTesteeLauncher {
bool pause_on_exit,
bool pause_on_unhandled_exceptions,
bool trace_service,
bool trace_compiler) {
bool trace_compiler,
bool testeeControlsServer) {
assert(pause_on_start != null);
assert(pause_on_exit != null);
assert(pause_on_unhandled_exceptions != null);
assert(trace_service != null);
assert(trace_compiler != null);
assert(testeeControlsServer != null);
if (_shouldLaunchSkyShell()) {
return _spawnSkyProcess(pause_on_start,
pause_on_exit,
pause_on_unhandled_exceptions,
trace_service,
trace_compiler);
trace_compiler,
testeeControlsServer);
} else {
return _spawnDartProcess(pause_on_start,
pause_on_exit,
pause_on_unhandled_exceptions,
trace_service,
trace_compiler);
trace_compiler,
testeeControlsServer);
}
}
@ -117,7 +121,8 @@ class _ServiceTesteeLauncher {
bool pause_on_exit,
bool pause_on_unhandled_exceptions,
bool trace_service,
bool trace_compiler) {
bool trace_compiler,
bool testeeControlsServer) {
assert(!_shouldLaunchSkyShell());
String dartExecutable = Platform.executable;
@ -141,7 +146,9 @@ class _ServiceTesteeLauncher {
}
fullArgs.addAll(Platform.executableArguments);
fullArgs.add('--enable-vm-service:0');
if (!testeeControlsServer) {
fullArgs.add('--enable-vm-service:0');
}
fullArgs.addAll(args);
return _spawnCommon(dartExecutable, fullArgs);
@ -179,7 +186,9 @@ class _ServiceTesteeLauncher {
dartFlags.add('--enable_mirrors=true');
fullArgs.addAll(Platform.executableArguments);
fullArgs.add('--observatory-port=0');
if (!testeeControlsServer) {
fullArgs.add('--observatory-port=0');
}
fullArgs.add('--dart-flags=${dartFlags.join(' ')}');
fullArgs.addAll(args);
@ -194,37 +203,38 @@ class _ServiceTesteeLauncher {
return Process.start(executable, arguments, environment: environment);
}
Future<int> launch(bool pause_on_start,
Future<Uri> launch(bool pause_on_start,
bool pause_on_exit,
bool pause_on_unhandled_exceptions,
bool trace_service,
bool trace_compiler) {
bool trace_compiler,
bool testeeControlsServer) {
return _spawnProcess(pause_on_start,
pause_on_exit,
pause_on_unhandled_exceptions,
trace_service,
trace_compiler).then((p) {
Completer completer = new Completer();
trace_compiler,
testeeControlsServer).then((p) {
Completer<Uri> completer = new Completer<Uri>();
process = p;
var portNumber;
Uri uri;
var blank;
var first = true;
process.stdout.transform(UTF8.decoder)
.transform(new LineSplitter()).listen((line) {
if (line.startsWith('Observatory listening on http://')) {
RegExp portExp = new RegExp(r"\d+.\d+.\d+.\d+:(\d+)");
var port = portExp.firstMatch(line).group(1);
portNumber = int.parse(port);
const kObservatoryListening = 'Observatory listening on ';
if (line.startsWith(kObservatoryListening)) {
uri = Uri.parse(line.substring(kObservatoryListening.length));
}
if (pause_on_start || line == '') {
// Received blank line.
blank = true;
}
if (portNumber != null && blank == true && first == true) {
completer.complete(portNumber);
if ((uri != null) && (blank == true) && (first == true)) {
completer.complete(uri);
// Stop repeat completions.
first = false;
print('** Signaled to run test queries on $portNumber');
print('** Signaled to run test queries on $uri');
}
print('>testee>out> $line');
});
@ -250,62 +260,11 @@ class _ServiceTesteeLauncher {
}
}
// A tester runner that doesn't spawn a process but instead connects to
// an already running flutter application running on a device. Assumes
// port 8100. This is only useful for debugging.
class _FlutterDeviceServiceTesterRunner {
void run({List<String> mainArgs,
List<VMTest> vmTests,
List<IsolateTest> isolateTests,
bool pause_on_start: false,
bool pause_on_exit: false,
bool trace_service: false,
bool trace_compiler: false,
bool verbose_vm: false,
bool pause_on_unhandled_exceptions: false}) {
var port = 8100;
serviceWebsocketAddress = 'ws://localhost:$port/ws';
serviceHttpAddress = 'http://localhost:$port';
var name = Platform.script.pathSegments.last;
Chain.capture(() async {
var vm =
new WebSocketVM(new WebSocketVMTarget(serviceWebsocketAddress));
print('Loading VM...');
await vm.load();
print('Done loading VM');
// Run vm tests.
if (vmTests != null) {
var testIndex = 1;
var totalTests = vmTests.length;
for (var test in vmTests) {
vm.verbose = verbose_vm;
print('Running $name [$testIndex/$totalTests]');
testIndex++;
await test(vm);
}
}
// Run isolate tests.
if (isolateTests != null) {
var isolate = await vm.isolates.first.load();
var testIndex = 1;
var totalTests = isolateTests.length;
for (var test in isolateTests) {
vm.verbose = verbose_vm;
print('Running $name [$testIndex/$totalTests]');
testIndex++;
await test(isolate);
}
}
}, onError: (error, stackTrace) {
print('Unexpected exception in service tests: $error\n$stackTrace');
});
}
}
void suppressWarning() {
new _FlutterDeviceServiceTesterRunner();
void setupAddresses(Uri serverAddress) {
serviceWebsocketAddress =
'ws://${serverAddress.authority}${serverAddress.path}ws';
serviceHttpAddress =
'http://${serverAddress.authority}${serverAddress.path}';
}
class _ServiceTesterRunner {
@ -317,19 +276,20 @@ class _ServiceTesterRunner {
bool trace_service: false,
bool trace_compiler: false,
bool verbose_vm: false,
bool pause_on_unhandled_exceptions: false}) {
bool pause_on_unhandled_exceptions: false,
bool testeeControlsServer: false}) {
var process = new _ServiceTesteeLauncher();
process.launch(pause_on_start, pause_on_exit,
pause_on_unhandled_exceptions,
trace_service, trace_compiler).then((port) async {
trace_service, trace_compiler,
testeeControlsServer).then((Uri serverAddress) async {
if (mainArgs.contains("--gdb")) {
var pid = process.process.pid;
var wait = new Duration(seconds: 10);
print("Testee has pid $pid, waiting $wait before continuing");
sleep(wait);
}
serviceWebsocketAddress = 'ws://localhost:$port/ws';
serviceHttpAddress = 'http://localhost:$port';
setupAddresses(serverAddress);
var name = Platform.script.pathSegments.last;
Chain.capture(() async {
var vm =
@ -386,7 +346,8 @@ Future runIsolateTests(List<String> mainArgs,
bool trace_service: false,
bool trace_compiler: false,
bool verbose_vm: false,
bool pause_on_unhandled_exceptions: false}) async {
bool pause_on_unhandled_exceptions: false,
bool testeeControlsServer: false}) async {
assert(!pause_on_start || testeeBefore == null);
if (_isTestee()) {
new _ServiceTesteeRunner().run(testeeBefore: testeeBefore,
@ -402,7 +363,8 @@ Future runIsolateTests(List<String> mainArgs,
trace_service: trace_service,
trace_compiler: trace_compiler,
verbose_vm: verbose_vm,
pause_on_unhandled_exceptions: pause_on_unhandled_exceptions);
pause_on_unhandled_exceptions: pause_on_unhandled_exceptions,
testeeControlsServer: testeeControlsServer);
}
}

View file

@ -70,11 +70,15 @@ namespace dart {
V(Bigint_getDigits, 1) \
V(Bigint_allocate, 4) \
V(Developer_debugger, 2) \
V(Developer_getServerInfo, 1) \
V(Developer_getServiceMajorVersion, 0) \
V(Developer_getServiceMinorVersion, 0) \
V(Developer_inspect, 1) \
V(Developer_lookupExtension, 1) \
V(Developer_registerExtension, 2) \
V(Developer_log, 8) \
V(Developer_postEvent, 2) \
V(Developer_webServerControl, 2) \
V(Double_getIsNegative, 1) \
V(Double_getIsInfinite, 1) \
V(Double_getIsNaN, 1) \

View file

@ -3833,8 +3833,10 @@ static const MethodParameter* get_version_params[] = {
static bool GetVersion(Thread* thread, JSONStream* js) {
JSONObject jsobj(js);
jsobj.AddProperty("type", "Version");
jsobj.AddProperty("major", static_cast<intptr_t>(3));
jsobj.AddProperty("minor", static_cast<intptr_t>(5));
jsobj.AddProperty("major",
static_cast<intptr_t>(SERVICE_PROTOCOL_MAJOR_VERSION));
jsobj.AddProperty("minor",
static_cast<intptr_t>(SERVICE_PROTOCOL_MINOR_VERSION));
jsobj.AddProperty("_privateMajor", static_cast<intptr_t>(0));
jsobj.AddProperty("_privateMinor", static_cast<intptr_t>(0));
return true;

View file

@ -13,6 +13,9 @@
namespace dart {
#define SERVICE_PROTOCOL_MAJOR_VERSION 3
#define SERVICE_PROTOCOL_MINOR_VERSION 5
class Array;
class EmbedderServiceHandler;
class Error;

View file

@ -43,6 +43,9 @@ static uint8_t* allocator(uint8_t* ptr, intptr_t old_size, intptr_t new_size) {
#define VM_SERVICE_ISOLATE_STARTUP_MESSAGE_ID 1
#define VM_SERVICE_ISOLATE_SHUTDOWN_MESSAGE_ID 2
#define VM_SERVICE_WEB_SERVER_CONTROL_MESSAGE_ID 3
#define VM_SERVICE_SERVER_INFO_MESSAGE_ID 4
static RawArray* MakeServiceControlMessage(Dart_Port port_id, intptr_t code,
const String& name) {
const Array& list = Array::Handle(Array::New(4));
@ -58,6 +61,18 @@ static RawArray* MakeServiceControlMessage(Dart_Port port_id, intptr_t code,
}
static RawArray* MakeServerControlMessage(const SendPort& sp,
intptr_t code,
bool enable = false) {
const Array& list = Array::Handle(Array::New(3));
ASSERT(!list.IsNull());
list.SetAt(0, Integer::Handle(Integer::New(code)));
list.SetAt(1, sp);
list.SetAt(2, Bool::Get(enable));
return list.raw();
}
static RawArray* MakeServiceExitMessage() {
const Array& list = Array::Handle(Array::New(1));
ASSERT(!list.IsNull());
@ -81,6 +96,36 @@ bool ServiceIsolate::initializing_ = true;
bool ServiceIsolate::shutting_down_ = false;
char* ServiceIsolate::server_address_ = NULL;
void ServiceIsolate::RequestServerInfo(const SendPort& sp) {
const Array& message =
Array::Handle(
MakeServerControlMessage(sp,
VM_SERVICE_SERVER_INFO_MESSAGE_ID,
false /* ignored */));
ASSERT(!message.IsNull());
uint8_t* data = NULL;
MessageWriter writer(&data, &allocator, false);
writer.WriteMessage(message);
intptr_t len = writer.BytesWritten();
PortMap::PostMessage(new Message(port_, data, len, Message::kNormalPriority));
}
void ServiceIsolate::ControlWebServer(const SendPort& sp, bool enable) {
const Array& message =
Array::Handle(
MakeServerControlMessage(sp,
VM_SERVICE_WEB_SERVER_CONTROL_MESSAGE_ID,
enable));
ASSERT(!message.IsNull());
uint8_t* data = NULL;
MessageWriter writer(&data, &allocator, false);
writer.WriteMessage(message);
intptr_t len = writer.BytesWritten();
PortMap::PostMessage(new Message(port_, data, len, Message::kNormalPriority));
}
void ServiceIsolate::SetServerAddress(const char* address) {
if (server_address_ != NULL) {
free(server_address_);

View file

@ -13,6 +13,7 @@
namespace dart {
class ObjectPointerVisitor;
class SendPort;
class ServiceIsolate : public AllStatic {
public:
@ -36,6 +37,9 @@ class ServiceIsolate : public AllStatic {
static void BootVmServiceLibrary();
static void RequestServerInfo(const SendPort& sp);
static void ControlWebServer(const SendPort& sp, bool enable);
static void SetServerAddress(const char* address);
// Returns the server's web address or NULL if none is running.

View file

@ -105,3 +105,23 @@ void _reportTaskEvent(int start,
String argumentsAsJson) {
// TODO.
}
@patch
int _getServiceMajorVersion() {
return 0;
}
@patch
int _getServiceMinorVersion() {
return 0;
}
@patch
void _getServerInfo(SendPort sp) {
sp.send(null);
}
@patch
void _webServerControl(SendPort sp, bool enable) {
sp.send(null);
}

View file

@ -21,6 +21,7 @@ import 'dart:convert';
part 'extension.dart';
part 'profiler.dart';
part 'timeline.dart';
part 'service.dart';
/// If [when] is true, stop the program as if a breakpoint were hit at the
/// following statement.

View file

@ -8,6 +8,7 @@
# The above file needs to be first if additional parts are added to the lib.
'extension.dart',
'profiler.dart',
'service.dart',
'timeline.dart',
],
}

View file

@ -0,0 +1,81 @@
// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
part of dart.developer;
/// Information about the service protocol.
class ServiceProtocolInfo {
/// The major version of the protocol. If the running Dart environment does
/// not support the service protocol, this is 0.
final int majorVersion = _getServiceMajorVersion();
/// The minor version of the protocol. If the running Dart environment does
/// not support the service protocol, this is 0.
final int minorVersion = _getServiceMinorVersion();
/// The Uri to access the service. If the web server is not running, this
/// will be null.
final Uri serverUri;
ServiceProtocolInfo(this.serverUri);
String toString() {
if (serverUri != null) {
return 'Dart VM Service Protocol v$majorVersion.$minorVersion '
'listening on $serverUri';
} else {
return 'Dart VM Service Protocol v$majorVersion.$minorVersion';
}
}
}
/// Access information about the service protocol and control the web server.
class Service {
/// Get information about the service protocol.
static Future<ServiceProtocolInfo> getInfo() async {
// Port to receive response from service isolate.
final RawReceivePort receivePort = new RawReceivePort();
final Completer<Uri> uriCompleter = new Completer<Uri>();
receivePort.handler = (Uri uri) => uriCompleter.complete(uri);
// Request the information from the service isolate.
_getServerInfo(receivePort.sendPort);
// Await the response from the service isolate.
Uri uri = await uriCompleter.future;
// Close the port.
receivePort.close();
return new ServiceProtocolInfo(uri);
}
/// Control the web server that the service protocol is accessed through.
static Future<ServiceProtocolInfo> controlWebServer(
{bool enable: false}) async {
if (enable is! bool) {
throw new ArgumentError.value(enable,
'enable',
'Must be a bool');
}
// Port to receive response from service isolate.
final RawReceivePort receivePort = new RawReceivePort();
final Completer<Uri> uriCompleter = new Completer<Uri>();
receivePort.handler = (Uri uri) => uriCompleter.complete(uri);
// Request the information from the service isolate.
_webServerControl(receivePort.sendPort, enable);
// Await the response from the service isolate.
Uri uri = await uriCompleter.future;
// Close the port.
receivePort.close();
return new ServiceProtocolInfo(uri);
}
}
/// [sp] will receive a Uri or null.
external void _getServerInfo(SendPort sp);
/// [sp] will receive a Uri or null.
external void _webServerControl(SendPort sp, bool enable);
/// Returns the major version of the service protocol.
external int _getServiceMajorVersion();
/// Returns the minor version of the service protocol.
external int _getServiceMinorVersion();

View file

@ -9,4 +9,6 @@ class Constants {
static const int SERVICE_EXIT_MESSAGE_ID = 0;
static const int ISOLATE_STARTUP_MESSAGE_ID = 1;
static const int ISOLATE_SHUTDOWN_MESSAGE_ID = 2;
static const int WEB_SERVER_CONTROL_MESSAGE_ID = 3;
static const int SERVER_INFO_MESSAGE_ID = 4;
}

View file

@ -7,7 +7,9 @@ library dart._vmservice;
import 'dart:async';
import 'dart:collection';
import 'dart:convert';
import 'dart:developer' show ServiceProtocolInfo;
import 'dart:isolate';
import 'dart:math';
import 'dart:typed_data';
part 'asset.dart';
@ -19,13 +21,29 @@ part 'running_isolates.dart';
part 'message.dart';
part 'message_router.dart';
final RawReceivePort isolateLifecyclePort = new RawReceivePort();
final RawReceivePort isolateControlPort = new RawReceivePort();
final RawReceivePort scriptLoadPort = new RawReceivePort();
abstract class IsolateEmbedderData {
void cleanup();
}
String _makeAuthToken() {
final kTokenByteSize = 8;
Uint8List bytes = new Uint8List(kTokenByteSize);
Random random = new Random.secure();
for (int i = 0; i < kTokenByteSize; i++) {
bytes[i] = random.nextInt(256);
}
return BASE64URL.encode(bytes);
}
// The randomly generated auth token used to access the VM service.
final String serviceAuthToken = _makeAuthToken();
// TODO(johnmccutchan): Enable the auth token and drop the origin check.
final bool useAuthToken = false;
// This is for use by the embedder. It is a map from the isolateId to
// anything implementing IsolateEmbedderData. When an isolate goes away,
// the cleanup method will be invoked after being removed from the map.
@ -125,6 +143,12 @@ typedef Future<List<int>> ReadFileCallback(Uri path);
/// Called to list all files under some path.
typedef Future<List<Map<String,String>>> ListFilesCallback(Uri path);
/// Called when we need information about the server.
typedef Future<Uri> ServerInformationCallback();
/// Called when we want to [enable] or disable the web server.
typedef Future<Uri> WebServerControlCallback(bool enable);
/// Hooks that are setup by the embedder.
class VMServiceEmbedderHooks {
static ServerStartCallback serverStart;
@ -136,6 +160,8 @@ class VMServiceEmbedderHooks {
static WriteStreamFileCallback writeStreamFile;
static ReadFileCallback readFile;
static ListFilesCallback listFiles;
static ServerInformationCallback serverInformation;
static WebServerControlCallback webServerControl;
}
class VMService extends MessageRouter {
@ -194,6 +220,27 @@ class VMService extends MessageRouter {
}
}
Future<Null> _serverMessageHandler(int code, SendPort sp, bool enable) async {
switch (code) {
case Constants.WEB_SERVER_CONTROL_MESSAGE_ID:
if (VMServiceEmbedderHooks.webServerControl == null) {
sp.send(null);
return;
}
Uri uri = await VMServiceEmbedderHooks.webServerControl(enable);
sp.send(uri);
break;
case Constants.SERVER_INFO_MESSAGE_ID:
if (VMServiceEmbedderHooks.serverInformation == null) {
sp.send(null);
return;
}
Uri uri = await VMServiceEmbedderHooks.serverInformation();
sp.send(uri);
break;
}
}
Future _exit() async {
// Stop the server.
if (VMServiceEmbedderHooks.serverStop != null) {
@ -201,7 +248,7 @@ class VMService extends MessageRouter {
}
// Close receive ports.
isolateLifecyclePort.close();
isolateControlPort.close();
scriptLoadPort.close();
// Create a copy of the set as a list because client.disconnect() will
@ -233,6 +280,13 @@ class VMService extends MessageRouter {
_exit();
return;
}
if (message.length == 3) {
// This is a message interacting with the web server.
assert((message[0] == Constants.WEB_SERVER_CONTROL_MESSAGE_ID) ||
(message[0] == Constants.SERVER_INFO_MESSAGE_ID));
_serverMessageHandler(message[0], message[1], message[2]);
return;
}
if (message.length == 4) {
// This is a message informing us of the birth or death of an
// isolate.
@ -244,7 +298,7 @@ class VMService extends MessageRouter {
}
VMService._internal()
: eventPort = isolateLifecyclePort {
: eventPort = isolateControlPort {
eventPort.handler = messageHandler;
}
@ -425,8 +479,8 @@ class VMService extends MessageRouter {
}
RawReceivePort boot() {
// Return the port we expect isolate startup and shutdown messages on.
return isolateLifecyclePort;
// Return the port we expect isolate control messages on.
return isolateControlPort;
}
void _registerIsolate(int port_id, SendPort sp, String name) {