Handle new observatory url (#7061)

* reapply handle new Observatory URL changes
Fixes https://github.com/flutter/flutter/issues/6843

* allow non numeric IPv4 addresses
Fixes https://github.com/flutter/flutter/issues/7051
diff --git a/packages/flutter_tools/lib/src/android/android_device.dart b/packages/flutter_tools/lib/src/android/android_device.dart
index bf86f76..01de477 100644
--- a/packages/flutter_tools/lib/src/android/android_device.dart
+++ b/packages/flutter_tools/lib/src/android/android_device.dart
@@ -8,6 +8,7 @@
 
 import '../android/android_sdk.dart';
 import '../application_package.dart';
+import '../base/common.dart';
 import '../base/os.dart';
 import '../base/logger.dart';
 import '../base/process.dart';
@@ -260,16 +261,15 @@
     return true;
   }
 
-  Future<int> _forwardPort(String service, int devicePort, int port) async {
+  Future<int> _forwardPort(int hostPort, int devicePort) async {
     try {
-      // Set up port forwarding for observatory.
-      port = await portForwarder.forward(devicePort, hostPort: port);
-      printTrace('$service listening on http://127.0.0.1:$port');
-      return port;
+      hostPort = await portForwarder.forward(devicePort, hostPort: hostPort);
+      printTrace('Forwarded host port $hostPort to device port $devicePort');
+      return hostPort;
     } catch (e) {
-      printError('Unable to forward port $port: $e');
+      throw new ToolExit(
+          'Unable to forward host port $hostPort to device port $devicePort: $e');
     }
-    return null;
   }
 
   @override
@@ -355,36 +355,40 @@
       // device has printed "Observatory is listening on...".
       printTrace('Waiting for observatory port to be available...');
 
+      // TODO(danrubel): The iOS device class does something similar to this code below.
+      // The various Device subclasses should be refactored and common code moved into the superclass.
       try {
-        int observatoryDevicePort, diagnosticDevicePort;
+        Uri observatoryDeviceUri, diagnosticDeviceUri;
 
         if (debuggingOptions.buildMode == BuildMode.debug) {
-          Future<List<int>> scrapeServicePorts = Future.wait(
-              <Future<int>>[observatoryDiscovery.nextPort(), diagnosticDiscovery.nextPort()]
+          Future<List<Uri>> scrapeServiceUris = Future.wait(
+              <Future<Uri>>[observatoryDiscovery.nextUri(), diagnosticDiscovery.nextUri()]
           );
-          List<int> devicePorts = await scrapeServicePorts.timeout(new Duration(seconds: 20));
-          observatoryDevicePort = devicePorts[0];
-          diagnosticDevicePort = devicePorts[1];
-        } else {
-          observatoryDevicePort = await observatoryDiscovery.nextPort().timeout(new Duration(seconds: 20));
+          List<Uri> deviceUris = await scrapeServiceUris.timeout(new Duration(seconds: 20));
+          observatoryDeviceUri = deviceUris[0];
+          diagnosticDeviceUri = deviceUris[1];
+        } else if (debuggingOptions.buildMode == BuildMode.profile) {
+          observatoryDeviceUri = await observatoryDiscovery.nextUri().timeout(new Duration(seconds: 20));
         }
 
-        printTrace('observatory port on device: $observatoryDevicePort');
+        printTrace('Observatory Uri on device: $observatoryDeviceUri');
         int observatoryLocalPort = await debuggingOptions.findBestObservatoryPort();
         // TODO(devoncarew): Remember the forwarding information (so we can later remove the
         // port forwarding).
-        observatoryLocalPort = await _forwardPort(ProtocolDiscovery.kObservatoryService, observatoryDevicePort, observatoryLocalPort);
+        observatoryLocalPort = await _forwardPort(observatoryLocalPort, observatoryDeviceUri.port);
+        Uri observatoryLocalUri = observatoryDeviceUri.replace(port: observatoryLocalPort);
 
-        int diagnosticLocalPort;
-        if (diagnosticDevicePort != null) {
-          printTrace('diagnostic port on device: $diagnosticDevicePort');
-          diagnosticLocalPort = await debuggingOptions.findBestDiagnosticPort();
-          diagnosticLocalPort = await _forwardPort(ProtocolDiscovery.kDiagnosticService, diagnosticDevicePort, diagnosticLocalPort);
+        Uri diagnosticLocalUri;
+        if (diagnosticDeviceUri != null) {
+          printTrace('Diagnostic Server Uri on device: $diagnosticDeviceUri');
+          int diagnosticLocalPort = await debuggingOptions.findBestDiagnosticPort();
+          diagnosticLocalPort = await _forwardPort(diagnosticLocalPort, diagnosticDeviceUri.port);
+          diagnosticLocalUri = diagnosticDeviceUri.replace(port: diagnosticLocalPort);
         }
 
         return new LaunchResult.succeeded(
-            observatoryPort: observatoryLocalPort,
-            diagnosticPort: diagnosticLocalPort
+            observatoryUri: observatoryLocalUri,
+            diagnosticUri: diagnosticLocalUri,
         );
       } catch (error) {
         if (error is TimeoutException)
diff --git a/packages/flutter_tools/lib/src/commands/daemon.dart b/packages/flutter_tools/lib/src/commands/daemon.dart
index a1a868c..1fbffeb 100644
--- a/packages/flutter_tools/lib/src/commands/daemon.dart
+++ b/packages/flutter_tools/lib/src/commands/daemon.dart
@@ -359,8 +359,8 @@
       connectionInfoCompleter = new Completer<DebugConnectionInfo>();
       connectionInfoCompleter.future.then((DebugConnectionInfo info) {
         Map<String, dynamic> params = <String, dynamic>{
-          'port': info.port,
-          'wsUri': info.wsUri
+          'port': info.httpUri.port,
+          'wsUri': info.wsUri.toString(),
         };
         if (info.baseUri != null)
           params['baseUri'] = info.baseUri;
diff --git a/packages/flutter_tools/lib/src/commands/trace.dart b/packages/flutter_tools/lib/src/commands/trace.dart
index 7464020..1bbaf6f 100644
--- a/packages/flutter_tools/lib/src/commands/trace.dart
+++ b/packages/flutter_tools/lib/src/commands/trace.dart
@@ -55,10 +55,14 @@
   Future<Null> runCommand() async {
     int observatoryPort = int.parse(argResults['debug-port']);
 
+    // TODO(danrubel): this will break if we move to the new observatory URL
+    // See https://github.com/flutter/flutter/issues/7038
+    Uri observatoryUri = Uri.parse('http://127.0.0.1:$observatoryPort');
+
     Tracing tracing;
 
     try {
-      tracing = await Tracing.connect(observatoryPort);
+      tracing = await Tracing.connect(observatoryUri);
     } catch (error) {
       throwToolExit('Error connecting to observatory: $error');
     }
@@ -100,8 +104,8 @@
 class Tracing {
   Tracing(this.vmService);
 
-  static Future<Tracing> connect(int port) {
-    return VMService.connect(port).then((VMService observatory) => new Tracing(observatory));
+  static Future<Tracing> connect(Uri uri) {
+    return VMService.connect(uri).then((VMService observatory) => new Tracing(observatory));
   }
 
   final VMService vmService;
diff --git a/packages/flutter_tools/lib/src/device.dart b/packages/flutter_tools/lib/src/device.dart
index 67354df..1769c2f 100644
--- a/packages/flutter_tools/lib/src/device.dart
+++ b/packages/flutter_tools/lib/src/device.dart
@@ -299,22 +299,22 @@
 }
 
 class LaunchResult {
-  LaunchResult.succeeded({ this.observatoryPort, this.diagnosticPort }) : started = true;
-  LaunchResult.failed() : started = false, observatoryPort = null, diagnosticPort = null;
+  LaunchResult.succeeded({ this.observatoryUri, this.diagnosticUri }) : started = true;
+  LaunchResult.failed() : started = false, observatoryUri = null, diagnosticUri = null;
 
-  bool get hasObservatory => observatoryPort != null;
+  bool get hasObservatory => observatoryUri != null;
 
   final bool started;
-  final int observatoryPort;
-  final int diagnosticPort;
+  final Uri observatoryUri;
+  final Uri diagnosticUri;
 
   @override
   String toString() {
     StringBuffer buf = new StringBuffer('started=$started');
-    if (observatoryPort != null)
-      buf.write(', observatory=$observatoryPort');
-    if (diagnosticPort != null)
-      buf.write(', diagnostic=$diagnosticPort');
+    if (observatoryUri != null)
+      buf.write(', observatory=$observatoryUri');
+    if (diagnosticUri != null)
+      buf.write(', diagnostic=$diagnosticUri');
     return buf.toString();
   }
 }
diff --git a/packages/flutter_tools/lib/src/hot.dart b/packages/flutter_tools/lib/src/hot.dart
index ec4f3ef..c490b97 100644
--- a/packages/flutter_tools/lib/src/hot.dart
+++ b/packages/flutter_tools/lib/src/hot.dart
@@ -109,7 +109,7 @@
   final String applicationBinary;
   bool get prebuiltMode => applicationBinary != null;
   Set<String> _dartDependencies;
-  int _observatoryPort;
+  Uri _observatoryUri;
   AssetBundle _bundle;
   AssetBundle get bundle => _bundle;
   final bool benchmarkMode;
@@ -217,9 +217,9 @@
       return 2;
     }
 
-    _observatoryPort = result.observatoryPort;
+    _observatoryUri = result.observatoryUri;
     try {
-      await connectToServiceProtocol(_observatoryPort);
+      await connectToServiceProtocol(_observatoryUri);
     } catch (error) {
       printError('Error connecting to the service protocol: $error');
       return 2;
@@ -231,8 +231,8 @@
       if (connectionInfoCompleter != null) {
         connectionInfoCompleter.complete(
           new DebugConnectionInfo(
-            port: _observatoryPort,
-            wsUri: 'ws://localhost:$_observatoryPort/ws',
+            httpUri: _observatoryUri,
+            wsUri: vmService.wsAddress,
             baseUri: baseUri.toString()
           )
         );
@@ -534,7 +534,7 @@
       ansiAlternative: '$red$fire$bold  To hot reload your app on the fly, '
                        'press "r" or F5. To restart the app entirely, press "R".$reset'
     );
-    printStatus('The Observatory debugger and profiler is available at: http://127.0.0.1:$_observatoryPort/');
+    printStatus('The Observatory debugger and profiler is available at: $_observatoryUri');
     if (details) {
       printHelpDetails();
       printStatus('To repeat this help message, press "h" or F1. To quit, press "q", F10, or Ctrl-C.');
diff --git a/packages/flutter_tools/lib/src/ios/devices.dart b/packages/flutter_tools/lib/src/ios/devices.dart
index 9e05e19..cb77af7 100644
--- a/packages/flutter_tools/lib/src/ios/devices.dart
+++ b/packages/flutter_tools/lib/src/ios/devices.dart
@@ -246,8 +246,8 @@
     }
 
     int installationResult = -1;
-    int localObsPort;
-    int localDiagPort;
+    Uri localObsUri;
+    Uri localDiagUri;
 
     if (!debuggingOptions.debuggingEnabled) {
       // If debugging is not enabled, just launch the application and continue.
@@ -258,30 +258,36 @@
       // ports post launch.
       printTrace("Debugging is enabled, connecting to observatory and the diagnostic server");
 
-      Future<int> forwardObsPort = _acquireAndForwardPort(app,
-                                                          ProtocolDiscovery.kObservatoryService,
-                                                          debuggingOptions.observatoryPort);
-      Future<int> forwardDiagPort;
+      // TODO(danrubel): The Android device class does something similar to this code below.
+      // The various Device subclasses should be refactored and common code moved into the superclass.
+      Future<Uri> forwardObsUri = _acquireServiceUri(
+        app,
+        ProtocolDiscovery.kObservatoryService,
+        debuggingOptions.observatoryPort,
+      );
+      Future<Uri> forwardDiagUri;
       if (debuggingOptions.buildMode == BuildMode.debug) {
-        forwardDiagPort = _acquireAndForwardPort(app,
-                                                 ProtocolDiscovery.kDiagnosticService,
-                                                 debuggingOptions.diagnosticPort);
+        forwardDiagUri = _acquireServiceUri(
+          app,
+          ProtocolDiscovery.kDiagnosticService,
+          debuggingOptions.diagnosticPort,
+        );
       } else {
-        forwardDiagPort = new Future<int>.value(null);
+        forwardDiagUri = new Future<Uri>.value(null);
       }
 
       Future<int> launch = runCommandAndStreamOutput(launchCommand, trace: true);
 
-      List<int> ports = await launch.then((int result) async {
+      List<Uri> uris = await launch.then((int result) async {
         installationResult = result;
 
         if (result != 0) {
           printTrace("Failed to launch the application on device.");
-          return <int>[null, null];
+          return <Uri>[null, null];
         }
 
         printTrace("Application launched on the device. Attempting to forward ports.");
-        return await Future.wait(<Future<int>>[forwardObsPort, forwardDiagPort])
+        return await Future.wait(<Future<Uri>>[forwardObsUri, forwardDiagUri])
             .timeout(
                 kPortForwardTimeout,
                 onTimeout: () {
@@ -290,11 +296,11 @@
             );
       });
 
-      printTrace("Local Observatory Port: ${ports[0]}");
-      printTrace("Local Diagnostic Server Port: ${ports[1]}");
+      printTrace("Observatory Uri on device: ${uris[0]}");
+      printTrace("Diagnostic Server Uri on device: ${uris[1]}");
 
-      localObsPort = ports[0];
-      localDiagPort = ports[1];
+      localObsUri = uris[0];
+      localDiagUri = uris[1];
     }
 
     if (installationResult != 0) {
@@ -305,25 +311,25 @@
       return new LaunchResult.failed();
     }
 
-    return new LaunchResult.succeeded(observatoryPort: localObsPort, diagnosticPort: localDiagPort);
+    return new LaunchResult.succeeded(observatoryUri: localObsUri, diagnosticUri: localDiagUri);
   }
 
-  Future<int> _acquireAndForwardPort(
+  Future<Uri> _acquireServiceUri(
       ApplicationPackage app,
       String serviceName,
       int localPort) async {
     Duration stepTimeout = const Duration(seconds: 60);
 
-    Future<int> remote = new ProtocolDiscovery(getLogReader(app: app), serviceName).nextPort();
+    Future<Uri> remote = new ProtocolDiscovery(getLogReader(app: app), serviceName).nextUri();
 
-    int remotePort = await remote.timeout(stepTimeout,
+    Uri remoteUri = await remote.timeout(stepTimeout,
         onTimeout: () {
-      printTrace("Timeout while attempting to retrieve remote port for $serviceName");
+      printTrace("Timeout while attempting to retrieve remote Uri for $serviceName");
       return null;
     });
 
-    if (remotePort == null) {
-      printTrace("Could not read port on device for $serviceName");
+    if (remoteUri == null) {
+      printTrace("Could not read Uri on device for $serviceName");
       return null;
     }
 
@@ -332,19 +338,22 @@
       printTrace("Auto selected local port to $localPort");
     }
 
-    int forwardResult = await portForwarder.forward(remotePort,
-        hostPort: localPort).timeout(stepTimeout, onTimeout: () {
-      printTrace("Timeout while atempting to foward port for $serviceName");
-      return null;
-    });
+    int forwardResult = await portForwarder
+      .forward(remoteUri.port, hostPort: localPort)
+      .timeout(stepTimeout, onTimeout: () {
+        printTrace("Timeout while atempting to foward port for $serviceName");
+        return null;
+      });
 
     if (forwardResult == null) {
-      printTrace("Could not foward remote $serviceName port $remotePort to local port $localPort");
+      printTrace("Could not foward remote $serviceName port $remoteUri to local port $localPort");
       return null;
     }
 
-    printStatus('$serviceName listening on http://127.0.0.1:$localPort');
-    return localPort;
+    Uri forwardUri = remoteUri.replace(port: forwardResult);
+
+    printStatus('$serviceName listening on $forwardUri');
+    return forwardUri;
   }
 
   @override
diff --git a/packages/flutter_tools/lib/src/ios/simulators.dart b/packages/flutter_tools/lib/src/ios/simulators.dart
index 3c30dca..2ddc23e 100644
--- a/packages/flutter_tools/lib/src/ios/simulators.dart
+++ b/packages/flutter_tools/lib/src/ios/simulators.dart
@@ -473,12 +473,12 @@
       printTrace('Waiting for observatory port to be available...');
 
       try {
-        int devicePort = await observatoryDiscovery
-          .nextPort()
+        Uri deviceUri = await observatoryDiscovery
+          .nextUri()
           .timeout(new Duration(seconds: 20));
-        printTrace('service protocol port = $devicePort');
-        printStatus('Observatory listening on http://127.0.0.1:$devicePort');
-        return new LaunchResult.succeeded(observatoryPort: devicePort);
+        printTrace('Observatory Uri on simulator: $deviceUri');
+        printStatus('Observatory listening on $deviceUri');
+        return new LaunchResult.succeeded(observatoryUri: deviceUri);
       } catch (error) {
         if (error is TimeoutException)
           printError('Timed out while waiting for a debug connection.');
diff --git a/packages/flutter_tools/lib/src/protocol_discovery.dart b/packages/flutter_tools/lib/src/protocol_discovery.dart
index 7c27b13..470d527 100644
--- a/packages/flutter_tools/lib/src/protocol_discovery.dart
+++ b/packages/flutter_tools/lib/src/protocol_discovery.dart
@@ -21,37 +21,37 @@
   final DeviceLogReader _logReader;
   final String _serviceName;
 
-  Completer<int> _completer = new Completer<int>();
+  Completer<Uri> _completer = new Completer<Uri>();
   StreamSubscription<String> _subscription;
 
   /// The [Future] returned by this function will complete when the next service
-  /// protocol port is found.
-  Future<int> nextPort() => _completer.future;
+  /// Uri is found.
+  Future<Uri> nextUri() => _completer.future;
 
   void cancel() {
     _subscription.cancel();
   }
 
   void _onLine(String line) {
-    int portNumber = 0;
-    if (line.contains('$_serviceName listening on http://')) {
+    Uri uri;
+    String prefix = '$_serviceName listening on ';
+    int index = line.indexOf(prefix + 'http://');
+    if (index >= 0) {
       try {
-        RegExp portExp = new RegExp(r"\d+.\d+.\d+.\d+:(\d+)");
-        String port = portExp.firstMatch(line).group(1);
-        portNumber = int.parse(port);
+        uri = Uri.parse(line.substring(index + prefix.length));
       } catch (_) {
         // Ignore errors.
       }
     }
-    if (portNumber != 0)
-      _located(portNumber);
+    if (uri != null)
+      _located(uri);
   }
 
-  void _located(int port) {
+  void _located(Uri uri) {
     assert(_completer != null);
     assert(!_completer.isCompleted);
 
-    _completer.complete(port);
-    _completer = new Completer<int>();
+    _completer.complete(uri);
+    _completer = new Completer<Uri>();
   }
 }
diff --git a/packages/flutter_tools/lib/src/resident_runner.dart b/packages/flutter_tools/lib/src/resident_runner.dart
index 70e161e..1492d81 100644
--- a/packages/flutter_tools/lib/src/resident_runner.dart
+++ b/packages/flutter_tools/lib/src/resident_runner.dart
@@ -130,12 +130,12 @@
     _loggingSubscription = null;
   }
 
-  Future<Null> connectToServiceProtocol(int port) async {
+  Future<Null> connectToServiceProtocol(Uri uri) async {
     if (!debuggingOptions.debuggingEnabled) {
       return new Future<Null>.error('Error the service protocol is not enabled.');
     }
-    vmService = await VMService.connect(port);
-    printTrace('Connected to service protocol on port $port');
+    vmService = await VMService.connect(uri);
+    printTrace('Connected to service protocol: $uri');
     await vmService.getVM();
 
     // Refresh the view list.
@@ -298,9 +298,11 @@
 }
 
 class DebugConnectionInfo {
-  DebugConnectionInfo({ this.port, this.wsUri, this.baseUri });
+  DebugConnectionInfo({ this.httpUri, this.wsUri, this.baseUri });
 
-  final int port;
-  final String wsUri;
+  // TODO(danrubel): the httpUri field should be removed as part of
+  // https://github.com/flutter/flutter/issues/7050
+  final Uri httpUri;
+  final Uri wsUri;
   final String baseUri;
 }
diff --git a/packages/flutter_tools/lib/src/run.dart b/packages/flutter_tools/lib/src/run.dart
index 55b543e..0d7ba0b 100644
--- a/packages/flutter_tools/lib/src/run.dart
+++ b/packages/flutter_tools/lib/src/run.dart
@@ -119,17 +119,16 @@
 
     startTime.stop();
 
-    if (_result.hasObservatory) {
-      int port = _result.observatoryPort;
-      connectionInfoCompleter?.complete(new DebugConnectionInfo(
-        port: port,
-        wsUri: 'ws://localhost:$port/ws'
-      ));
-    }
-
     // Connect to observatory.
     if (debuggingOptions.debuggingEnabled) {
-      await connectToServiceProtocol(_result.observatoryPort);
+      await connectToServiceProtocol(_result.observatoryUri);
+    }
+
+    if (_result.hasObservatory) {
+      connectionInfoCompleter?.complete(new DebugConnectionInfo(
+        httpUri: _result.observatoryUri,
+        wsUri: vmService.wsAddress,
+      ));
     }
 
     printTrace('Application running.');
@@ -176,7 +175,7 @@
   void printHelp({ @required bool details }) {
     bool haveDetails = false;
     if (_result.hasObservatory)
-      printStatus('The Observatory debugger and profiler is available at: http://127.0.0.1:${_result.observatoryPort}/');
+      printStatus('The Observatory debugger and profiler is available at: ${_result.observatoryUri}');
     if (supportsServiceProtocol) {
       haveDetails = true;
       if (details)
diff --git a/packages/flutter_tools/lib/src/vmservice.dart b/packages/flutter_tools/lib/src/vmservice.dart
index e767e2a..4f2a126 100644
--- a/packages/flutter_tools/lib/src/vmservice.dart
+++ b/packages/flutter_tools/lib/src/vmservice.dart
@@ -8,13 +8,14 @@
 
 import 'package:json_rpc_2/json_rpc_2.dart' as rpc;
 import 'package:json_rpc_2/error_code.dart' as rpc_error_code;
+import 'package:path/path.dart' as path;
 import 'package:web_socket_channel/io.dart';
 
 import 'globals.dart';
 
 /// A connection to the Dart VM Service.
 class VMService {
-  VMService._(this.peer, this.port, this.httpAddress) {
+  VMService._(this.peer, this.httpAddress, this.wsAddress) {
     _vm = new VM._empty(this);
 
     peer.registerMethod('streamNotify', (rpc.Parameters event) {
@@ -23,21 +24,20 @@
   }
 
   /// Connect to '127.0.0.1' at [port].
-  static Future<VMService> connect(int port) async {
-    Uri uri = new Uri(scheme: 'ws', host: '127.0.0.1', port: port, path: 'ws');
+  static Future<VMService> connect(Uri httpUri) async {
+    Uri wsUri = httpUri.replace(scheme: 'ws', path: path.join(httpUri.path, 'ws'));
     WebSocket ws;
     try {
-      ws = await WebSocket.connect(uri.toString());
+      ws = await WebSocket.connect(wsUri.toString());
     } catch (e) {
-      return new Future<VMService>.error('Failed to connect to $uri\n  $e');
+      return new Future<VMService>.error('Failed to connect to $wsUri\n  $e');
     }
     rpc.Peer peer = new rpc.Peer(new IOWebSocketChannel(ws).cast());
     peer.listen();
-    Uri httpAddress = new Uri(scheme: 'http', host: '127.0.0.1', port: port);
-    return new VMService._(peer, port, httpAddress);
+    return new VMService._(peer, httpUri, wsUri);
   }
   final Uri httpAddress;
-  final int port;
+  final Uri wsAddress;
   final rpc.Peer peer;
 
   VM _vm;