Remove fetch.dart (#3584)

These uses cases are now address by http.dart via http.readDataPipe.
diff --git a/examples/layers/services/media_service.dart b/examples/layers/services/media_service.dart
index 2bb0e4a..301955c 100644
--- a/examples/layers/services/media_service.dart
+++ b/examples/layers/services/media_service.dart
@@ -4,11 +4,11 @@
 
 import 'dart:async';
 
-import 'package:mojo/mojo/url_response.mojom.dart';
 import 'package:sky_services/media/media.mojom.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter/rendering.dart';
 import 'package:flutter/services.dart';
+import 'package:flutter/http.dart' as http;
 
 // All of these sounds are marked as public domain at soundbible.
 const String chimes = "http://soundbible.com/grab.php?id=2030&type=wav";
@@ -42,8 +42,7 @@
   Future<Null> load(MediaServiceProxy mediaService) async {
     try {
       mediaService.ptr.createPlayer(player);
-      UrlResponse response = await fetchUrl(soundUrl);
-      await player.ptr.prepare(response.body);
+      await player.ptr.prepare(await http.readDataPipe(soundUrl));
     } catch (e) {
       print("Error: failed to load sound file $soundUrl");
       player.close();
diff --git a/packages/flutter/lib/services.dart b/packages/flutter/lib/services.dart
index 6562107..73e1205 100644
--- a/packages/flutter/lib/services.dart
+++ b/packages/flutter/lib/services.dart
@@ -18,7 +18,6 @@
 export 'src/services/app_messages.dart';
 export 'src/services/asset_bundle.dart';
 export 'src/services/binding.dart';
-export 'src/services/fetch.dart';
 export 'src/services/image_cache.dart';
 export 'src/services/image_decoder.dart';
 export 'src/services/image_resource.dart';
diff --git a/packages/flutter/lib/src/http/http.dart b/packages/flutter/lib/src/http/http.dart
index 3d28a3d..d3149e9 100644
--- a/packages/flutter/lib/src/http/http.dart
+++ b/packages/flutter/lib/src/http/http.dart
@@ -6,6 +6,8 @@
 import 'dart:convert';
 import 'dart:typed_data';
 
+import 'package:mojo/core.dart' as mojo;
+
 import 'mojo_client.dart';
 import 'response.dart';
 
@@ -145,6 +147,20 @@
   return _withClient/*<Uint8List>*/((MojoClient client) => client.readBytes(url, headers: headers));
 }
 
+/// Sends an HTTP GET request with the given headers to the given URL, which can
+/// be a [Uri] or a [String], and returns a Future that completes to a data pipe
+/// containing the response bytes.
+///
+/// The Future will emit a [ClientException] if the response doesn't have a
+/// success status code.
+///
+/// This automatically initializes a new [MojoClient] and closes that client once
+/// the request is complete. If you're planning on making multiple requests to
+/// the same server, you should use a single [MojoClient] for all of those requests.
+Future<mojo.MojoDataPipeConsumer> readDataPipe(dynamic url, { Map<String, String> headers }) {
+  return _withClient/*<mojo.MojoDataPipeConsumer>*/((MojoClient client) => client.readDataPipe(url, headers: headers));
+}
+
 Future<dynamic/*=T*/> _withClient/*<T>*/(Future<dynamic/*=T*/> fn(MojoClient client)) {
   return fn(new MojoClient());
 }
diff --git a/packages/flutter/lib/src/http/mojo_client.dart b/packages/flutter/lib/src/http/mojo_client.dart
index caccef2..682bb9a 100644
--- a/packages/flutter/lib/src/http/mojo_client.dart
+++ b/packages/flutter/lib/src/http/mojo_client.dart
@@ -124,8 +124,31 @@
     });
   }
 
-  Future<Response> _send(String method, dynamic url, Map<String, String> headers, [dynamic body, Encoding encoding = UTF8]) async {
+  Future<mojo.MojoDataPipeConsumer> readDataPipe(dynamic url, { Map<String, String> headers }) async {
     mojom.UrlLoaderProxy loader = new mojom.UrlLoaderProxy.unbound();
+    mojom.UrlRequest request = _prepareRequest('get', url, headers);
+    mojom.UrlResponse response;
+    try {
+      networkService.ptr.createUrlLoader(loader);
+      response = (await loader.ptr.start(request)).response;
+    } catch (exception, stack) {
+      FlutterError.reportError(new FlutterErrorDetails(
+        exception: exception,
+        stack: stack,
+        library: 'networking HTTP library',
+        context: 'while sending bytes to the Mojo network library',
+        silent: true
+      ));
+      return null;
+    } finally {
+      loader.close();
+    }
+    if (response.statusCode < 400)
+      return response.body;
+    throw new Exception("Request to $url failed with status ${response.statusCode}.");
+  }
+
+  mojom.UrlRequest _prepareRequest(String method, dynamic url, Map<String, String> headers, [dynamic body, Encoding encoding = UTF8]) {
     List<mojom.HttpHeader> mojoHeaders = <mojom.HttpHeader>[];
     headers?.forEach((String name, String value) {
       mojom.HttpHeader header = new mojom.HttpHeader()
@@ -144,6 +167,12 @@
       ByteData data = new ByteData.view(encodedBody.buffer);
       mojo.DataPipeFiller.fillHandle(pipe.producer, data);
     }
+    return request;
+  }
+
+  Future<Response> _send(String method, dynamic url, Map<String, String> headers, [dynamic body, Encoding encoding = UTF8]) async {
+    mojom.UrlLoaderProxy loader = new mojom.UrlLoaderProxy.unbound();
+    mojom.UrlRequest request = _prepareRequest(method, url, headers, body, encoding);
     try {
       networkService.ptr.createUrlLoader(loader);
       mojom.UrlResponse response = (await loader.ptr.start(request)).response;
diff --git a/packages/flutter/lib/src/services/asset_bundle.dart b/packages/flutter/lib/src/services/asset_bundle.dart
index 20041fd..12be90c 100644
--- a/packages/flutter/lib/src/services/asset_bundle.dart
+++ b/packages/flutter/lib/src/services/asset_bundle.dart
@@ -10,7 +10,6 @@
 import 'package:mojo/core.dart' as core;
 import 'package:mojo_services/mojo/asset_bundle/asset_bundle.mojom.dart';
 
-import 'fetch.dart';
 import 'image_cache.dart';
 import 'image_decoder.dart';
 import 'image_resource.dart';
@@ -34,7 +33,7 @@
 
   @override
   Future<core.MojoDataPipeConsumer> load(String key) async {
-    return (await fetchUrl(_urlFromKey(key))).body;
+    return await http.readDataPipe(_urlFromKey(key));
   }
 
   @override
@@ -88,7 +87,7 @@
   }
 
   static Future<Null> _fetchAndUnpackBundle(String relativeUrl, AssetBundleProxy bundle) async {
-    core.MojoDataPipeConsumer bundleData = (await fetchUrl(relativeUrl)).body;
+    core.MojoDataPipeConsumer bundleData = await http.readDataPipe(Uri.base.resolve(relativeUrl));
     AssetUnpackerProxy unpacker = new AssetUnpackerProxy.unbound();
     shell.connectToService("mojo:asset_bundle", unpacker);
     unpacker.ptr.unpackZipStream(bundleData, bundle);
diff --git a/packages/flutter/lib/src/services/fetch.dart b/packages/flutter/lib/src/services/fetch.dart
deleted file mode 100644
index 74e09ff..0000000
--- a/packages/flutter/lib/src/services/fetch.dart
+++ /dev/null
@@ -1,53 +0,0 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import 'dart:async';
-
-import 'package:flutter/foundation.dart';
-import 'package:mojo/mojo/url_request.mojom.dart' as mojom;
-import 'package:mojo/mojo/url_response.mojom.dart' as mojom;
-import 'package:mojo_services/mojo/url_loader.mojom.dart' as mojom;
-
-import '../http/mojo_client.dart'; // TODO(ianh): clean this up, see https://github.com/flutter/flutter/issues/2889
-
-export 'package:mojo/mojo/url_response.mojom.dart' show UrlResponse;
-
-Future<mojom.UrlResponse> fetch(mojom.UrlRequest request, { bool require200: false }) async {
-  mojom.UrlLoaderProxy loader = new mojom.UrlLoaderProxy.unbound();
-  try {
-    MojoClient.networkService.ptr.createUrlLoader(loader);
-    mojom.UrlResponse response = (await loader.ptr.start(request)).response;
-    if (require200 && (response.error != null || response.statusCode != 200)) {
-      StringBuffer message = new StringBuffer();
-      message.writeln('Could not ${request.method ?? "fetch"} ${request.url ?? "resource"}');
-      if (response.error != null)
-        message.writeln('Network error: ${response.error.code} ${response.error.description ?? "<unknown network error>"}');
-      if (response.statusCode != 200)
-        message.writeln('Protocol error: ${response.statusCode} ${response.statusLine ?? "<no server message>"}');
-      if (response.url != request.url)
-        message.writeln('Final URL after redirects was: ${response.url}');
-      throw message; // this is not a FlutterError, because it's a real error, not an assertion
-    }
-    return response;
-  } catch (exception, stack) {
-    FlutterError.reportError(new FlutterErrorDetails(
-      exception: exception,
-      stack: stack,
-      library: 'fetch service',
-      context: 'while sending bytes to the Mojo network library',
-      silent: true
-    ));
-    return null;
-  } finally {
-    loader.close();
-  }
-}
-
-Future<mojom.UrlResponse> fetchUrl(String relativeUrl, { bool require200: false }) {
-  String url = Uri.base.resolve(relativeUrl).toString();
-  mojom.UrlRequest request = new mojom.UrlRequest()
-    ..url = url
-    ..autoFollowRedirects = true;
-  return fetch(request, require200: require200);
-}
diff --git a/packages/flutter/lib/src/services/image_cache.dart b/packages/flutter/lib/src/services/image_cache.dart
index 560e182..66298fd 100644
--- a/packages/flutter/lib/src/services/image_cache.dart
+++ b/packages/flutter/lib/src/services/image_cache.dart
@@ -6,9 +6,9 @@
 import 'dart:collection';
 import 'dart:ui' show hashValues;
 
-import 'package:mojo/mojo/url_response.mojom.dart';
+import 'package:flutter/foundation.dart';
+import 'package:flutter/http.dart' as http;
 
-import 'fetch.dart';
 import 'image_decoder.dart';
 import 'image_resource.dart';
 
@@ -52,14 +52,21 @@
 
   @override
   Future<ImageInfo> loadImage() async {
-    UrlResponse response = await fetchUrl(_url, require200: true);
-    if (response != null) {
+    try {
       return new ImageInfo(
-        image: await decodeImageFromDataPipe(response.body),
+        image: await decodeImageFromDataPipe(await http.readDataPipe(Uri.base.resolve(_url))),
         scale: _scale
       );
+    } catch (exception, stack) {
+      FlutterError.reportError(new FlutterErrorDetails(
+        exception: exception,
+        stack: stack,
+        library: 'services library',
+        context: 'while fetching an image for the image cache',
+        silent: true
+      ));
+      return null;
     }
-    return null;
   }
 
   @override