Add http package
Change-Id: I88b88e8c32c462bef1115a8dc8b0978831e528e3
diff --git a/importer/importer.py b/importer/importer.py
index 6c5642e..6f729ef 100755
--- a/importer/importer.py
+++ b/importer/importer.py
@@ -26,7 +26,6 @@
LOCAL_PACKAGES = {
'analyzer': '//dart/pkg/analyzer',
'flutter': '//lib/flutter/packages/flutter',
- 'http': '//apps/modules/packages/flutter-http',
'linter': '//dart/third_party/pkg/linter',
'typed_mock': '//dart/pkg/typed_mock',
}
diff --git a/pub/_discoveryapis_commons/BUILD.gn b/pub/_discoveryapis_commons/BUILD.gn
index 335cceb..1439d62 100644
--- a/pub/_discoveryapis_commons/BUILD.gn
+++ b/pub/_discoveryapis_commons/BUILD.gn
@@ -10,6 +10,6 @@
disable_analysis = true
deps = [
- "//apps/modules/packages/flutter-http",
+ "//third_party/dart-pkg/pub/http",
]
}
diff --git a/pub/googleapis/BUILD.gn b/pub/googleapis/BUILD.gn
index 1a7b13c..6729abe 100644
--- a/pub/googleapis/BUILD.gn
+++ b/pub/googleapis/BUILD.gn
@@ -10,7 +10,7 @@
disable_analysis = true
deps = [
- "//apps/modules/packages/flutter-http",
+ "//third_party/dart-pkg/pub/http",
"//third_party/dart-pkg/pub/_discoveryapis_commons",
]
}
diff --git a/pub/googleapis_auth/BUILD.gn b/pub/googleapis_auth/BUILD.gn
index 9ce54b2..a0c8170 100644
--- a/pub/googleapis_auth/BUILD.gn
+++ b/pub/googleapis_auth/BUILD.gn
@@ -10,7 +10,7 @@
disable_analysis = true
deps = [
- "//apps/modules/packages/flutter-http",
+ "//third_party/dart-pkg/pub/http",
"//third_party/dart-pkg/pub/crypto",
]
}
diff --git a/pub/http/.analysis_options b/pub/http/.analysis_options
new file mode 100644
index 0000000..a10d4c5
--- /dev/null
+++ b/pub/http/.analysis_options
@@ -0,0 +1,2 @@
+analyzer:
+ strong-mode: true
diff --git a/pub/http/.gitignore b/pub/http/.gitignore
new file mode 100644
index 0000000..7dbf035
--- /dev/null
+++ b/pub/http/.gitignore
@@ -0,0 +1,15 @@
+# Don’t commit the following directories created by pub.
+.buildlog
+.pub/
+build/
+packages
+.packages
+
+# Or the files created by dart2js.
+*.dart.js
+*.js_
+*.js.deps
+*.js.map
+
+# Include when developing application packages.
+pubspec.lock
\ No newline at end of file
diff --git a/pub/http/.status b/pub/http/.status
new file mode 100644
index 0000000..8e10eb2
--- /dev/null
+++ b/pub/http/.status
@@ -0,0 +1,25 @@
+# Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file
+# for details. All rights reserved. Use of this source code is governed by a
+# BSD-style license that can be found in the LICENSE file.
+
+# Skip non-test files ending with "_test".
+packages/*: Skip
+*/packages/*: Skip
+*/*/packages/*: Skip
+*/*/*/packages/*: Skip
+*/*/*/*packages/*: Skip
+*/*/*/*/*packages/*: Skip
+
+# Only run tests from the build directory, since we don't care about the
+# difference between transformed an untransformed code.
+test/*: Skip
+
+[ $browser ]
+build/test/io/*: Fail, OK # Uses dart:io.
+
+[ $runtime == vm ]
+build/test/html/*: Skip # Uses dart:html.
+
+[ $runtime == drt ]
+build/test/html/client_test: Skip # Issue 18566
+
diff --git a/pub/http/BUILD.gn b/pub/http/BUILD.gn
new file mode 100644
index 0000000..492b1b2
--- /dev/null
+++ b/pub/http/BUILD.gn
@@ -0,0 +1,19 @@
+# This file is generated by importer.py for http-0.11.3+12
+
+import("//build/dart/dart_package.gni")
+
+dart_package("http") {
+ package_name = "http"
+
+ source_dir = "lib"
+
+ disable_analysis = true
+
+ deps = [
+ "//third_party/dart-pkg/pub/async",
+ "//third_party/dart-pkg/pub/path",
+ "//third_party/dart-pkg/pub/http_parser",
+ "//third_party/dart-pkg/pub/collection",
+ "//third_party/dart-pkg/pub/stack_trace",
+ ]
+}
diff --git a/pub/http/CHANGELOG.md b/pub/http/CHANGELOG.md
new file mode 100644
index 0000000..4a5cb76
--- /dev/null
+++ b/pub/http/CHANGELOG.md
@@ -0,0 +1,104 @@
+## 0.11.3+12
+
+* Don't quote the boundary header for `MultipartRequest`. This is more
+ compatible with server quirks.
+
+## 0.11.3+11
+
+* Fix the SDK constraint to only include SDK versions that support importing
+ `dart:io` everywhere.
+
+## 0.11.3+10
+
+* Stop using `dart:mirrors`.
+
+## 0.11.3+9
+
+* Remove an extra newline in multipart chunks.
+
+## 0.11.3+8
+
+* Properly specify `Content-Transfer-Encoding` for multipart chunks.
+
+## 0.11.3+7
+
+* Declare compatibility with `http_parser` 3.0.0.
+
+## 0.11.3+6
+
+* Fix one more strong mode warning in `http/testing.dart`.
+
+## 0.11.3+5
+
+* Fix some lingering strong mode warnings.
+
+## 0.11.3+4
+
+* Fix all strong mode warnings.
+
+## 0.11.3+3
+
+* Support `http_parser` 2.0.0.
+
+## 0.11.3+2
+
+* Require Dart SDK >= 1.9.0
+
+* Eliminate many uses of `Chain.track` from the `stack_trace` package.
+
+## 0.11.3+1
+
+* Support `http_parser` 1.0.0.
+
+## 0.11.3
+
+* Add a `Client.patch` shortcut method and a matching top-level `patch` method.
+
+## 0.11.2
+
+* Add a `BrowserClient.withCredentials` property.
+
+## 0.11.1+3
+
+* Properly namespace an internal library name.
+
+## 0.11.1+2
+
+* Widen the version constraint on `unittest`.
+
+## 0.11.1+1
+
+* Widen the version constraint for `stack_trace`.
+
+## 0.11.1
+
+* Expose the `IOClient` class which wraps a `dart:io` `HttpClient`.
+
+## 0.11.0+1
+
+* Fix a bug in handling errors in decoding XMLHttpRequest responses for
+ `BrowserClient`.
+
+## 0.11.0
+
+* The package no longer depends on `dart:io`. The `BrowserClient` class in
+ `package:http/browser_client.dart` can now be used to make requests on the
+ browser.
+
+* Change `MultipartFile.contentType` from `dart:io`'s `ContentType` type to
+ `http_parser`'s `MediaType` type.
+
+* Exceptions are now of type `ClientException` rather than `dart:io`'s
+ `HttpException`.
+
+## 0.10.0
+
+* Make `BaseRequest.contentLength` and `BaseResponse.contentLength` use `null`
+ to indicate an unknown content length rather than -1.
+
+* The `contentLength` parameter to `new BaseResponse` is now named rather than
+ positional.
+
+* Make request headers case-insensitive.
+
+* Make `MultipartRequest` more closely adhere to browsers' encoding conventions.
diff --git a/pub/http/LICENSE b/pub/http/LICENSE
new file mode 100644
index 0000000..5c60afe
--- /dev/null
+++ b/pub/http/LICENSE
@@ -0,0 +1,26 @@
+Copyright 2014, the Dart project authors. All rights reserved.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+ * Neither the name of Google Inc. nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/pub/http/README.md b/pub/http/README.md
new file mode 100644
index 0000000..506bbad
--- /dev/null
+++ b/pub/http/README.md
@@ -0,0 +1,100 @@
+# http
+
+A composable, Future-based library for making HTTP requests.
+
+This package contains a set of high-level functions and classes that make it
+easy to consume HTTP resources. It's platform-independent, and can be used on
+both the command-line and the browser. Currently the global utility functions
+are unsupported on the browser; see "Using on the Browser" below.
+
+## Using
+
+The easiest way to use this library is via the top-level functions, although
+they currently only work on platforms where `dart:io` is available. They allow
+you to make individual HTTP requests with minimal hassle:
+
+```dart
+import 'package:http/http.dart' as http;
+
+var url = "http://example.com/whatsit/create";
+http.post(url, body: {"name": "doodle", "color": "blue"})
+ .then((response) {
+ print("Response status: ${response.statusCode}");
+ print("Response body: ${response.body}");
+});
+
+http.read("http://example.com/foobar.txt").then(print);
+```
+
+If you're making multiple requests to the same server, you can keep open a
+persistent connection by using a [Client][] rather than making one-off requests.
+If you do this, make sure to close the client when you're done:
+
+```dart
+var client = new http.Client();
+client.post(
+ "http://example.com/whatsit/create",
+ body: {"name": "doodle", "color": "blue"})
+ .then((response) => client.get(response.bodyFields['uri']))
+ .then((response) => print(response.body))
+ .whenComplete(client.close);
+```
+
+You can also exert more fine-grained control over your requests and responses by
+creating [Request][] or [StreamedRequest][] objects yourself and passing them to
+[Client.send][].
+
+[Request]: https://www.dartdocs.org/documentation/http/latest/http/Request-class.html
+[StreamedRequest]: https://www.dartdocs.org/documentation/http/latest/http/StreamedRequest-class.html
+[Client.send]: https://www.dartdocs.org/documentation/http/latest/http/Client/send.html
+
+This package is designed to be composable. This makes it easy for external
+libraries to work with one another to add behavior to it. Libraries wishing to
+add behavior should create a subclass of [BaseClient][] that wraps another
+[Client][] and adds the desired behavior:
+
+[BaseClient]: https://www.dartdocs.org/documentation/http/latest/http/BaseClient-class.html
+[Client]: https://www.dartdocs.org/documentation/http/latest/http/Client-class.html
+
+```dart
+class UserAgentClient extends http.BaseClient {
+ final String userAgent;
+ final http.Client _inner;
+
+ UserAgentClient(this.userAgent, this._inner);
+
+ Future<StreamedResponse> send(BaseRequest request) {
+ request.headers['user-agent'] = userAgent;
+ return _inner.send(request);
+ }
+}
+```
+
+## Using on the Browser
+
+The HTTP library can be used on the browser via the [BrowserClient][] class in
+`package:http/browser_client.dart`. This client translates requests into
+XMLHttpRequests. For example:
+
+[BrowserClient]: https://www.dartdocs.org/documentation/http/latest/http.browser_client/BrowserClient-class.html
+
+```dart
+import 'dart:async';
+import 'package:http/browser_client.dart';
+
+main() async {
+ var client = new BrowserClient();
+ var url = '/whatsit/create';
+ var response =
+ await client.post(url, body: {'name': 'doodle', 'color': 'blue'});
+ print('Response status: ${response.statusCode}');
+ print('Response body: ${response.body}');
+}
+```
+
+## Filing issues
+
+Please file issues for the http package at [http://dartbug.com/new][bugs].
+
+[bugs]: http://dartbug.com/new
+[docs]: https://api.dartlang.org/docs/channels/dev/latest/http.html
diff --git a/pub/http/codereview.settings b/pub/http/codereview.settings
new file mode 100644
index 0000000..f980465
--- /dev/null
+++ b/pub/http/codereview.settings
@@ -0,0 +1,3 @@
+CODE_REVIEW_SERVER: http://codereview.chromium.org/
+VIEW_VC: https://github.com/dart-lang/http/commit/
+CC_LIST: reviews@dartlang.org
\ No newline at end of file
diff --git a/pub/http/lib/browser_client.dart b/pub/http/lib/browser_client.dart
new file mode 100644
index 0000000..309b3ac
--- /dev/null
+++ b/pub/http/lib/browser_client.dart
@@ -0,0 +1,111 @@
+// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
+// for details. 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 'dart:html';
+import 'dart:typed_data';
+
+import 'package:stack_trace/stack_trace.dart';
+
+import 'src/base_client.dart';
+import 'src/base_request.dart';
+import 'src/byte_stream.dart';
+import 'src/exception.dart';
+import 'src/streamed_response.dart';
+
+// TODO(nweiz): Move this under src/, re-export from lib/http.dart, and use this
+// automatically from [new Client] once sdk#24581 is fixed.
+
+/// A `dart:html`-based HTTP client that runs in the browser and is backed by
+/// XMLHttpRequests.
+///
+/// This client inherits some of the limitations of XMLHttpRequest. It ignores
+/// the [BaseRequest.contentLength], [BaseRequest.persistentConnection],
+/// [BaseRequest.followRedirects], and [BaseRequest.maxRedirects] fields. It is
+/// also unable to stream requests or responses; a request will only be sent and
+/// a response will only be returned once all the data is available.
+class BrowserClient extends BaseClient {
+ /// The currently active XHRs.
+ ///
+ /// These are aborted if the client is closed.
+ final _xhrs = new Set<HttpRequest>();
+
+ /// Creates a new HTTP client.
+ BrowserClient();
+
+ /// Whether to send credentials such as cookies or authorization headers for
+ /// cross-site requests.
+ ///
+ /// Defaults to `false`.
+ bool withCredentials = false;
+
+ /// Sends an HTTP request and asynchronously returns the response.
+ Future<StreamedResponse> send(BaseRequest request) async {
+ var bytes = await request.finalize().toBytes();
+ var xhr = new HttpRequest();
+ _xhrs.add(xhr);
+ _openHttpRequest(xhr, request.method, request.url.toString(), asynch: true);
+ xhr.responseType = 'blob';
+ xhr.withCredentials = withCredentials;
+ request.headers.forEach(xhr.setRequestHeader);
+
+ var completer = new Completer<StreamedResponse>();
+ xhr.onLoad.first.then((_) {
+ // TODO(nweiz): Set the response type to "arraybuffer" when issue 18542
+ // is fixed.
+ var blob = xhr.response == null ? new Blob([]) : xhr.response;
+ var reader = new FileReader();
+
+ reader.onLoad.first.then((_) {
+ var body = reader.result as Uint8List;
+ completer.complete(new StreamedResponse(
+ new ByteStream.fromBytes(body),
+ xhr.status,
+ contentLength: body.length,
+ request: request,
+ headers: xhr.responseHeaders,
+ reasonPhrase: xhr.statusText));
+ });
+
+ reader.onError.first.then((error) {
+ completer.completeError(
+ new ClientException(error.toString(), request.url),
+ new Chain.current());
+ });
+
+ reader.readAsArrayBuffer(blob);
+ });
+
+ xhr.onError.first.then((_) {
+ // Unfortunately, the underlying XMLHttpRequest API doesn't expose any
+ // specific information about the error itself.
+ completer.completeError(
+ new ClientException("XMLHttpRequest error.", request.url),
+ new Chain.current());
+ });
+
+ xhr.send(bytes);
+
+ try {
+ return await completer.future;
+ } finally {
+ _xhrs.remove(xhr);
+ }
+ }
+
+ // TODO(nweiz): Remove this when sdk#24637 is fixed.
+ void _openHttpRequest(HttpRequest request, String method, String url,
+ {bool asynch, String user, String password}) {
+ request.open(method, url, async: asynch, user: user, password: password);
+ }
+
+ /// Closes the client.
+ ///
+ /// This terminates all active requests.
+ void close() {
+ for (var xhr in _xhrs) {
+ xhr.abort();
+ }
+ }
+}
diff --git a/pub/http/lib/http.dart b/pub/http/lib/http.dart
new file mode 100644
index 0000000..86bcefb
--- /dev/null
+++ b/pub/http/lib/http.dart
@@ -0,0 +1,171 @@
+// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/// A composable, [Future]-based library for making HTTP requests.
+import 'dart:async';
+import 'dart:convert';
+import 'dart:typed_data';
+
+import 'src/client.dart';
+import 'src/response.dart';
+
+export 'src/base_client.dart';
+export 'src/base_request.dart';
+export 'src/base_response.dart';
+export 'src/byte_stream.dart';
+export 'src/client.dart';
+export 'src/exception.dart';
+export 'src/io_client.dart';
+export 'src/multipart_file.dart';
+export 'src/multipart_request.dart';
+export 'src/request.dart';
+export 'src/response.dart';
+export 'src/streamed_request.dart';
+export 'src/streamed_response.dart';
+
+/// Sends an HTTP HEAD request with the given headers to the given URL, which
+/// can be a [Uri] or a [String].
+///
+/// This automatically initializes a new [Client] 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 [Client] for all of those requests.
+///
+/// For more fine-grained control over the request, use [Request] instead.
+Future<Response> head(url, {Map<String, String> headers}) =>
+ _withClient((client) => client.head(url, headers: headers));
+
+/// Sends an HTTP GET request with the given headers to the given URL, which can
+/// be a [Uri] or a [String].
+///
+/// This automatically initializes a new [Client] 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 [Client] for all of those requests.
+///
+/// For more fine-grained control over the request, use [Request] instead.
+Future<Response> get(url, {Map<String, String> headers}) =>
+ _withClient((client) => client.get(url, headers: headers));
+
+/// Sends an HTTP POST request with the given headers and body to the given URL,
+/// which can be a [Uri] or a [String].
+///
+/// [body] sets the body of the request. It can be a [String], a [List<int>] or
+/// a [Map<String, String>]. If it's a String, it's encoded using [encoding] and
+/// used as the body of the request. The content-type of the request will
+/// default to "text/plain".
+///
+/// If [body] is a List, it's used as a list of bytes for the body of the
+/// request.
+///
+/// If [body] is a Map, it's encoded as form fields using [encoding]. The
+/// content-type of the request will be set to
+/// `"application/x-www-form-urlencoded"`; this cannot be overridden.
+///
+/// [encoding] defaults to [UTF8].
+///
+/// For more fine-grained control over the request, use [Request] or
+/// [StreamedRequest] instead.
+Future<Response> post(url, {Map<String, String> headers, body,
+ Encoding encoding}) =>
+ _withClient((client) => client.post(url,
+ headers: headers, body: body, encoding: encoding));
+
+/// Sends an HTTP PUT request with the given headers and body to the given URL,
+/// which can be a [Uri] or a [String].
+///
+/// [body] sets the body of the request. It can be a [String], a [List<int>] or
+/// a [Map<String, String>]. If it's a String, it's encoded using [encoding] and
+/// used as the body of the request. The content-type of the request will
+/// default to "text/plain".
+///
+/// If [body] is a List, it's used as a list of bytes for the body of the
+/// request.
+///
+/// If [body] is a Map, it's encoded as form fields using [encoding]. The
+/// content-type of the request will be set to
+/// `"application/x-www-form-urlencoded"`; this cannot be overridden.
+///
+/// [encoding] defaults to [UTF8].
+///
+/// For more fine-grained control over the request, use [Request] or
+/// [StreamedRequest] instead.
+Future<Response> put(url, {Map<String, String> headers, body,
+ Encoding encoding}) =>
+ _withClient((client) => client.put(url,
+ headers: headers, body: body, encoding: encoding));
+
+/// Sends an HTTP PATCH request with the given headers and body to the given
+/// URL, which can be a [Uri] or a [String].
+///
+/// [body] sets the body of the request. It can be a [String], a [List<int>] or
+/// a [Map<String, String>]. If it's a String, it's encoded using [encoding] and
+/// used as the body of the request. The content-type of the request will
+/// default to "text/plain".
+///
+/// If [body] is a List, it's used as a list of bytes for the body of the
+/// request.
+///
+/// If [body] is a Map, it's encoded as form fields using [encoding]. The
+/// content-type of the request will be set to
+/// `"application/x-www-form-urlencoded"`; this cannot be overridden.
+///
+/// [encoding] defaults to [UTF8].
+///
+/// For more fine-grained control over the request, use [Request] or
+/// [StreamedRequest] instead.
+Future<Response> patch(url, {Map<String, String> headers, body,
+ Encoding encoding}) =>
+ _withClient((client) => client.patch(url,
+ headers: headers, body: body, encoding: encoding));
+
+/// Sends an HTTP DELETE request with the given headers to the given URL, which
+/// can be a [Uri] or a [String].
+///
+/// This automatically initializes a new [Client] 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 [Client] for all of those requests.
+///
+/// For more fine-grained control over the request, use [Request] instead.
+Future<Response> delete(url, {Map<String, String> headers}) =>
+ _withClient((client) => client.delete(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 the body of
+/// the response as a [String].
+///
+/// The Future will emit a [ClientException] if the response doesn't have a
+/// success status code.
+///
+/// This automatically initializes a new [Client] 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 [Client] for all of those requests.
+///
+/// For more fine-grained control over the request and response, use [Request]
+/// instead.
+Future<String> read(url, {Map<String, String> headers}) =>
+ _withClient((client) => client.read(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 the body of
+/// the response as a list of bytes.
+///
+/// The Future will emit a [ClientException] if the response doesn't have a
+/// success status code.
+///
+/// This automatically initializes a new [Client] 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 [Client] for all of those requests.
+///
+/// For more fine-grained control over the request and response, use [Request]
+/// instead.
+Future<Uint8List> readBytes(url, {Map<String, String> headers}) =>
+ _withClient((client) => client.readBytes(url, headers: headers));
+
+Future<T> _withClient<T>(Future<T> fn(Client client)) async {
+ var client = new Client();
+ try {
+ return await fn(client);
+ } finally {
+ client.close();
+ }
+}
diff --git a/pub/http/lib/src/base_client.dart b/pub/http/lib/src/base_client.dart
new file mode 100644
index 0000000..7b3fbfa
--- /dev/null
+++ b/pub/http/lib/src/base_client.dart
@@ -0,0 +1,189 @@
+// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
+// for details. 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 'dart:convert';
+import 'dart:typed_data';
+
+import 'package:collection/collection.dart';
+
+import 'base_request.dart';
+import 'client.dart';
+import 'exception.dart';
+import 'request.dart';
+import 'response.dart';
+import 'streamed_response.dart';
+
+/// The abstract base class for an HTTP client. This is a mixin-style class;
+/// subclasses only need to implement [send] and maybe [close], and then they
+/// get various convenience methods for free.
+abstract class BaseClient implements Client {
+ /// Sends an HTTP HEAD request with the given headers to the given URL, which
+ /// can be a [Uri] or a [String].
+ ///
+ /// For more fine-grained control over the request, use [send] instead.
+ Future<Response> head(url, {Map<String, String> headers}) =>
+ _sendUnstreamed("HEAD", url, headers);
+
+ /// Sends an HTTP GET request with the given headers to the given URL, which
+ /// can be a [Uri] or a [String].
+ ///
+ /// For more fine-grained control over the request, use [send] instead.
+ Future<Response> get(url, {Map<String, String> headers}) =>
+ _sendUnstreamed("GET", url, headers);
+
+ /// Sends an HTTP POST request with the given headers and body to the given
+ /// URL, which can be a [Uri] or a [String].
+ ///
+ /// [body] sets the body of the request. It can be a [String], a [List<int>]
+ /// or a [Map<String, String>]. If it's a String, it's encoded using
+ /// [encoding] and used as the body of the request. The content-type of the
+ /// request will default to "text/plain".
+ ///
+ /// If [body] is a List, it's used as a list of bytes for the body of the
+ /// request.
+ ///
+ /// If [body] is a Map, it's encoded as form fields using [encoding]. The
+ /// content-type of the request will be set to
+ /// `"application/x-www-form-urlencoded"`; this cannot be overridden.
+ ///
+ /// [encoding] defaults to UTF-8.
+ ///
+ /// For more fine-grained control over the request, use [send] instead.
+ Future<Response> post(url, {Map<String, String> headers, body,
+ Encoding encoding}) =>
+ _sendUnstreamed("POST", url, headers, body, encoding);
+
+ /// Sends an HTTP PUT request with the given headers and body to the given
+ /// URL, which can be a [Uri] or a [String].
+ ///
+ /// [body] sets the body of the request. It can be a [String], a [List<int>]
+ /// or a [Map<String, String>]. If it's a String, it's encoded using
+ /// [encoding] and used as the body of the request. The content-type of the
+ /// request will default to "text/plain".
+ ///
+ /// If [body] is a List, it's used as a list of bytes for the body of the
+ /// request.
+ ///
+ /// If [body] is a Map, it's encoded as form fields using [encoding]. The
+ /// content-type of the request will be set to
+ /// `"application/x-www-form-urlencoded"`; this cannot be overridden.
+ ///
+ /// [encoding] defaults to UTF-8.
+ ///
+ /// For more fine-grained control over the request, use [send] instead.
+ Future<Response> put(url, {Map<String, String> headers, body,
+ Encoding encoding}) =>
+ _sendUnstreamed("PUT", url, headers, body, encoding);
+
+ /// Sends an HTTP PATCH request with the given headers and body to the given
+ /// URL, which can be a [Uri] or a [String].
+ ///
+ /// [body] sets the body of the request. It can be a [String], a [List<int>]
+ /// or a [Map<String, String>]. If it's a String, it's encoded using
+ /// [encoding] and used as the body of the request. The content-type of the
+ /// request will default to "text/plain".
+ ///
+ /// If [body] is a List, it's used as a list of bytes for the body of the
+ /// request.
+ ///
+ /// If [body] is a Map, it's encoded as form fields using [encoding]. The
+ /// content-type of the request will be set to
+ /// `"application/x-www-form-urlencoded"`; this cannot be overridden.
+ ///
+ /// [encoding] defaults to UTF-8.
+ ///
+ /// For more fine-grained control over the request, use [send] instead.
+ Future<Response> patch(url, {Map<String, String> headers, body,
+ Encoding encoding}) =>
+ _sendUnstreamed("PATCH", url, headers, body, encoding);
+
+ /// Sends an HTTP DELETE request with the given headers to the given URL,
+ /// which can be a [Uri] or a [String].
+ ///
+ /// For more fine-grained control over the request, use [send] instead.
+ Future<Response> delete(url, {Map<String, String> headers}) =>
+ _sendUnstreamed("DELETE", url, 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 the
+ /// body of the response as a String.
+ ///
+ /// The Future will emit a [ClientException] if the response doesn't have a
+ /// success status code.
+ ///
+ /// For more fine-grained control over the request and response, use [send] or
+ /// [get] instead.
+ Future<String> read(url, {Map<String, String> headers}) {
+ return get(url, headers: headers).then((response) {
+ _checkResponseSuccess(url, response);
+ return response.body;
+ });
+ }
+
+ /// 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 the
+ /// body of the response as a list of bytes.
+ ///
+ /// The Future will emit an [ClientException] if the response doesn't have a
+ /// success status code.
+ ///
+ /// For more fine-grained control over the request and response, use [send] or
+ /// [get] instead.
+ Future<Uint8List> readBytes(url, {Map<String, String> headers}) {
+ return get(url, headers: headers).then((response) {
+ _checkResponseSuccess(url, response);
+ return response.bodyBytes;
+ });
+ }
+
+ /// Sends an HTTP request and asynchronously returns the response.
+ ///
+ /// Implementers should call [BaseRequest.finalize] to get the body of the
+ /// request as a [ByteStream]. They shouldn't make any assumptions about the
+ /// state of the stream; it could have data written to it asynchronously at a
+ /// later point, or it could already be closed when it's returned. Any
+ /// internal HTTP errors should be wrapped as [ClientException]s.
+ Future<StreamedResponse> send(BaseRequest request);
+
+ /// Sends a non-streaming [Request] and returns a non-streaming [Response].
+ Future<Response> _sendUnstreamed(String method, url,
+ Map<String, String> headers, [body, Encoding encoding]) async {
+
+ if (url is String) url = Uri.parse(url);
+ var request = new Request(method, url);
+
+ if (headers != null) request.headers.addAll(headers);
+ if (encoding != null) request.encoding = encoding;
+ if (body != null) {
+ if (body is String) {
+ request.body = body;
+ } else if (body is List) {
+ request.bodyBytes = DelegatingList.typed(body);
+ } else if (body is Map) {
+ request.bodyFields = DelegatingMap.typed(body);
+ } else {
+ throw new ArgumentError('Invalid request body "$body".');
+ }
+ }
+
+ return Response.fromStream(await send(request));
+ }
+
+ /// Throws an error if [response] is not successful.
+ void _checkResponseSuccess(url, Response response) {
+ if (response.statusCode < 400) return;
+ var message = "Request to $url failed with status ${response.statusCode}";
+ if (response.reasonPhrase != null) {
+ message = "$message: ${response.reasonPhrase}";
+ }
+ if (url is String) url = Uri.parse(url);
+ throw new ClientException("$message.", url);
+ }
+
+ /// Closes the client and cleans up any resources associated with it. It's
+ /// important to close each client when it's done being used; failing to do so
+ /// can cause the Dart process to hang.
+ void close() {}
+}
diff --git a/pub/http/lib/src/base_request.dart b/pub/http/lib/src/base_request.dart
new file mode 100644
index 0000000..b11ef05
--- /dev/null
+++ b/pub/http/lib/src/base_request.dart
@@ -0,0 +1,140 @@
+// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
+// for details. 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 'dart:collection';
+
+import 'byte_stream.dart';
+import 'client.dart';
+import 'streamed_response.dart';
+import 'utils.dart';
+
+/// The base class for HTTP requests.
+///
+/// Subclasses of [BaseRequest] can be constructed manually and passed to
+/// [BaseClient.send], which allows the user to provide fine-grained control
+/// over the request properties. However, usually it's easier to use convenience
+/// methods like [get] or [BaseClient.get].
+abstract class BaseRequest {
+ /// The HTTP method of the request. Most commonly "GET" or "POST", less
+ /// commonly "HEAD", "PUT", or "DELETE". Non-standard method names are also
+ /// supported.
+ final String method;
+
+ /// The URL to which the request will be sent.
+ final Uri url;
+
+ /// The size of the request body, in bytes.
+ ///
+ /// This defaults to `null`, which indicates that the size of the request is
+ /// not known in advance.
+ int get contentLength => _contentLength;
+ int _contentLength;
+
+ set contentLength(int value) {
+ if (value != null && value < 0) {
+ throw new ArgumentError("Invalid content length $value.");
+ }
+ _checkFinalized();
+ _contentLength = value;
+ }
+
+ /// Whether a persistent connection should be maintained with the server.
+ /// Defaults to true.
+ bool get persistentConnection => _persistentConnection;
+ bool _persistentConnection = true;
+
+ set persistentConnection(bool value) {
+ _checkFinalized();
+ _persistentConnection = value;
+ }
+
+ /// Whether the client should follow redirects while resolving this request.
+ /// Defaults to true.
+ bool get followRedirects => _followRedirects;
+ bool _followRedirects = true;
+
+ set followRedirects(bool value) {
+ _checkFinalized();
+ _followRedirects = value;
+ }
+
+ /// The maximum number of redirects to follow when [followRedirects] is true.
+ /// If this number is exceeded the [BaseResponse] future will signal a
+ /// [RedirectException]. Defaults to 5.
+ int get maxRedirects => _maxRedirects;
+ int _maxRedirects = 5;
+
+ set maxRedirects(int value) {
+ _checkFinalized();
+ _maxRedirects = value;
+ }
+
+ // TODO(nweiz): automatically parse cookies from headers
+
+ // TODO(nweiz): make this a HttpHeaders object
+ /// The headers for this request.
+ final Map<String, String> headers;
+
+ /// Whether the request has been finalized.
+ bool get finalized => _finalized;
+ bool _finalized = false;
+
+ /// Creates a new HTTP request.
+ BaseRequest(this.method, this.url)
+ : headers = new LinkedHashMap(
+ equals: (key1, key2) => key1.toLowerCase() == key2.toLowerCase(),
+ hashCode: (key) => key.toLowerCase().hashCode);
+
+ /// Finalizes the HTTP request in preparation for it being sent. This freezes
+ /// all mutable fields and returns a single-subscription [ByteStream] that
+ /// emits the body of the request.
+ ///
+ /// The base implementation of this returns null rather than a [ByteStream];
+ /// subclasses are responsible for creating the return value, which should be
+ /// single-subscription to ensure that no data is dropped. They should also
+ /// freeze any additional mutable fields they add that don't make sense to
+ /// change after the request headers are sent.
+ ByteStream finalize() {
+ // TODO(nweiz): freeze headers
+ if (finalized) throw new StateError("Can't finalize a finalized Request.");
+ _finalized = true;
+ return null;
+ }
+
+ /// Sends this request.
+ ///
+ /// This automatically initializes a new [Client] 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 [Client] for all of those
+ /// requests.
+ Future<StreamedResponse> send() async {
+ var client = new Client();
+
+ try {
+ var response = await client.send(this);
+ var stream = onDone(response.stream, client.close);
+ return new StreamedResponse(
+ new ByteStream(stream),
+ response.statusCode,
+ contentLength: response.contentLength,
+ request: response.request,
+ headers: response.headers,
+ isRedirect: response.isRedirect,
+ persistentConnection: response.persistentConnection,
+ reasonPhrase: response.reasonPhrase);
+ } catch (_) {
+ client.close();
+ rethrow;
+ }
+ }
+
+ // Throws an error if this request has been finalized.
+ void _checkFinalized() {
+ if (!finalized) return;
+ throw new StateError("Can't modify a finalized Request.");
+ }
+
+ String toString() => "$method $url";
+}
diff --git a/pub/http/lib/src/base_response.dart b/pub/http/lib/src/base_response.dart
new file mode 100644
index 0000000..26427f8
--- /dev/null
+++ b/pub/http/lib/src/base_response.dart
@@ -0,0 +1,53 @@
+// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'base_request.dart';
+
+/// The base class for HTTP responses.
+///
+/// Subclasses of [BaseResponse] are usually not constructed manually; instead,
+/// they're returned by [BaseClient.send] or other HTTP client methods.
+abstract class BaseResponse {
+ /// The (frozen) request that triggered this response.
+ final BaseRequest request;
+
+ /// The status code of the response.
+ final int statusCode;
+
+ /// The reason phrase associated with the status code.
+ final String reasonPhrase;
+
+ /// The size of the response body, in bytes.
+ ///
+ /// If the size of the request is not known in advance, this is `null`.
+ final int contentLength;
+
+ // TODO(nweiz): automatically parse cookies from headers
+
+ // TODO(nweiz): make this a HttpHeaders object.
+ /// The headers for this response.
+ final Map<String, String> headers;
+
+ /// Whether this response is a redirect.
+ final bool isRedirect;
+
+ /// Whether the server requested that a persistent connection be maintained.
+ final bool persistentConnection;
+
+ /// Creates a new HTTP response.
+ BaseResponse(
+ this.statusCode,
+ {this.contentLength,
+ this.request,
+ this.headers: const {},
+ this.isRedirect: false,
+ this.persistentConnection: true,
+ this.reasonPhrase}) {
+ if (statusCode < 100) {
+ throw new ArgumentError("Invalid status code $statusCode.");
+ } else if (contentLength != null && contentLength < 0) {
+ throw new ArgumentError("Invalid content length $contentLength.");
+ }
+ }
+}
diff --git a/pub/http/lib/src/byte_stream.dart b/pub/http/lib/src/byte_stream.dart
new file mode 100644
index 0000000..a9d47b0
--- /dev/null
+++ b/pub/http/lib/src/byte_stream.dart
@@ -0,0 +1,36 @@
+// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
+// for details. 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 'dart:convert';
+import 'dart:typed_data';
+
+/// A stream of chunks of bytes representing a single piece of data.
+class ByteStream extends StreamView<List<int>> {
+ ByteStream(Stream<List<int>> stream)
+ : super(stream);
+
+ /// Returns a single-subscription byte stream that will emit the given bytes
+ /// in a single chunk.
+ factory ByteStream.fromBytes(List<int> bytes) =>
+ new ByteStream(new Stream.fromIterable([bytes]));
+
+ /// Collects the data of this stream in a [Uint8List].
+ Future<Uint8List> toBytes() {
+ var completer = new Completer<Uint8List>();
+ var sink = new ByteConversionSink.withCallback((bytes) =>
+ completer.complete(new Uint8List.fromList(bytes)));
+ listen(sink.add, onError: completer.completeError, onDone: sink.close,
+ cancelOnError: true);
+ return completer.future;
+ }
+
+ /// Collect the data of this stream in a [String], decoded according to
+ /// [encoding], which defaults to `UTF8`.
+ Future<String> bytesToString([Encoding encoding=UTF8]) =>
+ encoding.decodeStream(this);
+
+ Stream<String> toStringStream([Encoding encoding=UTF8]) =>
+ encoding.decoder.bind(this);
+}
diff --git a/pub/http/lib/src/client.dart b/pub/http/lib/src/client.dart
new file mode 100644
index 0000000..cf1ff78
--- /dev/null
+++ b/pub/http/lib/src/client.dart
@@ -0,0 +1,142 @@
+// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
+// for details. 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 'dart:convert';
+import 'dart:typed_data';
+
+import 'base_client.dart';
+import 'base_request.dart';
+import 'io_client.dart';
+import 'response.dart';
+import 'streamed_response.dart';
+
+/// The interface for HTTP clients that take care of maintaining persistent
+/// connections across multiple requests to the same server. If you only need to
+/// send a single request, it's usually easier to use [head], [get], [post],
+/// [put], [patch], or [delete] instead.
+///
+/// When creating an HTTP client class with additional functionality, you must
+/// extend [BaseClient] rather than [Client]. In most cases, you can wrap
+/// another instance of [Client] and add functionality on top of that. This
+/// allows all classes implementing [Client] to be mutually composable.
+abstract class Client {
+ /// Creates a new client.
+ ///
+ /// Currently this will create an [IOClient] if `dart:io` is available and
+ /// throw an [UnsupportedError] otherwise. In the future, it will create a
+ /// [BrowserClient] if `dart:html` is available.
+ factory Client() => new IOClient();
+
+ /// Sends an HTTP HEAD request with the given headers to the given URL, which
+ /// can be a [Uri] or a [String].
+ ///
+ /// For more fine-grained control over the request, use [send] instead.
+ Future<Response> head(url, {Map<String, String> headers});
+
+ /// Sends an HTTP GET request with the given headers to the given URL, which
+ /// can be a [Uri] or a [String].
+ ///
+ /// For more fine-grained control over the request, use [send] instead.
+ Future<Response> get(url, {Map<String, String> headers});
+
+ /// Sends an HTTP POST request with the given headers and body to the given
+ /// URL, which can be a [Uri] or a [String].
+ ///
+ /// [body] sets the body of the request. It can be a [String], a [List<int>]
+ /// or a [Map<String, String>]. If it's a String, it's encoded using
+ /// [encoding] and used as the body of the request. The content-type of the
+ /// request will default to "text/plain".
+ ///
+ /// If [body] is a List, it's used as a list of bytes for the body of the
+ /// request.
+ ///
+ /// If [body] is a Map, it's encoded as form fields using [encoding]. The
+ /// content-type of the request will be set to
+ /// `"application/x-www-form-urlencoded"`; this cannot be overridden.
+ ///
+ /// [encoding] defaults to [UTF8].
+ ///
+ /// For more fine-grained control over the request, use [send] instead.
+ Future<Response> post(url, {Map<String, String> headers, body,
+ Encoding encoding});
+
+ /// Sends an HTTP PUT request with the given headers and body to the given
+ /// URL, which can be a [Uri] or a [String].
+ ///
+ /// [body] sets the body of the request. It can be a [String], a [List<int>]
+ /// or a [Map<String, String>]. If it's a String, it's encoded using
+ /// [encoding] and used as the body of the request. The content-type of the
+ /// request will default to "text/plain".
+ ///
+ /// If [body] is a List, it's used as a list of bytes for the body of the
+ /// request.
+ ///
+ /// If [body] is a Map, it's encoded as form fields using [encoding]. The
+ /// content-type of the request will be set to
+ /// `"application/x-www-form-urlencoded"`; this cannot be overridden.
+ ///
+ /// [encoding] defaults to [UTF8].
+ ///
+ /// For more fine-grained control over the request, use [send] instead.
+ Future<Response> put(url, {Map<String, String> headers, body,
+ Encoding encoding});
+
+ /// Sends an HTTP PATCH request with the given headers and body to the given
+ /// URL, which can be a [Uri] or a [String].
+ ///
+ /// [body] sets the body of the request. It can be a [String], a [List<int>]
+ /// or a [Map<String, String>]. If it's a String, it's encoded using
+ /// [encoding] and used as the body of the request. The content-type of the
+ /// request will default to "text/plain".
+ ///
+ /// If [body] is a List, it's used as a list of bytes for the body of the
+ /// request.
+ ///
+ /// If [body] is a Map, it's encoded as form fields using [encoding]. The
+ /// content-type of the request will be set to
+ /// `"application/x-www-form-urlencoded"`; this cannot be overridden.
+ ///
+ /// [encoding] defaults to [UTF8].
+ ///
+ /// For more fine-grained control over the request, use [send] instead.
+ Future<Response> patch(url, {Map<String, String> headers, body,
+ Encoding encoding});
+
+ /// Sends an HTTP DELETE request with the given headers to the given URL,
+ /// which can be a [Uri] or a [String].
+ ///
+ /// For more fine-grained control over the request, use [send] instead.
+ Future<Response> delete(url, {Map<String, String> 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 the
+ /// body of the response as a String.
+ ///
+ /// The Future will emit a [ClientException] if the response doesn't have a
+ /// success status code.
+ ///
+ /// For more fine-grained control over the request and response, use [send] or
+ /// [get] instead.
+ Future<String> read(url, {Map<String, String> 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 the
+ /// body of the response as a list of bytes.
+ ///
+ /// The Future will emit a [ClientException] if the response doesn't have a
+ /// success status code.
+ ///
+ /// For more fine-grained control over the request and response, use [send] or
+ /// [get] instead.
+ Future<Uint8List> readBytes(url, {Map<String, String> headers});
+
+ /// Sends an HTTP request and asynchronously returns the response.
+ Future<StreamedResponse> send(BaseRequest request);
+
+ /// Closes the client and cleans up any resources associated with it. It's
+ /// important to close each client when it's done being used; failing to do so
+ /// can cause the Dart process to hang.
+ void close();
+}
diff --git a/pub/http/lib/src/exception.dart b/pub/http/lib/src/exception.dart
new file mode 100644
index 0000000..db2c224
--- /dev/null
+++ b/pub/http/lib/src/exception.dart
@@ -0,0 +1,15 @@
+// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/// An exception caused by an error in a pkg/http client.
+class ClientException implements Exception {
+ final String message;
+
+ /// The URL of the HTTP request or response that failed.
+ final Uri uri;
+
+ ClientException(this.message, [this.uri]);
+
+ String toString() => message;
+}
diff --git a/pub/http/lib/src/io_client.dart b/pub/http/lib/src/io_client.dart
new file mode 100644
index 0000000..8a7fe6d
--- /dev/null
+++ b/pub/http/lib/src/io_client.dart
@@ -0,0 +1,74 @@
+// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
+// for details. 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 'dart:io';
+
+import 'package:async/async.dart';
+
+import 'base_client.dart';
+import 'base_request.dart';
+import 'exception.dart';
+import 'streamed_response.dart';
+
+/// A `dart:io`-based HTTP client.
+///
+/// This is the default client when running on the command line.
+class IOClient extends BaseClient {
+ /// The underlying `dart:io` HTTP client.
+ HttpClient _inner;
+
+ /// Creates a new HTTP client.
+ IOClient([HttpClient inner]) : _inner = inner ?? new HttpClient();
+
+ /// Sends an HTTP request and asynchronously returns the response.
+ Future<StreamedResponse> send(BaseRequest request) async {
+ var stream = request.finalize();
+
+ try {
+ var ioRequest = await _inner.openUrl(request.method, request.url);
+
+ ioRequest
+ ..followRedirects = request.followRedirects
+ ..maxRedirects = request.maxRedirects
+ ..contentLength = request.contentLength == null
+ ? -1
+ : request.contentLength
+ ..persistentConnection = request.persistentConnection;
+ request.headers.forEach((name, value) {
+ ioRequest.headers.set(name, value);
+ });
+
+ var response = await stream.pipe(
+ DelegatingStreamConsumer.typed(ioRequest));
+ var headers = <String, String>{};
+ response.headers.forEach((key, values) {
+ headers[key] = values.join(',');
+ });
+
+ return new StreamedResponse(
+ DelegatingStream.typed<List<int>>(response).handleError((error) =>
+ throw new ClientException(error.message, error.uri),
+ test: (error) => error is HttpException),
+ response.statusCode,
+ contentLength: response.contentLength == -1
+ ? null
+ : response.contentLength,
+ request: request,
+ headers: headers,
+ isRedirect: response.isRedirect,
+ persistentConnection: response.persistentConnection,
+ reasonPhrase: response.reasonPhrase);
+ } on HttpException catch (error) {
+ throw new ClientException(error.message, error.uri);
+ }
+ }
+
+ /// Closes the client. This terminates all active connections. If a client
+ /// remains unclosed, the Dart process may not terminate.
+ void close() {
+ if (_inner != null) _inner.close(force: true);
+ _inner = null;
+ }
+}
diff --git a/pub/http/lib/src/mock_client.dart b/pub/http/lib/src/mock_client.dart
new file mode 100644
index 0000000..acda5fd
--- /dev/null
+++ b/pub/http/lib/src/mock_client.dart
@@ -0,0 +1,86 @@
+// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
+// for details. 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 'base_client.dart';
+import 'base_request.dart';
+import 'byte_stream.dart';
+import 'request.dart';
+import 'response.dart';
+import 'streamed_response.dart';
+
+// TODO(nweiz): once Dart has some sort of Rack- or WSGI-like standard for
+// server APIs, MockClient should conform to it.
+
+/// A mock HTTP client designed for use when testing code that uses
+/// [BaseClient]. This client allows you to define a handler callback for all
+/// requests that are made through it so that you can mock a server without
+/// having to send real HTTP requests.
+class MockClient extends BaseClient {
+ /// The handler for receiving [StreamedRequest]s and sending
+ /// [StreamedResponse]s.
+ final MockClientStreamHandler _handler;
+
+ MockClient._(this._handler);
+
+ /// Creates a [MockClient] with a handler that receives [Request]s and sends
+ /// [Response]s.
+ MockClient(MockClientHandler fn)
+ : this._((baseRequest, bodyStream) {
+ return bodyStream.toBytes().then((bodyBytes) {
+ var request = new Request(baseRequest.method, baseRequest.url)
+ ..persistentConnection = baseRequest.persistentConnection
+ ..followRedirects = baseRequest.followRedirects
+ ..maxRedirects = baseRequest.maxRedirects
+ ..headers.addAll(baseRequest.headers)
+ ..bodyBytes = bodyBytes
+ ..finalize();
+
+ return fn(request);
+ }).then((response) {
+ return new StreamedResponse(
+ new ByteStream.fromBytes(response.bodyBytes),
+ response.statusCode,
+ contentLength: response.contentLength,
+ request: baseRequest,
+ headers: response.headers,
+ isRedirect: response.isRedirect,
+ persistentConnection: response.persistentConnection,
+ reasonPhrase: response.reasonPhrase);
+ });
+ });
+
+ /// Creates a [MockClient] with a handler that receives [StreamedRequest]s and
+ /// sends [StreamedResponse]s.
+ MockClient.streaming(MockClientStreamHandler fn)
+ : this._((request, bodyStream) {
+ return fn(request, bodyStream).then((response) {
+ return new StreamedResponse(
+ response.stream,
+ response.statusCode,
+ contentLength: response.contentLength,
+ request: request,
+ headers: response.headers,
+ isRedirect: response.isRedirect,
+ persistentConnection: response.persistentConnection,
+ reasonPhrase: response.reasonPhrase);
+ });
+ });
+
+ /// Sends a request.
+ Future<StreamedResponse> send(BaseRequest request) async {
+ var bodyStream = request.finalize();
+ return await _handler(request, bodyStream);
+ }
+}
+
+/// A handler function that receives [StreamedRequest]s and sends
+/// [StreamedResponse]s. Note that [request] will be finalized.
+typedef Future<StreamedResponse> MockClientStreamHandler(
+ BaseRequest request, ByteStream bodyStream);
+
+/// A handler function that receives [Request]s and sends [Response]s. Note that
+/// [request] will be finalized.
+typedef Future<Response> MockClientHandler(Request request);
diff --git a/pub/http/lib/src/multipart_file.dart b/pub/http/lib/src/multipart_file.dart
new file mode 100644
index 0000000..da4bfac
--- /dev/null
+++ b/pub/http/lib/src/multipart_file.dart
@@ -0,0 +1,111 @@
+// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
+// for details. 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 'dart:convert';
+import 'dart:io';
+
+import 'package:async/async.dart';
+import 'package:http_parser/http_parser.dart';
+import 'package:path/path.dart' as path;
+
+import 'byte_stream.dart';
+import 'utils.dart';
+
+/// A file to be uploaded as part of a [MultipartRequest]. This doesn't need to
+/// correspond to a physical file.
+class MultipartFile {
+ /// The name of the form field for the file.
+ final String field;
+
+ /// The size of the file in bytes. This must be known in advance, even if this
+ /// file is created from a [ByteStream].
+ final int length;
+
+ /// The basename of the file. May be null.
+ final String filename;
+
+ /// The content-type of the file. Defaults to `application/octet-stream`.
+ final MediaType contentType;
+
+ /// The stream that will emit the file's contents.
+ final ByteStream _stream;
+
+ /// Whether [finalize] has been called.
+ bool get isFinalized => _isFinalized;
+ bool _isFinalized = false;
+
+ /// Creates a new [MultipartFile] from a chunked [Stream] of bytes. The length
+ /// of the file in bytes must be known in advance. If it's not, read the data
+ /// from the stream and use [MultipartFile.fromBytes] instead.
+ ///
+ /// [contentType] currently defaults to `application/octet-stream`, but in the
+ /// future may be inferred from [filename].
+ MultipartFile(this.field, Stream<List<int>> stream, this.length,
+ {this.filename, MediaType contentType})
+ : this._stream = toByteStream(stream),
+ this.contentType = contentType != null ? contentType :
+ new MediaType("application", "octet-stream");
+
+ /// Creates a new [MultipartFile] from a byte array.
+ ///
+ /// [contentType] currently defaults to `application/octet-stream`, but in the
+ /// future may be inferred from [filename].
+ factory MultipartFile.fromBytes(String field, List<int> value,
+ {String filename, MediaType contentType}) {
+ var stream = new ByteStream.fromBytes(value);
+ return new MultipartFile(field, stream, value.length,
+ filename: filename,
+ contentType: contentType);
+ }
+
+ /// Creates a new [MultipartFile] from a string.
+ ///
+ /// The encoding to use when translating [value] into bytes is taken from
+ /// [contentType] if it has a charset set. Otherwise, it defaults to UTF-8.
+ /// [contentType] currently defaults to `text/plain; charset=utf-8`, but in
+ /// the future may be inferred from [filename].
+ factory MultipartFile.fromString(String field, String value,
+ {String filename, MediaType contentType}) {
+ contentType = contentType == null ? new MediaType("text", "plain")
+ : contentType;
+ var encoding = encodingForCharset(contentType.parameters['charset'], UTF8);
+ contentType = contentType.change(parameters: {'charset': encoding.name});
+
+ return new MultipartFile.fromBytes(field, encoding.encode(value),
+ filename: filename,
+ contentType: contentType);
+ }
+
+ // TODO(nweiz): Infer the content-type from the filename.
+ /// Creates a new [MultipartFile] from a path to a file on disk.
+ ///
+ /// [filename] defaults to the basename of [filePath]. [contentType] currently
+ /// defaults to `application/octet-stream`, but in the future may be inferred
+ /// from [filename].
+ ///
+ /// Throws an [UnsupportedError] if `dart:io` isn't supported in this
+ /// environment.
+ static Future<MultipartFile> fromPath(String field, String filePath,
+ {String filename, MediaType contentType}) async {
+ if (filename == null) filename = path.basename(filePath);
+ var file = new File(filePath);
+ var length = await file.length();
+ var stream = new ByteStream(DelegatingStream.typed(file.openRead()));
+ return new MultipartFile(field, stream, length,
+ filename: filename,
+ contentType: contentType);
+ }
+
+ // Finalizes the file in preparation for it being sent as part of a
+ // [MultipartRequest]. This returns a [ByteStream] that should emit the body
+ // of the file. The stream may be closed to indicate an empty file.
+ ByteStream finalize() {
+ if (isFinalized) {
+ throw new StateError("Can't finalize a finalized MultipartFile.");
+ }
+ _isFinalized = true;
+ return _stream;
+ }
+}
diff --git a/pub/http/lib/src/multipart_request.dart b/pub/http/lib/src/multipart_request.dart
new file mode 100644
index 0000000..7250def
--- /dev/null
+++ b/pub/http/lib/src/multipart_request.dart
@@ -0,0 +1,179 @@
+// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
+// for details. 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 'dart:convert';
+import 'dart:math';
+
+import 'base_request.dart';
+import 'byte_stream.dart';
+import 'multipart_file.dart';
+import 'utils.dart';
+
+final _newlineRegExp = new RegExp(r"\r\n|\r|\n");
+
+/// A `multipart/form-data` request. Such a request has both string [fields],
+/// which function as normal form fields, and (potentially streamed) binary
+/// [files].
+///
+/// This request automatically sets the Content-Type header to
+/// `multipart/form-data`. This value will override any value set by the user.
+///
+/// var uri = Uri.parse("http://pub.dartlang.org/packages/create");
+/// var request = new http.MultipartRequest("POST", url);
+/// request.fields['user'] = 'nweiz@google.com';
+/// request.files.add(new http.MultipartFile.fromFile(
+/// 'package',
+/// new File('build/package.tar.gz'),
+/// contentType: new MediaType('application', 'x-tar'));
+/// request.send().then((response) {
+/// if (response.statusCode == 200) print("Uploaded!");
+/// });
+class MultipartRequest extends BaseRequest {
+ /// The total length of the multipart boundaries used when building the
+ /// request body. According to http://tools.ietf.org/html/rfc1341.html, this
+ /// can't be longer than 70.
+ static const int _BOUNDARY_LENGTH = 70;
+
+ static final Random _random = new Random();
+
+ /// The form fields to send for this request.
+ final Map<String, String> fields;
+
+ /// The private version of [files].
+ final List<MultipartFile> _files;
+
+ /// Creates a new [MultipartRequest].
+ MultipartRequest(String method, Uri url)
+ : fields = {},
+ _files = <MultipartFile>[],
+ super(method, url);
+
+ /// The list of files to upload for this request.
+ List<MultipartFile> get files => _files;
+
+ /// The total length of the request body, in bytes. This is calculated from
+ /// [fields] and [files] and cannot be set manually.
+ int get contentLength {
+ var length = 0;
+
+ fields.forEach((name, value) {
+ length += "--".length + _BOUNDARY_LENGTH + "\r\n".length +
+ UTF8.encode(_headerForField(name, value)).length +
+ UTF8.encode(value).length + "\r\n".length;
+ });
+
+ for (var file in _files) {
+ length += "--".length + _BOUNDARY_LENGTH + "\r\n".length +
+ UTF8.encode(_headerForFile(file)).length +
+ file.length + "\r\n".length;
+ }
+
+ return length + "--".length + _BOUNDARY_LENGTH + "--\r\n".length;
+ }
+
+ void set contentLength(int value) {
+ throw new UnsupportedError("Cannot set the contentLength property of "
+ "multipart requests.");
+ }
+
+ /// Freezes all mutable fields and returns a single-subscription [ByteStream]
+ /// that will emit the request body.
+ ByteStream finalize() {
+ // TODO(nweiz): freeze fields and files
+ var boundary = _boundaryString();
+ headers['content-type'] = 'multipart/form-data; boundary=$boundary';
+ super.finalize();
+
+ var controller = new StreamController<List<int>>(sync: true);
+
+ void writeAscii(String string) {
+ controller.add(UTF8.encode(string));
+ }
+
+ writeUtf8(String string) => controller.add(UTF8.encode(string));
+ writeLine() => controller.add([13, 10]); // \r\n
+
+ fields.forEach((name, value) {
+ writeAscii('--$boundary\r\n');
+ writeAscii(_headerForField(name, value));
+ writeUtf8(value);
+ writeLine();
+ });
+
+ Future.forEach(_files, (file) {
+ writeAscii('--$boundary\r\n');
+ writeAscii(_headerForFile(file));
+ return writeStreamToSink(file.finalize(), controller)
+ .then((_) => writeLine());
+ }).then((_) {
+ // TODO(nweiz): pass any errors propagated through this future on to
+ // the stream. See issue 3657.
+ writeAscii('--$boundary--\r\n');
+ controller.close();
+ });
+
+ return new ByteStream(controller.stream);
+ }
+
+ /// All character codes that are valid in multipart boundaries. This is the
+ /// intersection of the characters allowed in the `bcharsnospace` production
+ /// defined in [RFC 2046][] and those allowed in the `token` production
+ /// defined in [RFC 1521][].
+ ///
+ /// [RFC 2046]: http://tools.ietf.org/html/rfc2046#section-5.1.1.
+ /// [RFC 1521]: https://tools.ietf.org/html/rfc1521#section-4
+ static const List<int> _BOUNDARY_CHARACTERS = const <int>[
+ 39, 43, 95, 44, 45, 46, 58, 61, 63, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57,
+ 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83,
+ 84, 85, 86, 87, 88, 89, 90, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106,
+ 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121,
+ 122
+ ];
+
+ /// Returns the header string for a field. The return value is guaranteed to
+ /// contain only ASCII characters.
+ String _headerForField(String name, String value) {
+ var header =
+ 'content-disposition: form-data; name="${_browserEncode(name)}"';
+ if (!isPlainAscii(value)) {
+ header = '$header\r\n'
+ 'content-type: text/plain; charset=utf-8\r\n'
+ 'content-transfer-encoding: binary';
+ }
+ return '$header\r\n\r\n';
+ }
+
+ /// Returns the header string for a file. The return value is guaranteed to
+ /// contain only ASCII characters.
+ String _headerForFile(MultipartFile file) {
+ var header = 'content-type: ${file.contentType}\r\n'
+ 'content-disposition: form-data; name="${_browserEncode(file.field)}"';
+
+ if (file.filename != null) {
+ header = '$header; filename="${_browserEncode(file.filename)}"';
+ }
+ return '$header\r\n\r\n';
+ }
+
+ /// Encode [value] in the same way browsers do.
+ String _browserEncode(String value) {
+ // http://tools.ietf.org/html/rfc2388 mandates some complex encodings for
+ // field names and file names, but in practice user agents seem not to
+ // follow this at all. Instead, they URL-encode `\r`, `\n`, and `\r\n` as
+ // `\r\n`; URL-encode `"`; and do nothing else (even for `%` or non-ASCII
+ // characters). We follow their behavior.
+ return value.replaceAll(_newlineRegExp, "%0D%0A").replaceAll('"', "%22");
+ }
+
+ /// Returns a randomly-generated multipart boundary string
+ String _boundaryString() {
+ var prefix = "dart-http-boundary-";
+ var list = new List<int>.generate(_BOUNDARY_LENGTH - prefix.length,
+ (index) =>
+ _BOUNDARY_CHARACTERS[_random.nextInt(_BOUNDARY_CHARACTERS.length)],
+ growable: false);
+ return "$prefix${new String.fromCharCodes(list)}";
+ }
+}
diff --git a/pub/http/lib/src/request.dart b/pub/http/lib/src/request.dart
new file mode 100644
index 0000000..67b664c
--- /dev/null
+++ b/pub/http/lib/src/request.dart
@@ -0,0 +1,161 @@
+// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
+// for details. 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:convert';
+import 'dart:typed_data';
+
+import 'package:http_parser/http_parser.dart';
+
+import 'base_request.dart';
+import 'byte_stream.dart';
+import 'utils.dart';
+
+/// An HTTP request where the entire request body is known in advance.
+class Request extends BaseRequest {
+ /// The size of the request body, in bytes. This is calculated from
+ /// [bodyBytes].
+ ///
+ /// The content length cannot be set for [Request], since it's automatically
+ /// calculated from [bodyBytes].
+ int get contentLength => bodyBytes.length;
+
+ set contentLength(int value) {
+ throw new UnsupportedError("Cannot set the contentLength property of "
+ "non-streaming Request objects.");
+ }
+
+ /// The default encoding to use when converting between [bodyBytes] and
+ /// [body]. This is only used if [encoding] hasn't been manually set and if
+ /// the content-type header has no encoding information.
+ Encoding _defaultEncoding;
+
+ /// The encoding used for the request. This encoding is used when converting
+ /// between [bodyBytes] and [body].
+ ///
+ /// If the request has a `Content-Type` header and that header has a `charset`
+ /// parameter, that parameter's value is used as the encoding. Otherwise, if
+ /// [encoding] has been set manually, that encoding is used. If that hasn't
+ /// been set either, this defaults to [UTF8].
+ ///
+ /// If the `charset` parameter's value is not a known [Encoding], reading this
+ /// will throw a [FormatException].
+ ///
+ /// If the request has a `Content-Type` header, setting this will set the
+ /// charset parameter on that header.
+ Encoding get encoding {
+ if (_contentType == null ||
+ !_contentType.parameters.containsKey('charset')) {
+ return _defaultEncoding;
+ }
+ return requiredEncodingForCharset(_contentType.parameters['charset']);
+ }
+
+ set encoding(Encoding value) {
+ _checkFinalized();
+ _defaultEncoding = value;
+ var contentType = _contentType;
+ if (contentType == null) return;
+ _contentType = contentType.change(parameters: {'charset': value.name});
+ }
+
+ // TODO(nweiz): make this return a read-only view
+ /// The bytes comprising the body of the request. This is converted to and
+ /// from [body] using [encoding].
+ ///
+ /// This list should only be set, not be modified in place.
+ Uint8List get bodyBytes => _bodyBytes;
+ Uint8List _bodyBytes;
+
+ set bodyBytes(List<int> value) {
+ _checkFinalized();
+ _bodyBytes = toUint8List(value);
+ }
+
+ /// The body of the request as a string. This is converted to and from
+ /// [bodyBytes] using [encoding].
+ ///
+ /// When this is set, if the request does not yet have a `Content-Type`
+ /// header, one will be added with the type `text/plain`. Then the `charset`
+ /// parameter of the `Content-Type` header (whether new or pre-existing) will
+ /// be set to [encoding] if it wasn't already set.
+ String get body => encoding.decode(bodyBytes);
+
+ set body(String value) {
+ bodyBytes = encoding.encode(value);
+ var contentType = _contentType;
+ if (contentType == null) {
+ _contentType = new MediaType("text", "plain", {'charset': encoding.name});
+ } else if (!contentType.parameters.containsKey('charset')) {
+ _contentType = contentType.change(parameters: {'charset': encoding.name});
+ }
+ }
+
+ /// The form-encoded fields in the body of the request as a map from field
+ /// names to values. The form-encoded body is converted to and from
+ /// [bodyBytes] using [encoding] (in the same way as [body]).
+ ///
+ /// If the request doesn't have a `Content-Type` header of
+ /// `application/x-www-form-urlencoded`, reading this will throw a
+ /// [StateError].
+ ///
+ /// If the request has a `Content-Type` header with a type other than
+ /// `application/x-www-form-urlencoded`, setting this will throw a
+ /// [StateError]. Otherwise, the content type will be set to
+ /// `application/x-www-form-urlencoded`.
+ ///
+ /// This map should only be set, not modified in place.
+ Map<String, String> get bodyFields {
+ var contentType = _contentType;
+ if (contentType == null ||
+ contentType.mimeType != "application/x-www-form-urlencoded") {
+ throw new StateError('Cannot access the body fields of a Request without '
+ 'content-type "application/x-www-form-urlencoded".');
+ }
+
+ return Uri.splitQueryString(body, encoding: encoding);
+ }
+
+ set bodyFields(Map<String, String> fields) {
+ var contentType = _contentType;
+ if (contentType == null) {
+ _contentType = new MediaType("application", "x-www-form-urlencoded");
+ } else if (contentType.mimeType != "application/x-www-form-urlencoded") {
+ throw new StateError('Cannot set the body fields of a Request with '
+ 'content-type "${contentType.mimeType}".');
+ }
+
+ this.body = mapToQuery(fields, encoding: encoding);
+ }
+
+ /// Creates a new HTTP request.
+ Request(String method, Uri url)
+ : _defaultEncoding = UTF8,
+ _bodyBytes = new Uint8List(0),
+ super(method, url);
+
+ /// Freezes all mutable fields and returns a single-subscription [ByteStream]
+ /// containing the request body.
+ ByteStream finalize() {
+ super.finalize();
+ return new ByteStream.fromBytes(bodyBytes);
+ }
+
+ /// The `Content-Type` header of the request (if it exists) as a
+ /// [MediaType].
+ MediaType get _contentType {
+ var contentType = headers['content-type'];
+ if (contentType == null) return null;
+ return new MediaType.parse(contentType);
+ }
+
+ set _contentType(MediaType value) {
+ headers['content-type'] = value.toString();
+ }
+
+ /// Throw an error if this request has been finalized.
+ void _checkFinalized() {
+ if (!finalized) return;
+ throw new StateError("Can't modify a finalized Request.");
+ }
+}
diff --git a/pub/http/lib/src/response.dart b/pub/http/lib/src/response.dart
new file mode 100644
index 0000000..9fa06ee
--- /dev/null
+++ b/pub/http/lib/src/response.dart
@@ -0,0 +1,95 @@
+// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
+// for details. 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 'dart:convert';
+import 'dart:typed_data';
+
+import 'package:http_parser/http_parser.dart';
+
+import 'base_request.dart';
+import 'base_response.dart';
+import 'streamed_response.dart';
+import 'utils.dart';
+
+/// An HTTP response where the entire response body is known in advance.
+class Response extends BaseResponse {
+ /// The bytes comprising the body of this response.
+ final Uint8List bodyBytes;
+
+ /// The body of the response as a string. This is converted from [bodyBytes]
+ /// using the `charset` parameter of the `Content-Type` header field, if
+ /// available. If it's unavailable or if the encoding name is unknown,
+ /// [LATIN1] is used by default, as per [RFC 2616][].
+ ///
+ /// [RFC 2616]: http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html
+ String get body => _encodingForHeaders(headers).decode(bodyBytes);
+
+ /// Creates a new HTTP response with a string body.
+ Response(
+ String body,
+ int statusCode,
+ {BaseRequest request,
+ Map<String, String> headers: const {},
+ bool isRedirect: false,
+ bool persistentConnection: true,
+ String reasonPhrase})
+ : this.bytes(
+ _encodingForHeaders(headers).encode(body),
+ statusCode,
+ request: request,
+ headers: headers,
+ isRedirect: isRedirect,
+ persistentConnection: persistentConnection,
+ reasonPhrase: reasonPhrase);
+
+ /// Create a new HTTP response with a byte array body.
+ Response.bytes(
+ List<int> bodyBytes,
+ int statusCode,
+ {BaseRequest request,
+ Map<String, String> headers: const {},
+ bool isRedirect: false,
+ bool persistentConnection: true,
+ String reasonPhrase})
+ : bodyBytes = toUint8List(bodyBytes),
+ super(
+ statusCode,
+ contentLength: bodyBytes.length,
+ request: request,
+ headers: headers,
+ isRedirect: isRedirect,
+ persistentConnection: persistentConnection,
+ reasonPhrase: reasonPhrase);
+
+ /// Creates a new HTTP response by waiting for the full body to become
+ /// available from a [StreamedResponse].
+ static Future<Response> fromStream(StreamedResponse response) {
+ return response.stream.toBytes().then((body) {
+ return new Response.bytes(
+ body,
+ response.statusCode,
+ request: response.request,
+ headers: response.headers,
+ isRedirect: response.isRedirect,
+ persistentConnection: response.persistentConnection,
+ reasonPhrase: response.reasonPhrase);
+ });
+ }
+}
+
+/// Returns the encoding to use for a response with the given headers. This
+/// defaults to [LATIN1] if the headers don't specify a charset or
+/// if that charset is unknown.
+Encoding _encodingForHeaders(Map<String, String> headers) =>
+ encodingForCharset(_contentTypeForHeaders(headers).parameters['charset']);
+
+/// Returns the [MediaType] object for the given headers's content-type.
+///
+/// Defaults to `application/octet-stream`.
+MediaType _contentTypeForHeaders(Map<String, String> headers) {
+ var contentType = headers['content-type'];
+ if (contentType != null) return new MediaType.parse(contentType);
+ return new MediaType("application", "octet-stream");
+}
diff --git a/pub/http/lib/src/streamed_request.dart b/pub/http/lib/src/streamed_request.dart
new file mode 100644
index 0000000..6a020bd
--- /dev/null
+++ b/pub/http/lib/src/streamed_request.dart
@@ -0,0 +1,41 @@
+// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
+// for details. 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 'byte_stream.dart';
+import 'base_request.dart';
+
+/// An HTTP request where the request body is sent asynchronously after the
+/// connection has been established and the headers have been sent.
+///
+/// When the request is sent via [BaseClient.send], only the headers and
+/// whatever data has already been written to [StreamedRequest.stream] will be
+/// sent immediately. More data will be sent as soon as it's written to
+/// [StreamedRequest.sink], and when the sink is closed the request will end.
+class StreamedRequest extends BaseRequest {
+ /// The sink to which to write data that will be sent as the request body.
+ /// This may be safely written to before the request is sent; the data will be
+ /// buffered.
+ ///
+ /// Closing this signals the end of the request.
+ EventSink<List<int>> get sink => _controller.sink;
+
+ /// The controller for [sink], from which [BaseRequest] will read data for
+ /// [finalize].
+ final StreamController<List<int>> _controller;
+
+ /// Creates a new streaming request.
+ StreamedRequest(String method, Uri url)
+ : _controller = new StreamController<List<int>>(sync: true),
+ super(method, url);
+
+ /// Freezes all mutable fields other than [stream] and returns a
+ /// single-subscription [ByteStream] that emits the data being written to
+ /// [sink].
+ ByteStream finalize() {
+ super.finalize();
+ return new ByteStream(_controller.stream);
+ }
+}
diff --git a/pub/http/lib/src/streamed_response.dart b/pub/http/lib/src/streamed_response.dart
new file mode 100644
index 0000000..69d8356
--- /dev/null
+++ b/pub/http/lib/src/streamed_response.dart
@@ -0,0 +1,39 @@
+// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
+// for details. 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 'byte_stream.dart';
+import 'base_response.dart';
+import 'base_request.dart';
+import 'utils.dart';
+
+/// An HTTP response where the response body is received asynchronously after
+/// the headers have been received.
+class StreamedResponse extends BaseResponse {
+ /// The stream from which the response body data can be read. This should
+ /// always be a single-subscription stream.
+ final ByteStream stream;
+
+ /// Creates a new streaming response. [stream] should be a single-subscription
+ /// stream.
+ StreamedResponse(
+ Stream<List<int>> stream,
+ int statusCode,
+ {int contentLength,
+ BaseRequest request,
+ Map<String, String> headers: const {},
+ bool isRedirect: false,
+ bool persistentConnection: true,
+ String reasonPhrase})
+ : this.stream = toByteStream(stream),
+ super(
+ statusCode,
+ contentLength: contentLength,
+ request: request,
+ headers: headers,
+ isRedirect: isRedirect,
+ persistentConnection: persistentConnection,
+ reasonPhrase: reasonPhrase);
+}
diff --git a/pub/http/lib/src/utils.dart b/pub/http/lib/src/utils.dart
new file mode 100644
index 0000000..789c2d9
--- /dev/null
+++ b/pub/http/lib/src/utils.dart
@@ -0,0 +1,141 @@
+// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
+// for details. 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 'dart:convert';
+import 'dart:typed_data';
+
+import 'byte_stream.dart';
+
+/// Converts a [Map] from parameter names to values to a URL query string.
+///
+/// mapToQuery({"foo": "bar", "baz": "bang"});
+/// //=> "foo=bar&baz=bang"
+String mapToQuery(Map<String, String> map, {Encoding encoding}) {
+ var pairs = <List<String>>[];
+ map.forEach((key, value) =>
+ pairs.add([Uri.encodeQueryComponent(key, encoding: encoding),
+ Uri.encodeQueryComponent(value, encoding: encoding)]));
+ return pairs.map((pair) => "${pair[0]}=${pair[1]}").join("&");
+}
+
+/// Like [String.split], but only splits on the first occurrence of the pattern.
+/// This will always return an array of two elements or fewer.
+///
+/// split1("foo,bar,baz", ","); //=> ["foo", "bar,baz"]
+/// split1("foo", ","); //=> ["foo"]
+/// split1("", ","); //=> []
+List<String> split1(String toSplit, String pattern) {
+ if (toSplit.isEmpty) return <String>[];
+
+ var index = toSplit.indexOf(pattern);
+ if (index == -1) return [toSplit];
+ return [
+ toSplit.substring(0, index),
+ toSplit.substring(index + pattern.length)
+ ];
+}
+
+/// Returns the [Encoding] that corresponds to [charset]. Returns [fallback] if
+/// [charset] is null or if no [Encoding] was found that corresponds to
+/// [charset].
+Encoding encodingForCharset(String charset, [Encoding fallback = LATIN1]) {
+ if (charset == null) return fallback;
+ var encoding = Encoding.getByName(charset);
+ return encoding == null ? fallback : encoding;
+}
+
+
+/// Returns the [Encoding] that corresponds to [charset]. Throws a
+/// [FormatException] if no [Encoding] was found that corresponds to [charset].
+/// [charset] may not be null.
+Encoding requiredEncodingForCharset(String charset) {
+ var encoding = Encoding.getByName(charset);
+ if (encoding != null) return encoding;
+ throw new FormatException('Unsupported encoding "$charset".');
+}
+
+/// A regular expression that matches strings that are composed entirely of
+/// ASCII-compatible characters.
+final RegExp _ASCII_ONLY = new RegExp(r"^[\x00-\x7F]+$");
+
+/// Returns whether [string] is composed entirely of ASCII-compatible
+/// characters.
+bool isPlainAscii(String string) => _ASCII_ONLY.hasMatch(string);
+
+/// Converts [input] into a [Uint8List].
+///
+/// If [input] is a [TypedData], this just returns a view on [input].
+Uint8List toUint8List(List<int> input) {
+ if (input is Uint8List) return input;
+ if (input is TypedData) {
+ // TODO(nweiz): remove "as" when issue 11080 is fixed.
+ return new Uint8List.view((input as TypedData).buffer);
+ }
+ return new Uint8List.fromList(input);
+}
+
+/// If [stream] is already a [ByteStream], returns it. Otherwise, wraps it in a
+/// [ByteStream].
+ByteStream toByteStream(Stream<List<int>> stream) {
+ if (stream is ByteStream) return stream;
+ return new ByteStream(stream);
+}
+
+/// Calls [onDone] once [stream] (a single-subscription [Stream]) is finished.
+/// The return value, also a single-subscription [Stream] should be used in
+/// place of [stream] after calling this method.
+Stream<T> onDone<T>(Stream<T> stream, void onDone()) =>
+ stream.transform(new StreamTransformer.fromHandlers(handleDone: (sink) {
+ sink.close();
+ onDone();
+ }));
+
+// TODO(nweiz): remove this when issue 7786 is fixed.
+/// Pipes all data and errors from [stream] into [sink]. When [stream] is done,
+/// [sink] is closed and the returned [Future] is completed.
+Future store(Stream stream, EventSink sink) {
+ var completer = new Completer();
+ stream.listen(sink.add,
+ onError: sink.addError,
+ onDone: () {
+ sink.close();
+ completer.complete();
+ });
+ return completer.future;
+}
+
+/// Pipes all data and errors from [stream] into [sink]. Completes [Future] once
+/// [stream] is done. Unlike [store], [sink] remains open after [stream] is
+/// done.
+Future writeStreamToSink(Stream stream, EventSink sink) {
+ var completer = new Completer();
+ stream.listen(sink.add,
+ onError: sink.addError,
+ onDone: () => completer.complete());
+ return completer.future;
+}
+
+/// A pair of values.
+class Pair<E, F> {
+ E first;
+ F last;
+
+ Pair(this.first, this.last);
+
+ String toString() => '($first, $last)';
+
+ bool operator==(other) {
+ if (other is! Pair) return false;
+ return other.first == first && other.last == last;
+ }
+
+ int get hashCode => first.hashCode ^ last.hashCode;
+}
+
+/// Configures [future] so that its result (success or exception) is passed on
+/// to [completer].
+void chainToCompleter(Future future, Completer completer) {
+ future.then(completer.complete, onError: completer.completeError);
+}
diff --git a/pub/http/lib/testing.dart b/pub/http/lib/testing.dart
new file mode 100644
index 0000000..d5a7874
--- /dev/null
+++ b/pub/http/lib/testing.dart
@@ -0,0 +1,25 @@
+// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/// This library contains testing classes for the HTTP library.
+///
+/// The [MockClient] class is a drop-in replacement for `http.Client` that
+/// allows test code to set up a local request handler in order to fake a server
+/// that responds to HTTP requests:
+///
+/// import 'dart:convert';
+/// import 'package:http/testing.dart';
+///
+/// var client = new MockClient((request) async {
+/// if (request.url.path != "/data.json") {
+/// return new Response("", 404);
+/// }
+/// return new Response(
+/// JSON.encode({
+/// 'numbers': [1, 4, 15, 19, 214]
+/// }),
+/// 200,
+/// headers: {'content-type': 'application/json'});
+/// });
+export 'src/mock_client.dart';
diff --git a/pub/http/pubspec.yaml b/pub/http/pubspec.yaml
new file mode 100644
index 0000000..b922a51
--- /dev/null
+++ b/pub/http/pubspec.yaml
@@ -0,0 +1,15 @@
+name: http
+version: 0.11.3+12
+author: "Dart Team <misc@dartlang.org>"
+homepage: https://github.com/dart-lang/http
+description: A composable, Future-based API for making HTTP requests.
+dependencies:
+ async: "^1.10.0"
+ collection: "^1.5.0"
+ http_parser: ">=0.0.1 <4.0.0"
+ path: ">=0.9.0 <2.0.0"
+ stack_trace: ">=0.9.1 <2.0.0"
+dev_dependencies:
+ unittest: ">=0.9.0 <0.12.0"
+environment:
+ sdk: ">=1.23.0-dev.0.0 <2.0.0"
diff --git a/pub/package_resolver/BUILD.gn b/pub/package_resolver/BUILD.gn
index 29af794..fce5ff2 100644
--- a/pub/package_resolver/BUILD.gn
+++ b/pub/package_resolver/BUILD.gn
@@ -12,7 +12,7 @@
deps = [
"//third_party/dart-pkg/pub/path",
"//third_party/dart-pkg/pub/package_config",
- "//apps/modules/packages/flutter-http",
+ "//third_party/dart-pkg/pub/http",
"//third_party/dart-pkg/pub/collection",
]
}