| // 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. | |
| import 'dart:io'; | |
| import 'dart:async'; | |
| class ChildServerProcess { | |
| /// [IOSink]s like [stdout] don't like to be piped more than one [Stream], but | |
| /// we want to pipe many of them (basically, every standard output and error | |
| /// of every child process we open), so we pipe to an accommodating consumer. | |
| static final _consoleOut = new _MultipleStreamConsumer(stdout); | |
| final Process process; | |
| final String host; | |
| final int port; | |
| ChildServerProcess._(this.process, this.host, this.port); | |
| get httpUri => Uri.parse('http://$host:$port'); | |
| static build(Future<Process> builder(String host, int port), | |
| {int defaultPort: 1024, | |
| int maxPort: 65535, | |
| String host: '0.0.0.0'}) async { | |
| var port = await _findUnusedPort(defaultPort, maxPort); | |
| var p = (await builder(host, port)) | |
| ..stdout.pipe(_consoleOut) | |
| ..stderr.pipe(_consoleOut); | |
| await _waitForServer(host, port); | |
| return new ChildServerProcess._(p, host, port); | |
| } | |
| static _waitForServer(String host, int port, | |
| {int attempts: 10, | |
| Duration retryDelay: const Duration(seconds: 1)}) async { | |
| var lastError; | |
| for (int i = 0; i < attempts; i++) { | |
| try { | |
| await (await Socket.connect(host, port)).close(); | |
| return; | |
| } catch (e) { | |
| lastError = e; | |
| await new Future.delayed(retryDelay); | |
| } | |
| } | |
| throw new StateError( | |
| 'Failed to connect to $host:$port after $attempts attempts; ' | |
| 'Last error:\n$lastError'); | |
| } | |
| static Future<int> _findUnusedPort(int fromPort, int toPort) async { | |
| var lastError; | |
| for (int port = fromPort; port <= toPort; port++) { | |
| try { | |
| await (await ServerSocket.bind(InternetAddress.ANY_IP_V4, port)) | |
| .close(); | |
| return port; | |
| } catch (e) { | |
| lastError = e; | |
| } | |
| } | |
| throw new StateError( | |
| 'Failed to find an unused port between $fromPort and $toPort; ' | |
| 'Last error:\n$lastError'); | |
| } | |
| } | |
| /// A consumer into which we can pipe as many streams as we want, that forwards | |
| /// everything to an [IOSink] (such as [stdout]). | |
| class _MultipleStreamConsumer extends StreamConsumer<List<int>> { | |
| final IOSink _sink; | |
| _MultipleStreamConsumer(this._sink); | |
| @override | |
| Future addStream(Stream<List<int>> stream) async { | |
| await for (var data in stream) { | |
| _sink.add(data); | |
| } | |
| } | |
| @override | |
| close() {} | |
| } |