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.