Handle malformed URLs (400 instead of 500). (#144)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2a62e70..571f425 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 0.7.8
+
+* Handle malformed URLs (400 instead of 500).
+
 ## 0.7.7
 
 * Fix internal error in `Request` when `.change()` matches the full path.
diff --git a/lib/src/request.dart b/lib/src/request.dart
index d1740c3..e3f94a6 100644
--- a/lib/src/request.dart
+++ b/lib/src/request.dart
@@ -149,7 +149,7 @@
   /// Any [Request] created by calling [change] will pass [_onHijack] from the
   /// source [Request] to ensure that [hijack] can only be called once, even
   /// from a changed [Request].
-  Request._(this.method, Uri requestedUri,
+  Request._(this.method, this.requestedUri,
       {String protocolVersion,
       Map<String, /* String | List<String> */ Object> headers,
       String handlerPath,
@@ -158,8 +158,7 @@
       Encoding encoding,
       Map<String, Object> context,
       _OnHijack onHijack})
-      : requestedUri = requestedUri,
-        protocolVersion = protocolVersion ?? '1.1',
+      : protocolVersion = protocolVersion ?? '1.1',
         url = _computeUrl(requestedUri, handlerPath, url),
         handlerPath = _computeHandlerPath(requestedUri, handlerPath, url),
         _onHijack = onHijack,
@@ -168,6 +167,16 @@
       throw ArgumentError.value(method, 'method', 'cannot be empty.');
     }
 
+    try {
+      // Trigger URI parsing methods that may throw format exception (in Request
+      // constructor or in handlers / routing).
+      requestedUri.pathSegments;
+      requestedUri.queryParametersAll;
+    } on FormatException catch (e) {
+      throw ArgumentError.value(
+          requestedUri, 'requestedUri', 'URI parsing failed: $e');
+    }
+
     if (!requestedUri.isAbsolute) {
       throw ArgumentError.value(
           requestedUri, 'requestedUri', 'must be an absolute URL.');
@@ -184,7 +193,7 @@
     // and not a String is probably the underlying flaw here.
     final pathSegments = Uri(path: this.handlerPath).pathSegments.join('/') +
         this.url.pathSegments.join('/');
-    if (pathSegments != this.requestedUri.pathSegments.join('/')) {
+    if (pathSegments != requestedUri.pathSegments.join('/')) {
       throw ArgumentError.value(
           requestedUri,
           'requestedUri',
diff --git a/pubspec.yaml b/pubspec.yaml
index 12eaa7a..b71d38d 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,5 +1,5 @@
 name: shelf
-version: 0.7.7
+version: 0.7.8
 description: >-
   A model for web server middleware that encourages composition and easy reuse
 homepage: https://github.com/dart-lang/shelf
diff --git a/test/io_server_test.dart b/test/io_server_test.dart
index 26226d4..4ab6a2a 100644
--- a/test/io_server_test.dart
+++ b/test/io_server_test.dart
@@ -31,6 +31,14 @@
     expect(await http.read(server.url), equals('Hello from /'));
   });
 
+  test('Handles malformed requests gracefully.', () async {
+    server.mount(syncHandler);
+    final rs =
+        await http.get('${server.url}/%D0%C2%BD%A8%CE%C4%BC%FE%BC%D0.zip');
+    expect(rs.statusCode, 400);
+    expect(rs.body, 'Bad Request');
+  });
+
   test('delays HTTP requests until a handler is mounted', () async {
     expect(http.read(server.url), completion(equals('Hello from /')));
     await Future.delayed(Duration.zero);