Merge pull request #6 from bcko/patch-1

Update .gitignore to new `dart_tool` pub cache
diff --git a/.travis.yml b/.travis.yml
index 74d7d9c..47f62b8 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,14 +1,19 @@
 language: dart
-sudo: false
+
 dart:
+  - 2.1.0
   - dev
-  - stable
 
 dart_task:
   - test
-  - dartfmt
   - dartanalyzer
 
+matrix:
+  include:
+  # Only validate formatting using the dev release
+  - dart: dev
+    dart_task: dartfmt
+
 # Only building master means that we don't run two builds for each pull request.
 branches:
   only: [master]
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 824ba46..181b4bd 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,20 @@
+## 2.1.0
+
+- Add `HttpMultiServer.bind` static which centralizes logic around common local
+  serving scenarios - handling a more flexible 'localhost' and listening on
+  'any' hostname.
+- Update SDK constraints to `>=2.1.0 <3.0.0`.
+
+
+## 2.0.6
+
+* If there is a problem starting a loopback Ipv6 server, don't keep the Ipv4
+  server open when throwing the exception.
+
+## 2.0.5
+
+* Update SDK constraints to `>=2.0.0-dev <3.0.0`.
+
 ## 2.0.4
 
 * Declare support for `async` 2.0.0.
diff --git a/analysis_options.yaml b/analysis_options.yaml
index a10d4c5..8dfb3d0 100644
--- a/analysis_options.yaml
+++ b/analysis_options.yaml
@@ -1,2 +1,44 @@
+include: package:pedantic/analysis_options.yaml
 analyzer:
-  strong-mode: true
+  strong-mode:
+    implicit-casts: false
+linter:
+  rules:
+    - avoid_empty_else
+    - avoid_init_to_null
+    - avoid_null_checks_in_equality_operators
+    - avoid_unused_constructor_parameters
+    - annotate_overrides
+    - await_only_futures
+    - camel_case_types
+    - cancel_subscriptions
+    - constant_identifier_names
+    - control_flow_in_finally
+    - directives_ordering
+    - empty_catches
+    - empty_constructor_bodies
+    - empty_statements
+    - hash_and_equals
+    - implementation_imports
+    - iterable_contains_unrelated_type
+    - library_names
+    - library_prefixes
+    - list_remove_unrelated_type
+    - non_constant_identifier_names
+    - overridden_fields
+    - package_api_docs
+    - package_names
+    - package_prefixed_library_names
+    - prefer_equal_for_default_values
+    - prefer_final_fields
+    - prefer_generic_function_type_aliases
+    - prefer_is_not_empty
+    - slash_for_doc_comments
+    - test_types_in_equals
+    - throw_in_finally
+    - type_init_formals
+    - unnecessary_brace_in_string_interps
+    - unnecessary_const
+    - unnecessary_new
+    - unrelated_type_equality_checks
+    - valid_regexps
diff --git a/codereview.settings b/codereview.settings
deleted file mode 100644
index 23d9fd4..0000000
--- a/codereview.settings
+++ /dev/null
@@ -1,3 +0,0 @@
-CODE_REVIEW_SERVER: http://codereview.chromium.org/
-VIEW_VC: https://github.com/dart-lang/http_multi_server/commit/
-CC_LIST: reviews@dartlang.org
\ No newline at end of file
diff --git a/lib/http_multi_server.dart b/lib/http_multi_server.dart
index b57e46c..1f10215 100644
--- a/lib/http_multi_server.dart
+++ b/lib/http_multi_server.dart
@@ -35,7 +35,9 @@
   ///
   /// If the wrapped servers have different default values, it's not defined
   /// which value is returned.
+  @override
   String get serverHeader => _servers.first.serverHeader;
+  @override
   set serverHeader(String value) {
     for (var server in _servers) {
       server.serverHeader = value;
@@ -46,16 +48,21 @@
   ///
   /// If the wrapped servers have different default headers, it's not defined
   /// which header is returned for accessor methods.
+  @override
   final HttpHeaders defaultResponseHeaders;
 
+  @override
   Duration get idleTimeout => _servers.first.idleTimeout;
+  @override
   set idleTimeout(Duration value) {
     for (var server in _servers) {
       server.idleTimeout = value;
     }
   }
 
+  @override
   bool get autoCompress => _servers.first.autoCompress;
+  @override
   set autoCompress(bool value) {
     for (var server in _servers) {
       server.autoCompress = value;
@@ -66,14 +73,17 @@
   ///
   /// If the wrapped servers are listening on different ports, it's not defined
   /// which port is returned.
+  @override
   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.
+  @override
   InternetAddress get address => _servers.first.address;
 
+  @override
   set sessionTimeout(int value) {
     for (var server in _servers) {
       server.sessionTimeout = value;
@@ -86,7 +96,7 @@
   /// listened to when this is called.
   HttpMultiServer(Iterable<HttpServer> servers)
       : _servers = servers.toSet(),
-        defaultResponseHeaders = new MultiHeaders(
+        defaultResponseHeaders = MultiHeaders(
             servers.map((server) => server.defaultResponseHeaders)),
         super(StreamGroup.merge(servers));
 
@@ -95,7 +105,7 @@
   ///
   /// See [HttpServer.bind].
   static Future<HttpServer> loopback(int port,
-      {int backlog, bool v6Only: false, bool shared: false}) {
+      {int backlog, bool v6Only = false, bool shared = false}) {
     if (backlog == null) backlog = 0;
 
     return _loopback(
@@ -109,9 +119,9 @@
   /// See [HttpServer.bindSecure].
   static Future<HttpServer> loopbackSecure(int port, SecurityContext context,
       {int backlog,
-      bool v6Only: false,
-      bool requestClientCertificate: false,
-      bool shared: false}) {
+      bool v6Only = false,
+      bool requestClientCertificate = false,
+      bool shared = false}) {
     if (backlog == null) backlog = 0;
 
     return _loopback(
@@ -123,6 +133,29 @@
             requestClientCertificate: requestClientCertificate));
   }
 
+  /// Bind an [HttpServer] with handling for special addresses 'localhost' and
+  /// 'any'.
+  ///
+  /// For address 'localhost' behaves like [loopback]. For 'any' listens on
+  /// [InternetAddress.anyIPv6] which listens on all hostnames for both IPv4 and
+  /// IPV6. For any other address forwards directly to `HttpServer.bind` where
+  /// the IPvX support may vary.
+  ///
+  /// See [HttpServer.bind].
+  static Future<HttpServer> bind(dynamic address, int port,
+      {int backlog = 0, bool v6Only = false, bool shared = false}) {
+    if (address == 'localhost') {
+      return HttpMultiServer.loopback(port,
+          backlog: backlog, v6Only: v6Only, shared: shared);
+    }
+    if (address == 'any') {
+      return HttpServer.bind(InternetAddress.anyIPv6, port,
+          backlog: backlog, v6Only: v6Only, shared: shared);
+    }
+    return HttpServer.bind(address, port,
+        backlog: backlog, v6Only: v6Only, shared: shared);
+  }
+
   /// A helper method for initializing loopback servers.
   ///
   /// [bind] should forward to either [HttpServer.bind] or
@@ -133,18 +166,22 @@
     remainingRetries ??= 5;
 
     if (!await supportsIPv4) {
-      return await bind(InternetAddress.LOOPBACK_IP_V6, port);
+      return await bind(InternetAddress.loopbackIPv6, port);
     }
 
-    var v4Server = await bind(InternetAddress.LOOPBACK_IP_V4, port);
+    var v4Server = await bind(InternetAddress.loopbackIPv4, port);
     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 bind(InternetAddress.LOOPBACK_IP_V6, v4Server.port);
-      return new HttpMultiServer([v4Server, v6Server]);
+      var v6Server = await bind(InternetAddress.loopbackIPv6, v4Server.port);
+      return HttpMultiServer([v4Server, v6Server]);
     } on SocketException catch (error) {
+      // If there is already a server listening we'll lose the reference on a
+      // rethrow.
+      await v4Server.close();
+
       if (error.osError.errorCode != _addressInUseErrno) rethrow;
       if (port != 0) rethrow;
       if (remainingRetries == 0) rethrow;
@@ -152,18 +189,19 @@
       // 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, bind, remainingRetries - 1);
     }
   }
 
-  Future close({bool force: false}) =>
+  @override
+  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.
+  @override
   HttpConnectionsInfo connectionsInfo() {
-    var info = new HttpConnectionsInfo();
+    var info = HttpConnectionsInfo();
     for (var server in _servers) {
       var subInfo = server.connectionsInfo();
       info.total += subInfo.total;
diff --git a/lib/src/multi_headers.dart b/lib/src/multi_headers.dart
index 9267aee..d1dd93f 100644
--- a/lib/src/multi_headers.dart
+++ b/lib/src/multi_headers.dart
@@ -10,63 +10,81 @@
   /// The wrapped headers.
   final Set<HttpHeaders> _headers;
 
+  @override
   bool get chunkedTransferEncoding => _headers.first.chunkedTransferEncoding;
+  @override
   set chunkedTransferEncoding(bool value) {
     for (var headers in _headers) {
       headers.chunkedTransferEncoding = value;
     }
   }
 
+  @override
   int get contentLength => _headers.first.contentLength;
+  @override
   set contentLength(int value) {
     for (var headers in _headers) {
       headers.contentLength = value;
     }
   }
 
+  @override
   ContentType get contentType => _headers.first.contentType;
+  @override
   set contentType(ContentType value) {
     for (var headers in _headers) {
       headers.contentType = value;
     }
   }
 
+  @override
   DateTime get date => _headers.first.date;
+  @override
   set date(DateTime value) {
     for (var headers in _headers) {
       headers.date = value;
     }
   }
 
+  @override
   DateTime get expires => _headers.first.expires;
+  @override
   set expires(DateTime value) {
     for (var headers in _headers) {
       headers.expires = value;
     }
   }
 
+  @override
   String get host => _headers.first.host;
+  @override
   set host(String value) {
     for (var headers in _headers) {
       headers.host = value;
     }
   }
 
+  @override
   DateTime get ifModifiedSince => _headers.first.ifModifiedSince;
+  @override
   set ifModifiedSince(DateTime value) {
     for (var headers in _headers) {
       headers.ifModifiedSince = value;
     }
   }
 
+  @override
   bool get persistentConnection => _headers.first.persistentConnection;
+  @override
   set persistentConnection(bool value) {
     for (var headers in _headers) {
       headers.persistentConnection = value;
     }
   }
 
+  @override
   int get port => _headers.first.port;
+  @override
   set port(int value) {
     for (var headers in _headers) {
       headers.port = value;
@@ -75,43 +93,52 @@
 
   MultiHeaders(Iterable<HttpHeaders> headers) : _headers = headers.toSet();
 
+  @override
   void add(String name, Object value) {
     for (var headers in _headers) {
       headers.add(name, value);
     }
   }
 
+  @override
   void forEach(void f(String name, List<String> values)) =>
       _headers.first.forEach(f);
 
+  @override
   void noFolding(String name) {
     for (var headers in _headers) {
       headers.noFolding(name);
     }
   }
 
+  @override
   void remove(String name, Object value) {
     for (var headers in _headers) {
       headers.remove(name, value);
     }
   }
 
+  @override
   void removeAll(String name) {
     for (var headers in _headers) {
       headers.removeAll(name);
     }
   }
 
+  @override
   void set(String name, Object value) {
     for (var headers in _headers) {
       headers.set(name, value);
     }
   }
 
+  @override
   String value(String name) => _headers.first.value(name);
 
+  @override
   List<String> operator [](String name) => _headers.first[name];
 
+  @override
   void clear() {
     for (var headers in _headers) {
       headers.clear();
diff --git a/lib/src/utils.dart b/lib/src/utils.dart
index 21c335c..2709dab 100644
--- a/lib/src/utils.dart
+++ b/lib/src/utils.dart
@@ -8,8 +8,8 @@
 /// Returns whether this computer supports binding to IPv6 addresses.
 final Future<bool> supportsIPv6 = () async {
   try {
-    var socket = await ServerSocket.bind(InternetAddress.LOOPBACK_IP_V6, 0);
-    socket.close();
+    var socket = await ServerSocket.bind(InternetAddress.loopbackIPv6, 0);
+    await socket.close();
     return true;
   } on SocketException catch (_) {
     return false;
@@ -19,8 +19,8 @@
 /// Returns whether this computer supports binding to IPv4 addresses.
 final Future<bool> supportsIPv4 = () async {
   try {
-    var socket = await ServerSocket.bind(InternetAddress.LOOPBACK_IP_V4, 0);
-    socket.close();
+    var socket = await ServerSocket.bind(InternetAddress.loopbackIPv4, 0);
+    await socket.close();
     return true;
   } on SocketException catch (_) {
     return false;
diff --git a/pubspec.yaml b/pubspec.yaml
index 7d4114a..7249a60 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,13 +1,18 @@
 name: http_multi_server
-version: 2.0.4
-author: "Dart Team <misc@dartlang.org>"
-homepage: https://github.com/dart-lang/http_multi_server
-description:
+version: 2.1.0
+
+description: >-
   A dart:io HttpServer wrapper that handles requests from multiple servers.
+author: Dart Team <misc@dartlang.org>
+homepage: https://github.com/dart-lang/http_multi_server
+
 environment:
-  sdk: ">=1.13.0 <2.0.0"
+  sdk: '>=2.1.0 <3.0.0'
+
 dependencies:
-  async: ">=1.2.0 <3.0.0"
+  async: '>=1.2.0 <3.0.0'
+
 dev_dependencies:
-  test: ">=0.12.0 <0.13.0"
-  http: ">=0.11.0 <0.12.0"
+  http: ^0.12.0
+  pedantic: ^1.4.0
+  test: ^1.5.2
diff --git a/test/http_multi_server_test.dart b/test/http_multi_server_test.dart
index 9c7becf..65cfd57 100644
--- a/test/http_multi_server_test.dart
+++ b/test/http_multi_server_test.dart
@@ -13,15 +13,15 @@
 void main() {
   group("with multiple HttpServers", () {
     var multiServer;
-    var subServer1;
-    var subServer2;
-    var subServer3;
+    HttpServer subServer1;
+    HttpServer subServer2;
+    HttpServer subServer3;
     setUp(() {
       return Future.wait([
         HttpServer.bind("localhost", 0).then((server) => subServer1 = server),
         HttpServer.bind("localhost", 0).then((server) => subServer2 = server),
         HttpServer.bind("localhost", 0).then((server) => subServer3 = server)
-      ]).then((servers) => multiServer = new HttpMultiServer(servers));
+      ]).then((servers) => multiServer = HttpMultiServer(servers));
     });
 
     tearDown(() => multiServer.close());
@@ -127,8 +127,8 @@
 
     test("connectionsInfo sums the values for all servers", () {
       var pendingRequests = 0;
-      var awaitingResponseCompleter = new Completer();
-      var sendResponseCompleter = new Completer();
+      var awaitingResponseCompleter = Completer();
+      var sendResponseCompleter = Completer();
       multiServer.listen((request) {
         sendResponseCompleter.future.then((_) {
           request.response.write("got request");
@@ -159,7 +159,7 @@
   group("HttpMultiServer.loopback", () {
     var server;
     setUp(() {
-      return HttpMultiServer.loopback(0).then((server_) => server = server_);
+      return HttpMultiServer.loopback(0).then((s) => server = s);
     });
 
     tearDown(() => server.close());
@@ -181,6 +181,62 @@
       }
     });
   });
+
+  group("HttpMultiServer.bind", () {
+    test("listens on all localhost interfaces for 'localhost'", () async {
+      final server = await HttpMultiServer.bind("localhost", 0);
+      server.listen((request) {
+        request.response.write("got request");
+        request.response.close();
+      });
+
+      if (await supportsIPv4) {
+        expect(http.read("http://127.0.0.1:${server.port}/"),
+            completion(equals("got request")));
+      }
+
+      if (await supportsIPv6) {
+        expect(http.read("http://[::1]:${server.port}/"),
+            completion(equals("got request")));
+      }
+    });
+
+    test("listens on all localhost interfaces for 'any'", () async {
+      final server = await HttpMultiServer.bind("any", 0);
+      server.listen((request) {
+        request.response.write("got request");
+        request.response.close();
+      });
+
+      if (await supportsIPv4) {
+        expect(http.read("http://127.0.0.1:${server.port}/"),
+            completion(equals("got request")));
+      }
+
+      if (await supportsIPv6) {
+        expect(http.read("http://[::1]:${server.port}/"),
+            completion(equals("got request")));
+      }
+    });
+
+    test("listens on specified hostname", () async {
+      final server = await HttpMultiServer.bind(InternetAddress.anyIPv4, 0);
+      server.listen((request) {
+        request.response.write("got request");
+        request.response.close();
+      });
+
+      if (await supportsIPv4) {
+        expect(http.read("http://127.0.0.1:${server.port}/"),
+            completion(equals("got request")));
+      }
+
+      if (await supportsIPv6) {
+        expect(http.read("http://[::1]:${server.port}/"),
+            throwsA(isA<SocketException>()));
+      }
+    });
+  });
 }
 
 /// Makes a GET request to the root of [server] and returns the response.