Fix package:shelf_router to ensure GET handlers also respond to HEAD requests (#36)

diff --git a/pkgs/shelf_router/CHANGELOG.md b/pkgs/shelf_router/CHANGELOG.md
index edcd170..08a1d6c 100644
--- a/pkgs/shelf_router/CHANGELOG.md
+++ b/pkgs/shelf_router/CHANGELOG.md
@@ -1,3 +1,8 @@
+## v0.7.2
+
+ * Always register a `HEAD` handler whenever a `GET` handler is registered.
+   Defaulting to calling the `GET` handler and throwing away the body.
+
 ## v0.7.1
 
  * Use `Function` instead of `dynamic` in `RouterEntry` to improve typing.
diff --git a/pkgs/shelf_router/lib/src/router.dart b/pkgs/shelf_router/lib/src/router.dart
index a298b52..6e58f5e 100644
--- a/pkgs/shelf_router/lib/src/router.dart
+++ b/pkgs/shelf_router/lib/src/router.dart
@@ -32,6 +32,17 @@
   return value;
 }
 
+/// Middleware to remove body from request.
+final _removeBody = createMiddleware(responseHandler: (r) {
+  if (r == null) {
+    return null;
+  }
+  if (r.headers.containsKey('content-length')) {
+    r = r.change(headers: {'content-length': '0'});
+  }
+  return r.change(body: <int>[]);
+});
+
 /// A shelf [Router] routes requests to handlers based on HTTP verb and route
 /// pattern.
 ///
@@ -77,6 +88,11 @@
   final List<RouterEntry> _routes = [];
 
   /// Add [handler] for [verb] requests to [route].
+  ///
+  /// If [verb] is `GET` the [handler] will also be called for `HEAD` requests
+  /// matching [route]. This is because handling `GET` requests without handling
+  /// `HEAD` is always wrong. To explicitely implement a `HEAD` handler it must
+  /// be registered before the `GET` handler.
   void add(String verb, String route, Function handler) {
     ArgumentError.checkNotNull(verb, 'verb');
     if (!isHttpMethod(verb)) {
@@ -84,6 +100,11 @@
     }
     verb = verb.toUpperCase();
 
+    if (verb == 'GET') {
+      // Handling in a 'GET' request without handling a 'HEAD' request is always
+      // wrong, thus, we add a default implementation that discards the body.
+      _routes.add(RouterEntry('HEAD', route, handler, middleware: _removeBody));
+    }
     _routes.add(RouterEntry(verb, route, handler));
   }
 
@@ -135,6 +156,9 @@
   // Handlers for all methods
 
   /// Handle `GET` request to [route] using [handler].
+  ///
+  /// If no matching handler for `HEAD` requests is registered, such requests
+  /// will also be routed to the [handler] registered here.
   void get(String route, Function handler) => add('GET', route, handler);
 
   /// Handle `HEAD` request to [route] using [handler].
diff --git a/pkgs/shelf_router/lib/src/router_entry.dart b/pkgs/shelf_router/lib/src/router_entry.dart
index 05d4b8c..251a91a 100644
--- a/pkgs/shelf_router/lib/src/router_entry.dart
+++ b/pkgs/shelf_router/lib/src/router_entry.dart
@@ -33,6 +33,7 @@
 
   final String verb, route;
   final Function _handler;
+  final Middleware _middleware;
 
   /// Expression that the request path must match.
   ///
@@ -45,7 +46,12 @@
   /// List of parameter names in the route pattern.
   List<String> get params => _params.toList(); // exposed for using generator.
 
-  RouterEntry(this.verb, this.route, this._handler) {
+  RouterEntry(
+    this.verb,
+    this.route,
+    this._handler, {
+    Middleware middleware,
+  }) : _middleware = middleware ?? ((Handler fn) => fn) {
     ArgumentError.checkNotNull(verb, 'verb');
     ArgumentError.checkNotNull(route, 'route');
     ArgumentError.checkNotNull(_handler, 'handler');
@@ -92,10 +98,13 @@
   // invoke handler with given request and params
   Future<Response> invoke(Request request, Map<String, String> params) async {
     request = request.change(context: {'shelf_router/params': params});
-    if (_handler is Handler || _params.isEmpty) {
-      return await _handler(request);
-    }
-    return await Function.apply(
-        _handler, [request]..addAll(_params.map((n) => params[n])));
+
+    return await _middleware((request) async {
+      if (_handler is Handler || _params.isEmpty) {
+        return await _handler(request);
+      }
+      return await Function.apply(
+          _handler, [request]..addAll(_params.map((n) => params[n])));
+    })(request);
   }
 }
diff --git a/pkgs/shelf_router/pubspec.yaml b/pkgs/shelf_router/pubspec.yaml
index f10cf88..5cf23ce 100644
--- a/pkgs/shelf_router/pubspec.yaml
+++ b/pkgs/shelf_router/pubspec.yaml
@@ -1,5 +1,5 @@
 name: shelf_router
-version: 0.7.1
+version: 0.7.2
 authors:
   - Jonas Finnemann Jensen <jonasfj@google.com>
 description: |
@@ -16,4 +16,4 @@
   pedantic: ^1.4.0
   http: ^0.12.0+1
 environment:
-  sdk: '>=2.0.0 <3.0.0'
\ No newline at end of file
+  sdk: '>=2.0.0 <3.0.0'
diff --git a/pkgs/shelf_router/test/router_test.dart b/pkgs/shelf_router/test/router_test.dart
index 6c09d5c..5eeae1b 100644
--- a/pkgs/shelf_router/test/router_test.dart
+++ b/pkgs/shelf_router/test/router_test.dart
@@ -39,6 +39,8 @@
   tearDown(() => server.close());
 
   Future<String> get(String path) => http.read(server.url.toString() + path);
+  Future<int> head(String path) async =>
+      (await http.head(server.url.toString() + path)).statusCode;
 
   test('get sync/async handler', () async {
     var app = Router();
@@ -63,6 +65,10 @@
     expect(await get('/sync-hello'), 'hello-world');
     expect(await get('/async-hello'), 'hello-world');
     expect(await get('/wrong-path'), 'not-found');
+
+    expect(await head('/sync-hello'), 200);
+    expect(await head('/async-hello'), 200);
+    expect(await head('/wrong-path'), 200);
   });
 
   test('params', () async {