| Index: mojo/public/dart/third_party/http_multi_server/lib/http_multi_server.dart
|
| diff --git a/mojo/public/dart/third_party/http_multi_server/lib/http_multi_server.dart b/mojo/public/dart/third_party/http_multi_server/lib/http_multi_server.dart
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..abad76ebb9b163910e9b92ad208f48b16185a90c
|
| --- /dev/null
|
| +++ b/mojo/public/dart/third_party/http_multi_server/lib/http_multi_server.dart
|
| @@ -0,0 +1,180 @@
|
| +// Copyright (c) 2014, 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.
|
| +
|
| +library http_multi_server;
|
| +
|
| +import 'dart:async';
|
| +import 'dart:io';
|
| +
|
| +import 'src/multi_headers.dart';
|
| +import 'src/utils.dart';
|
| +
|
| +/// The error code for an error caused by a port already being in use.
|
| +final _addressInUseErrno = _computeAddressInUseErrno();
|
| +int _computeAddressInUseErrno() {
|
| + if (Platform.isWindows) return 10048;
|
| + if (Platform.isMacOS) return 48;
|
| + assert(Platform.isLinux);
|
| + return 98;
|
| +}
|
| +
|
| +/// An implementation of `dart:io`'s [HttpServer] that wraps multiple servers
|
| +/// and forwards methods to all of them.
|
| +///
|
| +/// This is useful for serving the same application 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 [HttpMultiServer.loopback].
|
| +class HttpMultiServer extends StreamView<HttpRequest> implements HttpServer {
|
| + /// The wrapped servers.
|
| + final Set<HttpServer> _servers;
|
| +
|
| + /// Returns the default value of the `Server` header for all responses
|
| + /// generated by each server.
|
| + ///
|
| + /// If the wrapped servers have different default values, it's not defined
|
| + /// which value is returned.
|
| + String get serverHeader => _servers.first.serverHeader;
|
| + set serverHeader(String value) {
|
| + for (var server in _servers) {
|
| + server.serverHeader = value;
|
| + }
|
| + }
|
| +
|
| + /// Returns the default set of headers added to all response objects.
|
| + ///
|
| + /// If the wrapped servers have different default headers, it's not defined
|
| + /// which header is returned for accessor methods.
|
| + final HttpHeaders defaultResponseHeaders;
|
| +
|
| + Duration get idleTimeout => _servers.first.idleTimeout;
|
| + set idleTimeout(Duration value) {
|
| + for (var server in _servers) {
|
| + server.idleTimeout = value;
|
| + }
|
| + }
|
| +
|
| + bool get autoCompress => _servers.first.autoCompress;
|
| + set autoCompress(bool value) {
|
| + for (var server in _servers) {
|
| + server.autoCompress = value;
|
| + }
|
| + }
|
| +
|
| + /// 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;
|
| +
|
| + set sessionTimeout(int value) {
|
| + for (var server in _servers) {
|
| + server.sessionTimeout = value;
|
| + }
|
| + }
|
| +
|
| + /// Creates an [HttpMultiServer] wrapping [servers].
|
| + ///
|
| + /// All [servers] should have the same configuration and none should be
|
| + /// listened to when this is called.
|
| + HttpMultiServer(Iterable<HttpServer> servers)
|
| + : _servers = servers.toSet(),
|
| + defaultResponseHeaders = new MultiHeaders(
|
| + servers.map((server) => server.defaultResponseHeaders)),
|
| + super(mergeStreams(servers));
|
| +
|
| + /// Creates an [HttpServer] listening on all available loopback addresses for
|
| + /// this computer.
|
| + ///
|
| + /// If this computer supports both IPv4 and IPv6, this returns an
|
| + /// [HttpMultiServer] listening to [port] on both loopback addresses.
|
| + /// Otherwise, it returns a normal [HttpServer] listening only on the IPv4
|
| + /// address.
|
| + ///
|
| + /// If [port] is 0, the same ephemeral port is used for both the IPv4 and IPv6
|
| + /// addresses.
|
| + static Future<HttpServer> loopback(int port, {int backlog}) {
|
| + if (backlog == null) backlog = 0;
|
| +
|
| + return _loopback(port, (address, port) =>
|
| + HttpServer.bind(address, port, backlog: backlog));
|
| + }
|
| +
|
| + /// Like [loopback], but supports HTTPS requests.
|
| + ///
|
| + /// The certificate with nickname or distinguished name (DN) [certificateName]
|
| + /// is looked up in the certificate database, and is used as the server
|
| + /// certificate. If [requestClientCertificate] is true, the server will
|
| + /// request clients to authenticate with a client certificate.
|
| + static Future<HttpServer> loopbackSecure(int port, {int backlog,
|
| + String certificateName, bool requestClientCertificate: false}) {
|
| + if (backlog == null) backlog = 0;
|
| +
|
| + return _loopback(port, (address, port) =>
|
| + HttpServer.bindSecure(address, port, backlog: backlog,
|
| + certificateName: certificateName,
|
| + requestClientCertificate: requestClientCertificate));
|
| + }
|
| +
|
| + /// A helper method for initializing loopback servers.
|
| + ///
|
| + /// [bind] should forward to either [HttpServer.bind] or
|
| + /// [HttpServer.bindSecure].
|
| + static Future<HttpServer> _loopback(int port,
|
| + Future<HttpServer> bind(InternetAddress address, int port),
|
| + [int remainingRetries]) {
|
| + if (remainingRetries == null) remainingRetries = 5;
|
| +
|
| + return Future.wait([
|
| + supportsIpV6,
|
| + bind(InternetAddress.LOOPBACK_IP_V4, port)
|
| + ]).then((results) {
|
| + var supportsIpV6 = results[0];
|
| + var v4Server = results[1];
|
| +
|
| + if (!supportsIpV6) return v4Server;
|
| +
|
| + // Reuse the IPv4 server's port so that if [port] is 0, both servers use
|
| + // the same ephemeral port.
|
| + return bind(InternetAddress.LOOPBACK_IP_V6, v4Server.port)
|
| + .then((v6Server) {
|
| + return new HttpMultiServer([v4Server, v6Server]);
|
| + }).catchError((error) {
|
| + if (error is! SocketException) throw error;
|
| + if (error.osError.errorCode != _addressInUseErrno) throw error;
|
| + if (port != 0) throw error;
|
| + if (remainingRetries == 0) throw error;
|
| +
|
| + // 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 _loopback(port, bind, remainingRetries - 1);
|
| + });
|
| + });
|
| + }
|
| +
|
| + Future close({bool force: false}) =>
|
| + Future.wait(_servers.map((server) => server.close(force: force)));
|
| +
|
| + /// Returns an HttpConnectionsInfo object summarizing the total number of
|
| + /// current connections handled by all the servers.
|
| + HttpConnectionsInfo connectionsInfo() {
|
| + var info = new HttpConnectionsInfo();
|
| + for (var server in _servers) {
|
| + var subInfo = server.connectionsInfo();
|
| + info.total += subInfo.total;
|
| + info.active += subInfo.active;
|
| + info.idle += subInfo.idle;
|
| + info.closing += subInfo.closing;
|
| + }
|
| + return info;
|
| + }
|
| +}
|
|
|