blob: 8a6f85ef7a3ba37762b0e8b8202b880488e0280c [file] [log] [blame]
// 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() {}
}