blob: 90b79268dae23db79992b49c11dad4393a898ab9 [file] [log] [blame]
// Copyright (c) 2017, 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:async';
import 'dart:io';
import 'package:async/async.dart';
import 'src/utils.dart';
/// The error code for an error caused by a port already being in use.
final int _addressInUseErrno = () {
if (Platform.isWindows) return 10048;
if (Platform.isMacOS) return 48;
assert(Platform.isLinux);
return 98;
}();
/// An implementation of `dart:io`'s [ServerSocket] that wraps multiple servers
/// and forwards methods to all of them.
///
/// This is useful for listening on multiple network interfaces while still
/// having a unified way of controlling the servers. In particular, it supports
/// serving on both the IPv4 and IPv6 loopback addresses using
/// [MultiServerSocket.loopback].
class MultiServerSocket extends StreamView<Socket> implements ServerSocket {
/// The wrapped servers.
final Set<ServerSocket> _servers;
/// Returns the port that one of the wrapped servers is listening on.
///
/// If the wrapped servers are listening on different ports, it's not defined
/// which port is returned.
int get port => _servers.first.port;
/// Returns the address that one of the wrapped servers is listening on.
///
/// If the wrapped servers are listening on different addresses, it's not
/// defined which address is returned.
InternetAddress get address => _servers.first.address;
/// Creates a [MultiServerSocket] wrapping [servers].
///
/// None of the servers should be listened to when this is called.
MultiServerSocket(Iterable<ServerSocket> servers)
: _servers = servers.toSet(),
super(StreamGroup.merge(servers));
/// Creates a [ServerSocket] listening on all available loopback addresses for
/// this computer.
///
/// See [ServerSocket.bind].
static Future<ServerSocket> loopback(int port,
{int backlog, bool v6Only: false, bool shared: false}) {
backlog ??= 0;
return _loopback(port, 5, backlog, v6Only, shared);
}
/// A helper method for initializing loopback servers.
static Future<ServerSocket> _loopback(int port, int remainingRetries,
int backlog, bool v6Only, bool shared) async {
if (!await supportsIPv4) {
return await ServerSocket.bind(InternetAddress.LOOPBACK_IP_V6, port,
backlog: backlog, v6Only: v6Only, shared: shared);
}
var v4Server = await ServerSocket.bind(InternetAddress.LOOPBACK_IP_V4, port,
backlog: backlog, v6Only: v6Only, shared: shared);
if (!await supportsIPv6) return v4Server;
try {
// Reuse the IPv4 server's port so that if [port] is 0, both servers use
// the same ephemeral port.
var v6Server = await ServerSocket.bind(
InternetAddress.LOOPBACK_IP_V6, v4Server.port,
backlog: backlog, v6Only: v6Only, shared: shared);
return new MultiServerSocket([v4Server, v6Server]);
} on SocketException catch (error) {
if (error.osError.errorCode != _addressInUseErrno) rethrow;
if (port != 0) rethrow;
if (remainingRetries == 0) rethrow;
// A port being available on IPv4 doesn't necessarily mean that the same
// port is available on IPv6. If it's not (which is rare in practice),
// we try again until we find one that's available on both.
v4Server.close();
return await _loopback(
port, remainingRetries - 1, backlog, v6Only, shared);
}
}
Future<ServerSocket> close() =>
Future.wait(_servers.map((server) => server.close())).then((_) => this);
}