Fix retry logic (#1762)

Closes https://github.com/dart-lang/pub/issues/1556
diff --git a/lib/src/http.dart b/lib/src/http.dart
index 3441892..9f11012 100644
--- a/lib/src/http.dart
+++ b/lib/src/http.dart
@@ -92,39 +92,7 @@
 
     _logResponse(streamedResponse);
 
-    var status = streamedResponse.statusCode;
-    // 401 responses should be handled by the OAuth2 client. It's very
-    // unlikely that they'll be returned by non-OAuth2 requests. We also want
-    // to pass along 400 responses from the token endpoint.
-    var tokenRequest = streamedResponse.request.url == oauth2.tokenEndpoint;
-    if (status < 400 || status == 401 || (status == 400 && tokenRequest)) {
-      return streamedResponse;
-    }
-
-    if (status == 406 &&
-        request.headers['Accept'] == PUB_API_HEADERS['Accept']) {
-      fail("Pub ${sdk.version} is incompatible with the current version of "
-          "${request.url.host}.\n"
-          "Upgrade pub to the latest version and try again.");
-    }
-
-    if (status == 500 &&
-        (request.url.host == "pub.dartlang.org" ||
-            request.url.host == "storage.googleapis.com")) {
-      var message = "HTTP error 500: Internal Server Error at "
-          "${request.url}.";
-
-      if (request.url.host == "pub.dartlang.org" ||
-          request.url.host == "storage.googleapis.com") {
-        message += "\nThis is likely a transient error. Please try again "
-            "later.";
-      }
-
-      fail(message);
-    }
-
-    throw new PubHttpException(
-        await http.Response.fromStream(streamedResponse));
+    return streamedResponse;
   }
 
   /// Whether extra metadata headers should be added to [request].
@@ -216,10 +184,48 @@
 /// we're waiting for them to come back up.
 final _retriedHosts = new Set<String>();
 
+/// Intercepts all requests and throws exceptions if the response was not
+/// considered successful.
+class _ThrowingClient extends http.BaseClient {
+  final http.Client _inner;
+
+  _ThrowingClient(this._inner);
+
+  Future<http.StreamedResponse> send(http.BaseRequest request) async {
+    final streamedResponse = await _inner.send(request);
+
+    var status = streamedResponse.statusCode;
+    // 401 responses should be handled by the OAuth2 client. It's very
+    // unlikely that they'll be returned by non-OAuth2 requests. We also want
+    // to pass along 400 responses from the token endpoint.
+    var tokenRequest = streamedResponse.request.url == oauth2.tokenEndpoint;
+    if (status < 400 || status == 401 || (status == 400 && tokenRequest)) {
+      return streamedResponse;
+    }
+
+    if (status == 406 &&
+        request.headers['Accept'] == PUB_API_HEADERS['Accept']) {
+      fail("Pub ${sdk.version} is incompatible with the current version of "
+          "${request.url.host}.\n"
+          "Upgrade pub to the latest version and try again.");
+    }
+
+    if (status == 500 &&
+        (request.url.host == "pub.dartlang.org" ||
+            request.url.host == "storage.googleapis.com")) {
+      fail("HTTP error 500: Internal Server Error at ${request.url}.\n"
+          "This is likely a transient error. Please try again later.");
+    }
+
+    throw new PubHttpException(
+        await http.Response.fromStream(streamedResponse));
+  }
+}
+
 /// The HTTP client to use for all HTTP requests.
 final httpClient = new ThrottleClient(
     16,
-    new RetryClient(_pubClient,
+    new _ThrowingClient(new RetryClient(_pubClient,
         retries: 5,
         when: (response) => const [502, 503, 504].contains(response.statusCode),
         delay: (retryCount) {
@@ -245,7 +251,7 @@
           log.message(
               "It looks like ${request.url.host} is having some trouble.\n"
               "Pub will wait for a while before trying to connect again.");
-        }));
+        })));
 
 /// The underlying HTTP client wrapped by [httpClient].
 http.Client get innerHttpClient => _pubClient._inner;