Include the request context for shelf_io errors.

Closes #51

R=kevmoo@google.com

Review URL: https://codereview.chromium.org//1417523005 .
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e4b2939..fbd6232 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,8 @@
+## 0.6.4+1
+
+* When the `shelf_io` adapter detects an error, print the request context as
+  well as the error itself.
+
 ## 0.6.4
 
 * Add a `Server` interface representing an adapter that knows its own URL.
diff --git a/lib/shelf_io.dart b/lib/shelf_io.dart
index 7d0c053..c2813c7 100644
--- a/lib/shelf_io.dart
+++ b/lib/shelf_io.dart
@@ -53,56 +53,60 @@
   catchTopLevelErrors(() {
     requests.listen((request) => handleRequest(request, handler));
   }, (error, stackTrace) {
-    _logError('Asynchronous error\n$error', stackTrace);
+    _logTopLevelError('Asynchronous error\n$error', stackTrace);
   });
 }
 
 /// Uses [handler] to handle [request].
 ///
 /// Returns a [Future] which completes when the request has been handled.
-Future handleRequest(HttpRequest request, Handler handler) {
+Future handleRequest(HttpRequest request, Handler handler) async {
   var shelfRequest;
   try {
     shelfRequest = _fromHttpRequest(request);
   } catch (error, stackTrace) {
-    var response = _logError('Error parsing request.\n$error', stackTrace);
-    return _writeResponse(response, request.response);
+    var response = _logTopLevelError(
+        'Error parsing request.\n$error', stackTrace);
+    await _writeResponse(response, request.response);
+    return;
   }
 
   // TODO(nweiz): abstract out hijack handling to make it easier to implement an
   // adapter.
-  return new Future.sync(() => handler(shelfRequest))
-      .catchError((error, stackTrace) {
-    if (error is HijackException) {
-      // A HijackException should bypass the response-writing logic entirely.
-      if (!shelfRequest.canHijack) throw error;
+  var response;
+  try {
+    response = await handler(shelfRequest);
+  } on HijackException catch (error, stackTrace) {
+    // A HijackException should bypass the response-writing logic entirely.
+    if (!shelfRequest.canHijack) return;
 
-      // If the request wasn't hijacked, we shouldn't be seeing this exception.
-      return _logError(
-          "Caught HijackException, but the request wasn't hijacked.",
-          stackTrace);
-    }
+    // If the request wasn't hijacked, we shouldn't be seeing this exception.
+    response = _logError(
+        shelfRequest,
+        "Caught HijackException, but the request wasn't hijacked.",
+        stackTrace);
+  } catch (error, stackTrace) {
+    response = _logError(
+        shelfRequest, 'Error thrown by handler.\n$error', stackTrace);
+  }
 
-    return _logError('Error thrown by handler.\n$error', stackTrace);
-  }).then((response) {
-    if (response == null) {
-      return _writeResponse(
-          _logError('null response from handler.'), request.response);
-    } else if (shelfRequest.canHijack) {
-      return _writeResponse(response, request.response);
-    }
+  if (response == null) {
+    await _writeResponse(
+        _logError(shelfRequest, 'null response from handler.'),
+        request.response);
+    return;
+  } else if (shelfRequest.canHijack) {
+    await _writeResponse(response, request.response);
+    return;
+  }
 
-    var message = new StringBuffer()
-      ..writeln("Got a response for hijacked request "
-          "${shelfRequest.method} ${shelfRequest.requestedUri}:")
-      ..writeln(response.statusCode);
-    response.headers
-        .forEach((key, value) => message.writeln("${key}: ${value}"));
-    throw new Exception(message.toString().trim());
-  }).catchError((error, stackTrace) {
-    // Ignore HijackExceptions.
-    if (error is! HijackException) throw error;
-  });
+  var message = new StringBuffer()
+    ..writeln("Got a response for hijacked request "
+        "${shelfRequest.method} ${shelfRequest.requestedUri}:")
+    ..writeln(response.statusCode);
+  response.headers
+      .forEach((key, value) => message.writeln("${key}: ${value}"));
+  throw new Exception(message.toString().trim());
 }
 
 /// Creates a new [Request] from the provided [HttpRequest].
@@ -154,7 +158,20 @@
 
 // TODO(kevmoo) A developer mode is needed to include error info in response
 // TODO(kevmoo) Make error output plugable. stderr, logging, etc
-Response _logError(String message, [StackTrace stackTrace]) {
+Response _logError(Request request, String message, [StackTrace stackTrace]) {
+  // Add information about the request itself.
+  var buffer = new StringBuffer();
+  buffer.write("${request.method} ${request.requestedUri.path}");
+  if (request.requestedUri.query.isNotEmpty) {
+    buffer.write("?${request.requestedUri.query}");
+  }
+  buffer.writeln();
+  buffer.write(message);
+
+  return _logTopLevelError(buffer.toString(), stackTrace);
+}
+
+Response _logTopLevelError(String message, [StackTrace stackTrace]) {
   var chain = new Chain.current();
   if (stackTrace != null) {
     chain = new Chain.forTrace(stackTrace);
diff --git a/pubspec.yaml b/pubspec.yaml
index 6463c4b..7c18f5e 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,5 +1,5 @@
 name: shelf
-version: 0.6.4
+version: 0.6.4+1
 author: Dart Team <misc@dartlang.org>
 description: Web Server Middleware for Dart
 homepage: https://github.com/dart-lang/shelf