// 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 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 _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> { final IOSink _sink; _MultipleStreamConsumer(this._sink); @override Future addStream(Stream> stream) async { await for (var data in stream) { _sink.add(data); } } @override close() {} }