diff --git a/lib/http.dart b/lib/http.dart index 9ccd77a6f8..f59c19e732 100644 --- a/lib/http.dart +++ b/lib/http.dart @@ -11,8 +11,6 @@ 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'; @@ -21,8 +19,6 @@ 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]. @@ -65,10 +61,10 @@ Future get(url, {Map headers}) => /// /// For more fine-grained control over the request, use [Request] or /// [StreamedRequest] instead. -Future post(url, {Map headers, body, +Future post(url, body, {Map headers, Encoding encoding}) => - _withClient((client) => client.post(url, - headers: headers, body: body, encoding: encoding)); + _withClient((client) => client.post(url, body, + headers: headers, 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]. @@ -89,10 +85,10 @@ Future post(url, {Map headers, body, /// /// For more fine-grained control over the request, use [Request] or /// [StreamedRequest] instead. -Future put(url, {Map headers, body, +Future put(url, body, {Map headers, Encoding encoding}) => - _withClient((client) => client.put(url, - headers: headers, body: body, encoding: encoding)); + _withClient((client) => client.put(url, body, + headers: headers, 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]. @@ -113,10 +109,10 @@ Future put(url, {Map headers, body, /// /// For more fine-grained control over the request, use [Request] or /// [StreamedRequest] instead. -Future patch(url, {Map headers, body, +Future patch(url, body, {Map headers, Encoding encoding}) => - _withClient((client) => client.patch(url, - headers: headers, body: body, encoding: encoding)); + _withClient((client) => client.patch(url, body, + headers: headers, encoding: encoding)); /// Sends an HTTP DELETE request with the given headers to the given URL, which /// can be a [Uri] or a [String]. @@ -161,7 +157,7 @@ Future read(url, {Map headers}) => Future readBytes(url, {Map headers}) => _withClient((client) => client.readBytes(url, headers: headers)); -Future/**/ _withClient/**/(Future/**/ fn(Client client)) async { +Future _withClient(Future fn(Client client)) async { var client = new Client(); try { return await fn(client); diff --git a/lib/src/base_client.dart b/lib/src/base_client.dart index 7b3fbfa41e..29dd1107e3 100644 --- a/lib/src/base_client.dart +++ b/lib/src/base_client.dart @@ -6,14 +6,12 @@ import 'dart:async'; import 'dart:convert'; import 'dart:typed_data'; -import 'package:collection/collection.dart'; +import 'package:async/async.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 @@ -24,14 +22,14 @@ abstract class BaseClient implements Client { /// /// For more fine-grained control over the request, use [send] instead. Future head(url, {Map headers}) => - _sendUnstreamed("HEAD", url, headers); + send(new Request.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]. /// /// For more fine-grained control over the request, use [send] instead. Future get(url, {Map headers}) => - _sendUnstreamed("GET", url, headers); + send(new Request.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]. @@ -51,9 +49,10 @@ abstract class BaseClient implements Client { /// [encoding] defaults to UTF-8. /// /// For more fine-grained control over the request, use [send] instead. - Future post(url, {Map headers, body, + Future post(url, body, {Map headers, Encoding encoding}) => - _sendUnstreamed("POST", url, headers, body, encoding); + send(new Request.post(url, body, headers: headers, + 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]. @@ -73,9 +72,10 @@ abstract class BaseClient implements Client { /// [encoding] defaults to UTF-8. /// /// For more fine-grained control over the request, use [send] instead. - Future put(url, {Map headers, body, + Future put(url, body, {Map headers, Encoding encoding}) => - _sendUnstreamed("PUT", url, headers, body, encoding); + send(new Request.put(url, body, headers: headers, + 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]. @@ -95,16 +95,17 @@ abstract class BaseClient implements Client { /// [encoding] defaults to UTF-8. /// /// For more fine-grained control over the request, use [send] instead. - Future patch(url, {Map headers, body, + Future patch(url, body, {Map headers, Encoding encoding}) => - _sendUnstreamed("PATCH", url, headers, body, encoding); + send(new Request.patch(url, body, headers: headers, + 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 delete(url, {Map headers}) => - _sendUnstreamed("DELETE", url, headers); + send(new Request.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 @@ -115,11 +116,11 @@ abstract class BaseClient implements Client { /// /// For more fine-grained control over the request and response, use [send] or /// [get] instead. - Future read(url, {Map headers}) { - return get(url, headers: headers).then((response) { - _checkResponseSuccess(url, response); - return response.body; - }); + Future read(url, {Map headers}) async { + var response = await get(url, headers: headers); + _checkResponseSuccess(url, response); + + return await response.readAsString(); } /// Sends an HTTP GET request with the given headers to the given URL, which @@ -131,11 +132,11 @@ abstract class BaseClient implements Client { /// /// For more fine-grained control over the request and response, use [send] or /// [get] instead. - Future readBytes(url, {Map headers}) { - return get(url, headers: headers).then((response) { - _checkResponseSuccess(url, response); - return response.bodyBytes; - }); + Future readBytes(url, {Map headers}) async { + var response = await get(url, headers: headers); + _checkResponseSuccess(url, response); + + return await collectBytes(response.read()); } /// Sends an HTTP request and asynchronously returns the response. @@ -145,31 +146,7 @@ abstract class BaseClient implements Client { /// 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 send(BaseRequest request); - - /// Sends a non-streaming [Request] and returns a non-streaming [Response]. - Future _sendUnstreamed(String method, url, - Map 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)); - } + Future send(Request request); /// Throws an error if [response] is not successful. void _checkResponseSuccess(url, Response response) { diff --git a/lib/src/base_request.dart b/lib/src/base_request.dart deleted file mode 100644 index b11ef05ae5..0000000000 --- a/lib/src/base_request.dart +++ /dev/null @@ -1,140 +0,0 @@ -// 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 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 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/lib/src/base_response.dart b/lib/src/base_response.dart deleted file mode 100644 index 26427f806f..0000000000 --- a/lib/src/base_response.dart +++ /dev/null @@ -1,53 +0,0 @@ -// 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 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/lib/src/client.dart b/lib/src/client.dart index cf1ff784a0..fd4144326f 100644 --- a/lib/src/client.dart +++ b/lib/src/client.dart @@ -7,10 +7,9 @@ import 'dart:convert'; import 'dart:typed_data'; import 'base_client.dart'; -import 'base_request.dart'; import 'io_client.dart'; +import 'request.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 @@ -59,7 +58,7 @@ abstract class Client { /// [encoding] defaults to [UTF8]. /// /// For more fine-grained control over the request, use [send] instead. - Future post(url, {Map headers, body, + Future post(url, body, {Map headers, Encoding encoding}); /// Sends an HTTP PUT request with the given headers and body to the given @@ -80,7 +79,7 @@ abstract class Client { /// [encoding] defaults to [UTF8]. /// /// For more fine-grained control over the request, use [send] instead. - Future put(url, {Map headers, body, + Future put(url, body, {Map headers, Encoding encoding}); /// Sends an HTTP PATCH request with the given headers and body to the given @@ -101,7 +100,7 @@ abstract class Client { /// [encoding] defaults to [UTF8]. /// /// For more fine-grained control over the request, use [send] instead. - Future patch(url, {Map headers, body, + Future patch(url, body, {Map headers, Encoding encoding}); /// Sends an HTTP DELETE request with the given headers to the given URL, @@ -133,7 +132,7 @@ abstract class Client { Future readBytes(url, {Map headers}); /// Sends an HTTP request and asynchronously returns the response. - Future send(BaseRequest request); + Future send(Request 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 diff --git a/lib/src/request.dart b/lib/src/request.dart index 1f2be464df..f49c5b9f16 100644 --- a/lib/src/request.dart +++ b/lib/src/request.dart @@ -18,7 +18,8 @@ class Request extends Message { /// The URL to which the request will be sent. final Uri url; - /// Creates a new [Request] for [url] using [method]. + /// Creates a new [Request] for [url], which can be a [Uri] or a [String], + /// using [method]. /// /// [body] is the request body. It may be either a [String], a [List], a /// [Stream>], or `null` to indicate no body. If it's a [String], @@ -30,36 +31,37 @@ class Request extends Message { /// /// Extra [context] can be used to pass information between inner middleware /// and handlers. - Request(this.method, this.url, + Request(String method, url, {body, Encoding encoding, Map headers, Map context}) - : super(body, encoding: encoding, headers: headers, context: context); + : this._(method, getUrl(url), body, encoding, headers, context); - /// Creates a new HEAD [Request] to [url]. + /// Creates a new HEAD [Request] to [url], which can be a [Uri] or a [String]. /// /// [headers] are the HTTP headers for the request. If [headers] is `null`, /// it is treated as empty. /// /// Extra [context] can be used to pass information between inner middleware /// and handlers. - Request.head(Uri url, + Request.head(url, {Map headers, Map context}) : this('HEAD', url, headers: headers, context: context); - /// Creates a new GET [Request] to [url]. + /// Creates a new GET [Request] to [url], which can be a [Uri] or a [String]. /// /// [headers] are the HTTP headers for the request. If [headers] is `null`, /// it is treated as empty. /// /// Extra [context] can be used to pass information between inner middleware /// and handlers. - Request.get(Uri url, + Request.get(url, {Map headers, Map context}) : this('GET', url, headers: headers, context: context); - /// Creates a new POST [Request] to [url]. + /// Creates a new POST [Request] to [url], which can be a [Uri] or a [String]. + /// /// [body] is the request body. It may be either a [String], a [List], a /// [Stream>], or `null` to indicate no body. If it's a [String], /// [encoding] is used to encode it to a [Stream>]. It defaults to @@ -70,15 +72,14 @@ class Request extends Message { /// /// Extra [context] can be used to pass information between inner middleware /// and handlers. - Request.post(Uri url, - body, + Request.post(url, body, {Encoding encoding, Map headers, Map context}) : this('POST', url, body: body, encoding: encoding, headers: headers, context: context); - /// Creates a new PUT [Request] to [url]. + /// Creates a new PUT [Request] to [url], which can be a [Uri] or a [String]. /// /// [body] is the request body. It may be either a [String], a [List], a /// [Stream>], or `null` to indicate no body. If it's a [String], @@ -90,15 +91,15 @@ class Request extends Message { /// /// Extra [context] can be used to pass information between inner middleware /// and handlers. - Request.put(Uri url, - body, + Request.put(url, body, {Encoding encoding, Map headers, Map context}) : this('PUT', url, body: body, encoding: encoding, headers: headers, context: context); - /// Creates a new PATCH [Request] to [url]. + /// Creates a new PATCH [Request] to [url], which can be a [Uri] or a + /// [String]. /// /// [body] is the request body. It may be either a [String], a [List], a /// [Stream>], or `null` to indicate no body. If it's a [String], @@ -110,25 +111,32 @@ class Request extends Message { /// /// Extra [context] can be used to pass information between inner middleware /// and handlers. - Request.patch(Uri url, - body, + Request.patch(url, body, {Encoding encoding, Map headers, Map context}) : this('PATCH', url, body: body, encoding: encoding, headers: headers, context: context); - /// Creates a new DELETE [Request] to [url]. + /// Creates a new DELETE [Request] to [url], which can be a [Uri] or a + /// [String]. /// /// [headers] are the HTTP headers for the request. If [headers] is `null`, /// it is treated as empty. /// /// Extra [context] can be used to pass information between inner middleware /// and handlers. - Request.delete(Uri url, + Request.delete(url, {Map headers, Map context}) : this('DELETE', url, headers: headers, context: context); + Request._(this.method, this.url, + body, + Encoding encoding, + Map headers, + Map context) + : super(body, encoding: encoding, headers: headers, context: context); + /// Creates a new [Request] by copying existing values and applying specified /// changes. /// @@ -147,10 +155,12 @@ class Request extends Message { var updatedHeaders = updateMap(this.headers, headers); var updatedContext = updateMap(this.context, context); - return new Request(this.method, this.url, - body: body ?? getBody(this), - encoding: this.encoding, - headers: updatedHeaders, - context: updatedContext); + return new Request._( + this.method, + this.url, + body ?? getBody(this), + this.encoding, + updatedHeaders, + updatedContext); } } diff --git a/lib/src/streamed_request.dart b/lib/src/streamed_request.dart deleted file mode 100644 index 6a020bd77a..0000000000 --- a/lib/src/streamed_request.dart +++ /dev/null @@ -1,41 +0,0 @@ -// 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> get sink => _controller.sink; - - /// The controller for [sink], from which [BaseRequest] will read data for - /// [finalize]. - final StreamController> _controller; - - /// Creates a new streaming request. - StreamedRequest(String method, Uri url) - : _controller = new StreamController>(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/lib/src/streamed_response.dart b/lib/src/streamed_response.dart deleted file mode 100644 index 69d8356c1d..0000000000 --- a/lib/src/streamed_response.dart +++ /dev/null @@ -1,39 +0,0 @@ -// 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> stream, - int statusCode, - {int contentLength, - BaseRequest request, - Map 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/lib/src/utils.dart b/lib/src/utils.dart index 39f1aefac4..b7e7347a3d 100644 --- a/lib/src/utils.dart +++ b/lib/src/utils.dart @@ -170,3 +170,16 @@ String getHeader(Map headers, String name) { } return null; } + +/// Returns a [Uri] from the [url], which can be a [Uri] or a [String]. +/// +/// If the [url] is not a [Uri] or [String] an [ArgumentError] is thrown. +Uri getUrl(url) { + if (url is Uri) { + return url; + } else if (url is String) { + return Uri.parse(url); + } else { + throw new ArgumentError.value(url, 'url', 'Not a Uri or String'); + } +} diff --git a/pubspec.yaml b/pubspec.yaml index 8f419adbb6..f059565133 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,10 +1,10 @@ name: http -version: 0.11.3+11 +version: 0.12.0-dev author: "Dart Team " homepage: https://github.com/dart-lang/http description: A composable, Future-based API for making HTTP requests. dependencies: - async: "^1.10.0" + async: "^1.13.0" collection: "^1.5.0" http_parser: ">=0.0.1 <4.0.0" path: ">=0.9.0 <2.0.0"