Remove the http package from the repo.

This also does away with the pub_packages build target.

R=ahe@google.com, rnystrom@google.com, sigmund@google.com

Review URL: https://codereview.chromium.org//810223002

git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@42469 260f80e4-7a28-3924-810f-c04153c831b5
This commit is contained in:
nweiz@google.com 2014-12-18 19:04:04 +00:00
parent 0935b930ea
commit 80e6b5be2c
43 changed files with 4 additions and 3891 deletions

View file

@ -1,40 +0,0 @@
## 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.

View file

@ -1,26 +0,0 @@
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.

View file

@ -1,100 +0,0 @@
# 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://api.dartlang.org/apidocs/channels/stable/dartdoc-viewer/http/http.Request
[StreamedRequest]: https://api.dartlang.org/apidocs/channels/stable/dartdoc-viewer/http/http.StreamedRequest
[Client.send]: https://api.dartlang.org/apidocs/channels/stable/dartdoc-viewer/http/http.Client#id_send
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://api.dartlang.org/apidocs/channels/stable/dartdoc-viewer/http/http.BaseClient
[Client]: https://api.dartlang.org/apidocs/channels/stable/dartdoc-viewer/http/http.Client
```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:
```dart
import 'package:http/browser_client.dart';
import 'package:http/http.dart' as http;
var client = new BrowserClient();
var url = "/whatsit/create";
client.post(url, body: {"name": "doodle", "color": "blue"})
.then((response) {
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

View file

@ -1,97 +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.
library http.browser_client;
import 'dart:async';
import 'dart:html';
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 we can create an HttpRequest using
// mirrors on dart2js (issue 18541) and dart2js doesn't crash on pkg/collection
// (issue 18535).
/// 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();
/// Sends an HTTP request and asynchronously returns the response.
Future<StreamedResponse> send(BaseRequest request) {
return request.finalize().toBytes().then((bytes) {
var xhr = new HttpRequest();
_xhrs.add(xhr);
xhr.open(request.method, request.url.toString(), async: true);
xhr.responseType = 'blob';
request.headers.forEach(xhr.setRequestHeader);
var completer = new Completer();
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;
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);
return completer.future.whenComplete(() => _xhrs.remove(xhr));
});
}
/// Closes the client.
///
/// This terminates all active requests.
void close() {
for (var xhr in _xhrs) {
xhr.abort();
}
}
}

View file

@ -1,146 +0,0 @@
// 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.
library http;
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 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 _withClient(Future fn(Client)) {
var client = new Client();
var future = fn(client);
return future.whenComplete(client.close);
}

View file

@ -1,169 +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.
library base_client;
import 'dart:async';
import 'dart:convert';
import 'dart:typed_data';
import 'base_request.dart';
import 'client.dart';
import 'exception.dart';
import 'request.dart';
import 'response.dart';
import 'streamed_response.dart';
import 'utils.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 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]) {
return syncFuture(() {
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 = body;
} else if (body is Map) {
request.bodyFields = body;
} else {
throw new ArgumentError('Invalid request body "$body".');
}
}
return send(request);
}).then(Response.fromStream);
}
/// 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() {}
}

View file

@ -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.
library base_request;
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() {
var client = new Client();
return client.send(this).then((response) {
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);
}).catchError((e) {
client.close();
throw e;
});
}
// 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";
}

View file

@ -1,55 +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.
library base_response;
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.");
}
}
}

View file

@ -1,40 +0,0 @@
// 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.
library byte_stream;
import 'dart:async';
import 'dart:convert';
import 'dart:typed_data';
import 'utils.dart';
/// 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(streamFromIterable([bytes]));
/// Collects the data of this stream in a [Uint8List].
Future<Uint8List> toBytes() {
var completer = new Completer();
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]) =>
transform(encoding.decoder);
}

View file

@ -1,127 +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.
library client;
import 'dart:async';
import 'dart:convert';
import 'dart:typed_data';
import 'base_client.dart';
import 'base_request.dart';
import 'io.dart' as io;
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], 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() {
io.assertSupported("IOClient");
return 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 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();
}

View file

@ -1,17 +0,0 @@
// 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.
library http.exception;
/// 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;
}

View file

@ -1,57 +0,0 @@
// 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.
library http.io;
@MirrorsUsed(targets: const ['dart.io.HttpClient', 'dart.io.HttpException',
'dart.io.File'])
import 'dart:mirrors';
/// Whether `dart:io` is supported on this platform.
bool get supported => _library != null;
/// The `dart:io` library mirror, or `null` if it couldn't be loaded.
final _library = _getLibrary();
/// The `dart:io` HttpClient class mirror.
final ClassMirror _httpClient =
_library.declarations[const Symbol('HttpClient')];
/// The `dart:io` HttpException class mirror.
final ClassMirror _httpException =
_library.declarations[const Symbol('HttpException')];
/// The `dart:io` File class mirror.
final ClassMirror _file = _library.declarations[const Symbol('File')];
/// Asserts that the [name]d `dart:io` feature is supported on this platform.
///
/// If `dart:io` doesn't work on this platform, this throws an
/// [UnsupportedError].
void assertSupported(String name) {
if (supported) return;
throw new UnsupportedError("$name isn't supported on this platform.");
}
/// Creates a new `dart:io` HttpClient instance.
newHttpClient() => _httpClient.newInstance(const Symbol(''), []).reflectee;
/// Creates a new `dart:io` File instance with the given [path].
newFile(String path) => _file.newInstance(const Symbol(''), [path]).reflectee;
/// Returns whether [error] is a `dart:io` HttpException.
bool isHttpException(error) => reflect(error).type.isSubtypeOf(_httpException);
/// Returns whether [client] is a `dart:io` HttpClient.
bool isHttpClient(client) => reflect(client).type.isSubtypeOf(_httpClient);
/// Tries to load `dart:io` and returns `null` if it fails.
LibraryMirror _getLibrary() {
try {
return currentMirrorSystem().findLibrary(const Symbol('dart.io'));
} catch (_) {
// TODO(nweiz): narrow the catch clause when issue 18532 is fixed.
return null;
}
}

View file

@ -1,88 +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.
library io_client;
import 'dart:async';
import 'package:stack_trace/stack_trace.dart';
import 'base_client.dart';
import 'base_request.dart';
import 'exception.dart';
import 'io.dart' as io;
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.
var _inner;
/// Creates a new HTTP client.
///
/// [innerClient] must be a `dart:io` HTTP client. If it's not passed, a
/// default one will be instantiated.
IOClient([innerClient]) {
io.assertSupported("IOClient");
if (innerClient != null) {
// TODO(nweiz): remove this assert when we can type [innerClient]
// properly.
assert(io.isHttpClient(innerClient));
_inner = innerClient;
} else {
_inner = io.newHttpClient();
}
}
/// Sends an HTTP request and asynchronously returns the response.
Future<StreamedResponse> send(BaseRequest request) {
var stream = request.finalize();
return Chain.track(_inner.openUrl(request.method, request.url))
.then((ioRequest) {
var contentLength = request.contentLength == null ?
-1 : request.contentLength;
ioRequest
..followRedirects = request.followRedirects
..maxRedirects = request.maxRedirects
..contentLength = contentLength
..persistentConnection = request.persistentConnection;
request.headers.forEach((name, value) {
ioRequest.headers.set(name, value);
});
return Chain.track(stream.pipe(ioRequest));
}).then((response) {
var headers = {};
response.headers.forEach((key, values) {
headers[key] = values.join(',');
});
var contentLength = response.contentLength == -1 ?
null : response.contentLength;
return new StreamedResponse(
response.handleError((error) =>
throw new ClientException(error.message, error.uri),
test: (error) => io.isHttpException(error)),
response.statusCode,
contentLength: contentLength,
request: request,
headers: headers,
isRedirect: response.isRedirect,
persistentConnection: response.persistentConnection,
reasonPhrase: response.reasonPhrase);
}).catchError((error) {
if (!io.isHttpException(error)) throw 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;
}
}

View file

@ -1,89 +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.
library mock_client;
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';
import 'utils.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) {
var bodyStream = request.finalize();
return async.then((_) => _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);

View file

@ -1,114 +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.
library multipart_file;
import 'dart:async';
import 'dart:convert';
import 'package:http_parser/http_parser.dart';
import 'package:path/path.dart' as path;
import 'package:stack_trace/stack_trace.dart';
import 'byte_stream.dart';
import 'io.dart' as io;
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].
///
/// This can only be used in an environment that supports "dart:io".
static Future<MultipartFile> fromPath(String field, String filePath,
{String filename, MediaType contentType}) {
io.assertSupported("MultipartFile.fromPath");
if (filename == null) filename = path.basename(filePath);
var file = io.newFile(filePath);
return Chain.track(file.length()).then((length) {
var stream = new ByteStream(Chain.track(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;
}
}

View file

@ -1,176 +0,0 @@
// 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.
library multipart_request;
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` and the Content-Transfer-Encoding header to `binary`.
/// These values will override any values 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)
: super(method, url),
fields = {},
_files = <MultipartFile>[];
/// 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"';
headers['content-transfer-encoding'] = 'binary';
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. From
/// http://tools.ietf.org/html/rfc2046#section-5.1.1.
static const List<int> _BOUNDARY_CHARACTERS = const <int>[
39, 40, 41, 43, 95, 44, 45, 46, 47, 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\ncontent-type: text/plain; charset=utf-8';
}
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)}";
}
}

View file

@ -1,163 +0,0 @@
// 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.
library request;
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 queryToMap(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)
: super(method, url),
_defaultEncoding = UTF8,
_bodyBytes = new Uint8List(0);
/// 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.");
}
}

View file

@ -1,97 +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.
library response;
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");
}

View file

@ -1,43 +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.
library streamed_request;
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)
: super(method, url),
_controller = new StreamController<List<int>>(sync: true);
/// 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);
}
}

View file

@ -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.
library streamed_response;
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})
: super(
statusCode,
contentLength: contentLength,
request: request,
headers: headers,
isRedirect: isRedirect,
persistentConnection: persistentConnection,
reasonPhrase: reasonPhrase),
this.stream = toByteStream(stream);
}

View file

@ -1,201 +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.
library utils;
import 'dart:async';
import 'dart:convert';
import 'dart:typed_data';
import 'package:stack_trace/stack_trace.dart';
import 'byte_stream.dart';
/// Converts a URL query string (or `application/x-www-form-urlencoded` body)
/// into a [Map] from parameter names to values.
///
/// queryToMap("foo=bar&baz=bang&qux");
/// //=> {"foo": "bar", "baz": "bang", "qux": ""}
Map<String, String> queryToMap(String queryList, {Encoding encoding}) {
var map = {};
for (var pair in queryList.split("&")) {
var split = split1(pair, "=");
if (split.isEmpty) continue;
var key = Uri.decodeQueryComponent(split[0], encoding: encoding);
var value = Uri.decodeQueryComponent(split.length > 1 ? split[1] : "",
encoding: encoding);
map[key] = value;
}
return map;
}
/// 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 onDone(Stream stream, void onDone()) {
var pair = tee(stream);
pair.first.listen((_) {}, onError: (_) {}, onDone: onDone);
return pair.last;
}
// 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;
}
/// Returns a [Future] that asynchronously completes to `null`.
Future get async => new Future.value();
/// Returns a closed [Stream] with no elements.
Stream get emptyStream => streamFromIterable([]);
/// Creates a single-subscription stream that emits the items in [iter] and then
/// ends.
Stream streamFromIterable(Iterable iter) {
var controller = new StreamController(sync: true);
iter.forEach(controller.add);
controller.close();
return controller.stream;
}
// TODO(nweiz): remove this when issue 7787 is fixed.
/// Creates two single-subscription [Stream]s that each emit all values and
/// errors from [stream]. This is useful if [stream] is single-subscription but
/// multiple subscribers are necessary.
Pair<Stream, Stream> tee(Stream stream) {
var controller1 = new StreamController(sync: true);
var controller2 = new StreamController(sync: true);
stream.listen((value) {
controller1.add(value);
controller2.add(value);
}, onError: (error, [StackTrace stackTrace]) {
controller1.addError(error, stackTrace);
controller2.addError(error, stackTrace);
}, onDone: () {
controller1.close();
controller2.close();
});
return new Pair<Stream, Stream>(controller1.stream, controller2.stream);
}
/// 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);
}
/// Like [Future.sync], but wraps the Future in [Chain.track] as well.
Future syncFuture(callback()) => Chain.track(new Future.sync(callback));

View file

@ -1,26 +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.
/// 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) {
/// 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'
/// });
/// };
library http.testing;
export 'src/mock_client.dart';

View file

@ -1,13 +0,0 @@
name: http
version: 0.11.1+2
author: "Dart Team <misc@dartlang.org>"
homepage: https://pub.dartlang.org/packages/http
description: A composable, Future-based API for making HTTP requests.
dependencies:
http_parser: ">=0.0.1 <0.1.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.1.0 <2.0.0"

View file

@ -1,37 +0,0 @@
// 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.
library client_test;
import 'package:http/http.dart' as http;
import 'package:http/browser_client.dart';
import 'package:unittest/unittest.dart';
import 'utils.dart';
void main() {
test('#send a StreamedRequest', () {
var client = new BrowserClient();
var request = new http.StreamedRequest("POST", echoUrl);
expect(client.send(request).then((response) {
return response.stream.bytesToString();
}).whenComplete(client.close), completion(equals('{"hello": "world"}')));
request.sink.add('{"hello": "world"}'.codeUnits);
request.sink.close();
});
test('#send with an invalid URL', () {
var client = new BrowserClient();
var url = Uri.parse('http://http.invalid');
var request = new http.StreamedRequest("POST", url);
expect(client.send(request),
throwsClientException("XMLHttpRequest error."));
request.sink.add('{"hello": "world"}'.codeUnits);
request.sink.close();
});
}

View file

@ -1,38 +0,0 @@
// 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.
library http.test.html.streamed_request_test;
import 'package:http/http.dart' as http;
import 'package:http/browser_client.dart';
import 'package:unittest/unittest.dart';
import 'utils.dart';
void main() {
group('contentLength', () {
test("works when it's set", () {
var request = new http.StreamedRequest('POST', echoUrl);
request.contentLength = 10;
request.sink.add([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
request.sink.close();
return new BrowserClient().send(request).then((response) {
expect(response.stream.toBytes(),
completion(equals([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])));
});
});
test("works when it's not set", () {
var request = new http.StreamedRequest('POST', echoUrl);
request.sink.add([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
request.sink.close();
return new BrowserClient().send(request).then((response) {
expect(response.stream.toBytes(),
completion(equals([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])));
});
});
});
}

View file

@ -1,13 +0,0 @@
// 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.
library http.test.html_utils;
import 'dart:html';
export '../utils.dart';
/// The test server's echo URL.
Uri get echoUrl => Uri.parse(
'${window.location.protocol}//${window.location.host}/echo');

View file

@ -1,100 +0,0 @@
// 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.
library http.test.io.client_test;
import 'dart:io';
import 'package:http/http.dart' as http;
import 'package:unittest/unittest.dart';
import 'utils.dart';
void main() {
tearDown(stopServer);
test('#send a StreamedRequest', () {
expect(startServer().then((_) {
var client = new http.Client();
var request = new http.StreamedRequest("POST", serverUrl);
request.headers[HttpHeaders.CONTENT_TYPE] =
'application/json; charset=utf-8';
request.headers[HttpHeaders.USER_AGENT] = 'Dart';
expect(client.send(request).then((response) {
expect(response.request, equals(request));
expect(response.statusCode, equals(200));
expect(response.headers['single'], equals('value'));
// dart:io internally normalizes outgoing headers so that they never
// have multiple headers with the same name, so there's no way to test
// whether we handle that case correctly.
return response.stream.bytesToString();
}).whenComplete(client.close), completion(parse(equals({
'method': 'POST',
'path': '/',
'headers': {
'content-type': ['application/json; charset=utf-8'],
'accept-encoding': ['gzip'],
'user-agent': ['Dart'],
'transfer-encoding': ['chunked']
},
'body': '{"hello": "world"}'
}))));
request.sink.add('{"hello": "world"}'.codeUnits);
request.sink.close();
}), completes);
});
test('#send a StreamedRequest with a custom client', () {
expect(startServer().then((_) {
var ioClient = new HttpClient();
var client = new http.IOClient(ioClient);
var request = new http.StreamedRequest("POST", serverUrl);
request.headers[HttpHeaders.CONTENT_TYPE] =
'application/json; charset=utf-8';
request.headers[HttpHeaders.USER_AGENT] = 'Dart';
expect(client.send(request).then((response) {
expect(response.request, equals(request));
expect(response.statusCode, equals(200));
expect(response.headers['single'], equals('value'));
// dart:io internally normalizes outgoing headers so that they never
// have multiple headers with the same name, so there's no way to test
// whether we handle that case correctly.
return response.stream.bytesToString();
}).whenComplete(client.close), completion(parse(equals({
'method': 'POST',
'path': '/',
'headers': {
'content-type': ['application/json; charset=utf-8'],
'accept-encoding': ['gzip'],
'user-agent': ['Dart'],
'transfer-encoding': ['chunked']
},
'body': '{"hello": "world"}'
}))));
request.sink.add('{"hello": "world"}'.codeUnits);
request.sink.close();
}), completes);
});
test('#send with an invalid URL', () {
expect(startServer().then((_) {
var client = new http.Client();
var url = Uri.parse('http://http.invalid');
var request = new http.StreamedRequest("POST", url);
request.headers[HttpHeaders.CONTENT_TYPE] =
'application/json; charset=utf-8';
expect(client.send(request), throwsSocketException);
request.sink.add('{"hello": "world"}'.codeUnits);
request.sink.close();
}), completes);
});
}

View file

@ -1,334 +0,0 @@
// 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.
library http.test.io.http_test;
import 'package:http/http.dart' as http;
import 'package:unittest/unittest.dart';
import 'utils.dart';
main() {
group('http.', () {
tearDown(stopServer);
test('head', () {
expect(startServer().then((_) {
expect(http.head(serverUrl).then((response) {
expect(response.statusCode, equals(200));
expect(response.body, equals(''));
}), completes);
}), completes);
});
test('get', () {
expect(startServer().then((_) {
expect(http.get(serverUrl, headers: {
'X-Random-Header': 'Value',
'X-Other-Header': 'Other Value',
'User-Agent': 'Dart'
}).then((response) {
expect(response.statusCode, equals(200));
expect(response.body, parse(equals({
'method': 'GET',
'path': '/',
'headers': {
'content-length': ['0'],
'accept-encoding': ['gzip'],
'user-agent': ['Dart'],
'x-random-header': ['Value'],
'x-other-header': ['Other Value']
},
})));
}), completes);
}), completes);
});
test('post', () {
expect(startServer().then((_) {
expect(http.post(serverUrl, headers: {
'X-Random-Header': 'Value',
'X-Other-Header': 'Other Value',
'Content-Type': 'text/plain',
'User-Agent': 'Dart'
}).then((response) {
expect(response.statusCode, equals(200));
expect(response.body, parse(equals({
'method': 'POST',
'path': '/',
'headers': {
'accept-encoding': ['gzip'],
'content-length': ['0'],
'content-type': ['text/plain'],
'user-agent': ['Dart'],
'x-random-header': ['Value'],
'x-other-header': ['Other Value']
}
})));
}), completes);
}), completes);
});
test('post with string', () {
expect(startServer().then((_) {
expect(http.post(serverUrl, headers: {
'X-Random-Header': 'Value',
'X-Other-Header': 'Other Value',
'User-Agent': 'Dart'
}, body: 'request body').then((response) {
expect(response.statusCode, equals(200));
expect(response.body, parse(equals({
'method': 'POST',
'path': '/',
'headers': {
'content-type': ['text/plain; charset=utf-8'],
'content-length': ['12'],
'accept-encoding': ['gzip'],
'user-agent': ['Dart'],
'x-random-header': ['Value'],
'x-other-header': ['Other Value']
},
'body': 'request body'
})));
}), completes);
}), completes);
});
test('post with bytes', () {
expect(startServer().then((_) {
expect(http.post(serverUrl, headers: {
'X-Random-Header': 'Value',
'X-Other-Header': 'Other Value',
'User-Agent': 'Dart'
}, body: [104, 101, 108, 108, 111]).then((response) {
expect(response.statusCode, equals(200));
expect(response.body, parse(equals({
'method': 'POST',
'path': '/',
'headers': {
'content-length': ['5'],
'accept-encoding': ['gzip'],
'user-agent': ['Dart'],
'x-random-header': ['Value'],
'x-other-header': ['Other Value']
},
'body': [104, 101, 108, 108, 111]
})));
}), completes);
}), completes);
});
test('post with fields', () {
expect(startServer().then((_) {
expect(http.post(serverUrl, headers: {
'X-Random-Header': 'Value',
'X-Other-Header': 'Other Value',
'User-Agent': 'Dart'
}, body: {
'some-field': 'value',
'other-field': 'other value'
}).then((response) {
expect(response.statusCode, equals(200));
expect(response.body, parse(equals({
'method': 'POST',
'path': '/',
'headers': {
'content-type': [
'application/x-www-form-urlencoded; charset=utf-8'
],
'content-length': ['40'],
'accept-encoding': ['gzip'],
'user-agent': ['Dart'],
'x-random-header': ['Value'],
'x-other-header': ['Other Value']
},
'body': 'some-field=value&other-field=other+value'
})));
}), completes);
}), completes);
});
test('put', () {
expect(startServer().then((_) {
expect(http.put(serverUrl, headers: {
'X-Random-Header': 'Value',
'X-Other-Header': 'Other Value',
'Content-Type': 'text/plain',
'User-Agent': 'Dart'
}).then((response) {
expect(response.statusCode, equals(200));
expect(response.body, parse(equals({
'method': 'PUT',
'path': '/',
'headers': {
'accept-encoding': ['gzip'],
'content-length': ['0'],
'content-type': ['text/plain'],
'user-agent': ['Dart'],
'x-random-header': ['Value'],
'x-other-header': ['Other Value']
}
})));
}), completes);
}), completes);
});
test('put with string', () {
expect(startServer().then((_) {
expect(http.put(serverUrl, headers: {
'X-Random-Header': 'Value',
'X-Other-Header': 'Other Value',
'User-Agent': 'Dart'
}, body: 'request body').then((response) {
expect(response.statusCode, equals(200));
expect(response.body, parse(equals({
'method': 'PUT',
'path': '/',
'headers': {
'content-type': ['text/plain; charset=utf-8'],
'content-length': ['12'],
'accept-encoding': ['gzip'],
'user-agent': ['Dart'],
'x-random-header': ['Value'],
'x-other-header': ['Other Value']
},
'body': 'request body'
})));
}), completes);
}), completes);
});
test('put with bytes', () {
expect(startServer().then((_) {
expect(http.put(serverUrl, headers: {
'X-Random-Header': 'Value',
'X-Other-Header': 'Other Value',
'User-Agent': 'Dart'
}, body: [104, 101, 108, 108, 111]).then((response) {
expect(response.statusCode, equals(200));
expect(response.body, parse(equals({
'method': 'PUT',
'path': '/',
'headers': {
'content-length': ['5'],
'accept-encoding': ['gzip'],
'user-agent': ['Dart'],
'x-random-header': ['Value'],
'x-other-header': ['Other Value']
},
'body': [104, 101, 108, 108, 111]
})));
}), completes);
}), completes);
});
test('put with fields', () {
expect(startServer().then((_) {
expect(http.put(serverUrl, headers: {
'X-Random-Header': 'Value',
'X-Other-Header': 'Other Value',
'User-Agent': 'Dart'
}, body: {
'some-field': 'value',
'other-field': 'other value'
}).then((response) {
expect(response.statusCode, equals(200));
expect(response.body, parse(equals({
'method': 'PUT',
'path': '/',
'headers': {
'content-type': [
'application/x-www-form-urlencoded; charset=utf-8'
],
'content-length': ['40'],
'accept-encoding': ['gzip'],
'user-agent': ['Dart'],
'x-random-header': ['Value'],
'x-other-header': ['Other Value']
},
'body': 'some-field=value&other-field=other+value'
})));
}), completes);
}), completes);
});
test('delete', () {
expect(startServer().then((_) {
expect(http.delete(serverUrl, headers: {
'X-Random-Header': 'Value',
'X-Other-Header': 'Other Value',
'User-Agent': 'Dart'
}).then((response) {
expect(response.statusCode, equals(200));
expect(response.body, parse(equals({
'method': 'DELETE',
'path': '/',
'headers': {
'content-length': ['0'],
'accept-encoding': ['gzip'],
'user-agent': ['Dart'],
'x-random-header': ['Value'],
'x-other-header': ['Other Value']
}
})));
}), completes);
}), completes);
});
test('read', () {
expect(startServer().then((_) {
expect(http.read(serverUrl, headers: {
'X-Random-Header': 'Value',
'X-Other-Header': 'Other Value',
'User-Agent': 'Dart'
}).then((val) => val), completion(parse(equals({
'method': 'GET',
'path': '/',
'headers': {
'content-length': ['0'],
'accept-encoding': ['gzip'],
'user-agent': ['Dart'],
'x-random-header': ['Value'],
'x-other-header': ['Other Value']
},
}))));
}), completes);
});
test('read throws an error for a 4** status code', () {
expect(startServer().then((_) {
expect(http.read(serverUrl.resolve('/error')), throwsClientException);
}), completes);
});
test('readBytes', () {
expect(startServer().then((_) {
var future = http.readBytes(serverUrl, headers: {
'X-Random-Header': 'Value',
'X-Other-Header': 'Other Value',
'User-Agent': 'Dart'
}).then((bytes) => new String.fromCharCodes(bytes));
expect(future, completion(parse(equals({
'method': 'GET',
'path': '/',
'headers': {
'content-length': ['0'],
'accept-encoding': ['gzip'],
'user-agent': ['Dart'],
'x-random-header': ['Value'],
'x-other-header': ['Other Value']
},
}))));
}), completes);
});
test('readBytes throws an error for a 4** status code', () {
expect(startServer().then((_) {
expect(http.readBytes(serverUrl.resolve('/error')),
throwsClientException);
}), completes);
});
});
}

View file

@ -1,43 +0,0 @@
// 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.
library http.test.io.multipart_test;
import 'dart:async';
import 'dart:io';
import 'package:http/http.dart' as http;
import 'package:path/path.dart' as path;
import 'package:unittest/unittest.dart';
import 'utils.dart';
void main() {
var tempDir;
setUp(() {
tempDir = Directory.systemTemp.createTempSync('http_test_');
});
tearDown(() => tempDir.deleteSync(recursive: true));
test('with a file from disk', () {
expect(new Future.sync(() {
var filePath = path.join(tempDir.path, 'test-file');
new File(filePath).writeAsStringSync('hello');
return http.MultipartFile.fromPath('file', filePath);
}).then((file) {
var request = new http.MultipartRequest('POST', dummyUrl);
request.files.add(file);
expect(request, bodyMatches('''
--{{boundary}}
content-type: application/octet-stream
content-disposition: form-data; name="file"; filename="test-file"
hello
--{{boundary}}--
'''));
}), completes);
});
}

View file

@ -1,61 +0,0 @@
// 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.
library http.test.io.request_test;
import 'package:http/http.dart' as http;
import 'package:unittest/unittest.dart';
import 'utils.dart';
void main() {
test('.send', () {
expect(startServer().then((_) {
var request = new http.Request('POST', serverUrl);
request.body = "hello";
request.headers['User-Agent'] = 'Dart';
expect(request.send().then((response) {
expect(response.statusCode, equals(200));
return response.stream.bytesToString();
}).whenComplete(stopServer), completion(parse(equals({
'method': 'POST',
'path': '/',
'headers': {
'content-type': ['text/plain; charset=utf-8'],
'accept-encoding': ['gzip'],
'user-agent': ['Dart'],
'content-length': ['5']
},
'body': 'hello'
}))));
}), completes);
});
test('#followRedirects', () {
expect(startServer().then((_) {
var request = new http.Request('POST', serverUrl.resolve('/redirect'))
..followRedirects = false;
var future = request.send().then((response) {
expect(response.statusCode, equals(302));
});
expect(future.catchError((_) {}).then((_) => stopServer()), completes);
expect(future, completes);
}), completes);
});
test('#maxRedirects', () {
expect(startServer().then((_) {
var request = new http.Request('POST', serverUrl.resolve('/loop?1'))
..maxRedirects = 2;
var future = request.send().catchError((error) {
expect(error, isRedirectLimitExceededException);
expect(error.redirects.length, equals(2));
});
expect(future.catchError((_) {}).then((_) => stopServer()), completes);
expect(future, completes);
}), completes);
});
}

View file

@ -1,58 +0,0 @@
// 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.
library http.test.io.streamed_request_test;
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:unittest/unittest.dart';
import 'utils.dart';
void main() {
group('contentLength', () {
test('controls the Content-Length header', () {
return startServer().then((_) {
var request = new http.StreamedRequest('POST', serverUrl);
request.contentLength = 10;
request.sink.add([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
request.sink.close();
return request.send();
}).then((response) {
expect(UTF8.decodeStream(response.stream),
completion(parse(containsPair('headers',
containsPair('content-length', ['10'])))));
}).whenComplete(stopServer);
});
test('defaults to sending no Content-Length', () {
return startServer().then((_) {
var request = new http.StreamedRequest('POST', serverUrl);
request.sink.add([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
request.sink.close();
return request.send();
}).then((response) {
expect(UTF8.decodeStream(response.stream),
completion(parse(containsPair('headers',
isNot(contains('content-length'))))));
}).whenComplete(stopServer);
});
});
// Regression test.
test('.send() with a response with no content length', () {
return startServer().then((_) {
var request = new http.StreamedRequest(
'GET', serverUrl.resolve('/no-content-length'));
request.sink.close();
return request.send();
}).then((response) {
expect(UTF8.decodeStream(response.stream), completion(equals('body')));
}).whenComplete(stopServer);
});
}

View file

@ -1,151 +0,0 @@
// 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.
library http.test.io_utils;
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:http/http.dart';
import 'package:http/src/utils.dart';
import 'package:unittest/unittest.dart';
export '../utils.dart';
/// The current server instance.
HttpServer _server;
/// The URL for the current server instance.
Uri get serverUrl => Uri.parse('http://localhost:${_server.port}');
/// Starts a new HTTP server.
Future startServer() {
return HttpServer.bind("localhost", 0).then((s) {
_server = s;
s.listen((request) {
var path = request.uri.path;
var response = request.response;
if (path == '/error') {
response.statusCode = 400;
response.contentLength = 0;
response.close();
return;
}
if (path == '/loop') {
var n = int.parse(request.uri.query);
response.statusCode = 302;
response.headers.set('location',
serverUrl.resolve('/loop?${n + 1}').toString());
response.contentLength = 0;
response.close();
return;
}
if (path == '/redirect') {
response.statusCode = 302;
response.headers.set('location', serverUrl.resolve('/').toString());
response.contentLength = 0;
response.close();
return;
}
if (path == '/no-content-length') {
response.statusCode = 200;
response.contentLength = -1;
response.write('body');
response.close();
return;
}
new ByteStream(request).toBytes().then((requestBodyBytes) {
var outputEncoding;
var encodingName = request.uri.queryParameters['response-encoding'];
if (encodingName != null) {
outputEncoding = requiredEncodingForCharset(encodingName);
} else {
outputEncoding = ASCII;
}
response.headers.contentType =
new ContentType(
"application", "json", charset: outputEncoding.name);
response.headers.set('single', 'value');
var requestBody;
if (requestBodyBytes.isEmpty) {
requestBody = null;
} else if (request.headers.contentType != null &&
request.headers.contentType.charset != null) {
var encoding = requiredEncodingForCharset(
request.headers.contentType.charset);
requestBody = encoding.decode(requestBodyBytes);
} else {
requestBody = requestBodyBytes;
}
var content = {
'method': request.method,
'path': request.uri.path,
'headers': {}
};
if (requestBody != null) content['body'] = requestBody;
request.headers.forEach((name, values) {
// These headers are automatically generated by dart:io, so we don't
// want to test them here.
if (name == 'cookie' || name == 'host') return;
content['headers'][name] = values;
});
var body = JSON.encode(content);
response.contentLength = body.length;
response.write(body);
response.close();
});
});
});
}
/// Stops the current HTTP server.
void stopServer() {
if (_server != null) {
_server.close();
_server = null;
}
}
/// A matcher for functions that throw HttpException.
Matcher get throwsClientException =>
throwsA(new isInstanceOf<ClientException>());
/// A matcher for RedirectLimitExceededExceptions.
const isRedirectLimitExceededException =
const _RedirectLimitExceededException();
/// A matcher for functions that throw RedirectLimitExceededException.
const Matcher throwsRedirectLimitExceededException =
const Throws(isRedirectLimitExceededException);
class _RedirectLimitExceededException extends TypeMatcher {
const _RedirectLimitExceededException() :
super("RedirectLimitExceededException");
bool matches(item, Map matchState) =>
item is RedirectException && item.message == "Redirect limit exceeded";
}
/// A matcher for SocketExceptions.
const isSocketException = const _SocketException();
/// A matcher for functions that throw SocketException.
const Matcher throwsSocketException =
const Throws(isSocketException);
class _SocketException extends TypeMatcher {
const _SocketException() : super("SocketException");
bool matches(item, Map matchState) => item is SocketException;
}

View file

@ -1,64 +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.
library mock_client_test;
import 'dart:async';
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:http/src/utils.dart';
import 'package:http/testing.dart';
import 'package:unittest/unittest.dart';
import 'utils.dart';
void main() {
test('handles a request', () {
var client = new MockClient((request) {
return new Future.value(new http.Response(
JSON.encode(request.bodyFields), 200,
request: request, headers: {'content-type': 'application/json'}));
});
expect(client.post("http://example.com/foo", body: {
'field1': 'value1',
'field2': 'value2'
}).then((response) => response.body), completion(parse(equals({
'field1': 'value1',
'field2': 'value2'
}))));
});
test('handles a streamed request', () {
var client = new MockClient.streaming((request, bodyStream) {
return bodyStream.bytesToString().then((bodyString) {
var controller = new StreamController<List<int>>(sync: true);
async.then((_) {
controller.add('Request body was "$bodyString"'.codeUnits);
controller.close();
});
return new http.StreamedResponse(controller.stream, 200);
});
});
var uri = Uri.parse("http://example.com/foo");
var request = new http.Request("POST", uri);
request.body = "hello, world";
var future = client.send(request)
.then(http.Response.fromStream)
.then((response) => response.body);
expect(future, completion(equals('Request body was "hello, world"')));
});
test('handles a request with no body', () {
var client = new MockClient((request) {
return new Future.value(new http.Response('you did it', 200));
});
expect(client.read("http://example.com/foo"),
completion(equals('you did it')));
});
}

View file

@ -1,235 +0,0 @@
// 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.
library multipart_test;
import 'dart:async';
import 'package:http/http.dart' as http;
import 'package:http_parser/http_parser.dart';
import 'package:unittest/unittest.dart';
import 'utils.dart';
void main() {
test('empty', () {
var request = new http.MultipartRequest('POST', dummyUrl);
expect(request, bodyMatches('''
--{{boundary}}--
'''));
});
test('with fields and files', () {
var request = new http.MultipartRequest('POST', dummyUrl);
request.fields['field1'] = 'value1';
request.fields['field2'] = 'value2';
request.files.add(new http.MultipartFile.fromString("file1", "contents1",
filename: "filename1.txt"));
request.files.add(new http.MultipartFile.fromString("file2", "contents2"));
expect(request, bodyMatches('''
--{{boundary}}
content-disposition: form-data; name="field1"
value1
--{{boundary}}
content-disposition: form-data; name="field2"
value2
--{{boundary}}
content-type: text/plain; charset=utf-8
content-disposition: form-data; name="file1"; filename="filename1.txt"
contents1
--{{boundary}}
content-type: text/plain; charset=utf-8
content-disposition: form-data; name="file2"
contents2
--{{boundary}}--
'''));
});
test('with a unicode field name', () {
var request = new http.MultipartRequest('POST', dummyUrl);
request.fields['fïēld'] = 'value';
expect(request, bodyMatches('''
--{{boundary}}
content-disposition: form-data; name="fïēld"
value
--{{boundary}}--
'''));
});
test('with a field name with newlines', () {
var request = new http.MultipartRequest('POST', dummyUrl);
request.fields['foo\nbar\rbaz\r\nbang'] = 'value';
expect(request, bodyMatches('''
--{{boundary}}
content-disposition: form-data; name="foo%0D%0Abar%0D%0Abaz%0D%0Abang"
value
--{{boundary}}--
'''));
});
test('with a field name with a quote', () {
var request = new http.MultipartRequest('POST', dummyUrl);
request.fields['foo"bar'] = 'value';
expect(request, bodyMatches('''
--{{boundary}}
content-disposition: form-data; name="foo%22bar"
value
--{{boundary}}--
'''));
});
test('with a unicode field value', () {
var request = new http.MultipartRequest('POST', dummyUrl);
request.fields['field'] = 'vⱥlūe';
expect(request, bodyMatches('''
--{{boundary}}
content-disposition: form-data; name="field"
content-type: text/plain; charset=utf-8
vⱥlūe
--{{boundary}}--
'''));
});
test('with a unicode filename', () {
var request = new http.MultipartRequest('POST', dummyUrl);
request.files.add(new http.MultipartFile.fromString('file', 'contents',
filename: 'fïlēname.txt'));
expect(request, bodyMatches('''
--{{boundary}}
content-type: text/plain; charset=utf-8
content-disposition: form-data; name="file"; filename="fïlēname.txt"
contents
--{{boundary}}--
'''));
});
test('with a filename with newlines', () {
var request = new http.MultipartRequest('POST', dummyUrl);
request.files.add(new http.MultipartFile.fromString('file', 'contents',
filename: 'foo\nbar\rbaz\r\nbang'));
expect(request, bodyMatches('''
--{{boundary}}
content-type: text/plain; charset=utf-8
content-disposition: form-data; name="file"; filename="foo%0D%0Abar%0D%0Abaz%0D%0Abang"
contents
--{{boundary}}--
'''));
});
test('with a filename with a quote', () {
var request = new http.MultipartRequest('POST', dummyUrl);
request.files.add(new http.MultipartFile.fromString('file', 'contents',
filename: 'foo"bar'));
expect(request, bodyMatches('''
--{{boundary}}
content-type: text/plain; charset=utf-8
content-disposition: form-data; name="file"; filename="foo%22bar"
contents
--{{boundary}}--
'''));
});
test('with a string file with a content-type but no charset', () {
var request = new http.MultipartRequest('POST', dummyUrl);
var file = new http.MultipartFile.fromString('file', '{"hello": "world"}',
contentType: new MediaType('application', 'json'));
request.files.add(file);
expect(request, bodyMatches('''
--{{boundary}}
content-type: application/json; charset=utf-8
content-disposition: form-data; name="file"
{"hello": "world"}
--{{boundary}}--
'''));
});
test('with a file with a iso-8859-1 body', () {
var request = new http.MultipartRequest('POST', dummyUrl);
// "Ã¥" encoded as ISO-8859-1 and then read as UTF-8 results in "å".
var file = new http.MultipartFile.fromString('file', 'non-ascii: "Ã¥"',
contentType: new MediaType('text', 'plain', {'charset': 'iso-8859-1'}));
request.files.add(file);
expect(request, bodyMatches('''
--{{boundary}}
content-type: text/plain; charset=iso-8859-1
content-disposition: form-data; name="file"
non-ascii: "å"
--{{boundary}}--
'''));
});
test('with a stream file', () {
var request = new http.MultipartRequest('POST', dummyUrl);
var controller = new StreamController(sync: true);
request.files.add(new http.MultipartFile('file', controller.stream, 5));
expect(request, bodyMatches('''
--{{boundary}}
content-type: application/octet-stream
content-disposition: form-data; name="file"
hello
--{{boundary}}--
'''));
controller.add([104, 101, 108, 108, 111]);
controller.close();
});
test('with an empty stream file', () {
var request = new http.MultipartRequest('POST', dummyUrl);
var controller = new StreamController(sync: true);
request.files.add(new http.MultipartFile('file', controller.stream, 0));
expect(request, bodyMatches('''
--{{boundary}}
content-type: application/octet-stream
content-disposition: form-data; name="file"
--{{boundary}}--
'''));
controller.close();
});
test('with a byte file', () {
var request = new http.MultipartRequest('POST', dummyUrl);
var file = new http.MultipartFile.fromBytes(
'file', [104, 101, 108, 108, 111]);
request.files.add(file);
expect(request, bodyMatches('''
--{{boundary}}
content-type: application/octet-stream
content-disposition: form-data; name="file"
hello
--{{boundary}}--
'''));
});
}

View file

@ -1,356 +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.
library request_test;
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:unittest/unittest.dart';
import 'utils.dart';
void main() {
group('#contentLength', () {
test('is computed from bodyBytes', () {
var request = new http.Request('POST', dummyUrl);
request.bodyBytes = [1, 2, 3, 4, 5];
expect(request.contentLength, equals(5));
request.bodyBytes = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
expect(request.contentLength, equals(10));
});
test('is computed from body', () {
var request = new http.Request('POST', dummyUrl);
request.body = "hello";
expect(request.contentLength, equals(5));
request.body = "hello, world";
expect(request.contentLength, equals(12));
});
test('is not directly mutable', () {
var request = new http.Request('POST', dummyUrl);
expect(() => request.contentLength = 50, throwsUnsupportedError);
});
});
group('#encoding', () {
test('defaults to utf-8', () {
var request = new http.Request('POST', dummyUrl);
expect(request.encoding.name, equals(UTF8.name));
});
test('can be set', () {
var request = new http.Request('POST', dummyUrl);
request.encoding = LATIN1;
expect(request.encoding.name, equals(LATIN1.name));
});
test('is based on the content-type charset if it exists', () {
var request = new http.Request('POST', dummyUrl);
request.headers['Content-Type'] = 'text/plain; charset=iso-8859-1';
expect(request.encoding.name, equals(LATIN1.name));
});
test('remains the default if the content-type charset is set and unset',
() {
var request = new http.Request('POST', dummyUrl);
request.encoding = LATIN1;
request.headers['Content-Type'] = 'text/plain; charset=utf-8';
expect(request.encoding.name, equals(UTF8.name));
request.headers.remove('Content-Type');
expect(request.encoding.name, equals(LATIN1.name));
});
test('throws an error if the content-type charset is unknown', () {
var request = new http.Request('POST', dummyUrl);
request.headers['Content-Type'] =
'text/plain; charset=not-a-real-charset';
expect(() => request.encoding, throwsFormatException);
});
});
group('#bodyBytes', () {
test('defaults to empty', () {
var request = new http.Request('POST', dummyUrl);
expect(request.bodyBytes, isEmpty);
});
test('can be set', () {
var request = new http.Request('POST', dummyUrl);
request.bodyBytes = [104, 101, 108, 108, 111];
expect(request.bodyBytes, equals([104, 101, 108, 108, 111]));
});
test('changes when body changes', () {
var request = new http.Request('POST', dummyUrl);
request.body = "hello";
expect(request.bodyBytes, equals([104, 101, 108, 108, 111]));
});
});
group('#body', () {
test('defaults to empty', () {
var request = new http.Request('POST', dummyUrl);
expect(request.body, isEmpty);
});
test('can be set', () {
var request = new http.Request('POST', dummyUrl);
request.body = "hello";
expect(request.body, equals("hello"));
});
test('changes when bodyBytes changes', () {
var request = new http.Request('POST', dummyUrl);
request.bodyBytes = [104, 101, 108, 108, 111];
expect(request.body, equals("hello"));
});
test('is encoded according to the given encoding', () {
var request = new http.Request('POST', dummyUrl);
request.encoding = LATIN1;
request.body = "föøbãr";
expect(request.bodyBytes, equals([102, 246, 248, 98, 227, 114]));
});
test('is decoded according to the given encoding', () {
var request = new http.Request('POST', dummyUrl);
request.encoding = LATIN1;
request.bodyBytes = [102, 246, 248, 98, 227, 114];
expect(request.body, equals("föøbãr"));
});
});
group('#bodyFields', () {
test("can't be read without setting the content-type", () {
var request = new http.Request('POST', dummyUrl);
expect(() => request.bodyFields, throwsStateError);
});
test("can't be read with the wrong content-type", () {
var request = new http.Request('POST', dummyUrl);
request.headers['Content-Type'] = 'text/plain';
expect(() => request.bodyFields, throwsStateError);
});
test("can't be set with the wrong content-type", () {
var request = new http.Request('POST', dummyUrl);
request.headers['Content-Type'] = 'text/plain';
expect(() => request.bodyFields = {}, throwsStateError);
});
test('defaults to empty', () {
var request = new http.Request('POST', dummyUrl);
request.headers['Content-Type'] =
'application/x-www-form-urlencoded';
expect(request.bodyFields, isEmpty);
});
test('can be set with no content-type', () {
var request = new http.Request('POST', dummyUrl);
request.bodyFields = {'hello': 'world'};
expect(request.bodyFields, equals({'hello': 'world'}));
});
test('changes when body changes', () {
var request = new http.Request('POST', dummyUrl);
request.headers['Content-Type'] =
'application/x-www-form-urlencoded';
request.body = 'key%201=value&key+2=other%2bvalue';
expect(request.bodyFields,
equals({'key 1': 'value', 'key 2': 'other+value'}));
});
test('is encoded according to the given encoding', () {
var request = new http.Request('POST', dummyUrl);
request.headers['Content-Type'] =
'application/x-www-form-urlencoded';
request.encoding = LATIN1;
request.bodyFields = {"föø": "bãr"};
expect(request.body, equals('f%F6%F8=b%E3r'));
});
test('is decoded according to the given encoding', () {
var request = new http.Request('POST', dummyUrl);
request.headers['Content-Type'] =
'application/x-www-form-urlencoded';
request.encoding = LATIN1;
request.body = 'f%F6%F8=b%E3r';
expect(request.bodyFields, equals({"föø": "bãr"}));
});
});
group('content-type header', () {
test('defaults to empty', () {
var request = new http.Request('POST', dummyUrl);
expect(request.headers['Content-Type'], isNull);
});
test('defaults to empty if only encoding is set', () {
var request = new http.Request('POST', dummyUrl);
request.encoding = LATIN1;
expect(request.headers['Content-Type'], isNull);
});
test('name is case insensitive', () {
var request = new http.Request('POST', dummyUrl);
request.headers['CoNtEnT-tYpE'] = 'application/json';
expect(request.headers,
containsPair('content-type', 'application/json'));
});
test('is set to application/x-www-form-urlencoded with charset utf-8 if '
'bodyFields is set', () {
var request = new http.Request('POST', dummyUrl);
request.bodyFields = {'hello': 'world'};
expect(request.headers['Content-Type'],
equals('application/x-www-form-urlencoded; charset=utf-8'));
});
test('is set to application/x-www-form-urlencoded with the given charset '
'if bodyFields and encoding are set', () {
var request = new http.Request('POST', dummyUrl);
request.encoding = LATIN1;
request.bodyFields = {'hello': 'world'};
expect(request.headers['Content-Type'],
equals('application/x-www-form-urlencoded; charset=iso-8859-1'));
});
test('is set to text/plain and the given encoding if body and encoding are '
'both set', () {
var request = new http.Request('POST', dummyUrl);
request.encoding = LATIN1;
request.body = 'hello, world';
expect(request.headers['Content-Type'],
equals('text/plain; charset=iso-8859-1'));
});
test('is modified to include utf-8 if body is set', () {
var request = new http.Request('POST', dummyUrl);
request.headers['Content-Type'] = 'application/json';
request.body = '{"hello": "world"}';
expect(request.headers['Content-Type'],
equals('application/json; charset=utf-8'));
});
test('is modified to include the given encoding if encoding is set', () {
var request = new http.Request('POST', dummyUrl);
request.headers['Content-Type'] = 'application/json';
request.encoding = LATIN1;
expect(request.headers['Content-Type'],
equals('application/json; charset=iso-8859-1'));
});
test('has its charset overridden by an explicit encoding', () {
var request = new http.Request('POST', dummyUrl);
request.headers['Content-Type'] =
'application/json; charset=utf-8';
request.encoding = LATIN1;
expect(request.headers['Content-Type'],
equals('application/json; charset=iso-8859-1'));
});
test("doen't have its charset overridden by setting bodyFields", () {
var request = new http.Request('POST', dummyUrl);
request.headers['Content-Type'] =
'application/x-www-form-urlencoded; charset=iso-8859-1';
request.bodyFields = {'hello': 'world'};
expect(request.headers['Content-Type'],
equals('application/x-www-form-urlencoded; charset=iso-8859-1'));
});
test("doen't have its charset overridden by setting body", () {
var request = new http.Request('POST', dummyUrl);
request.headers['Content-Type'] =
'application/json; charset=iso-8859-1';
request.body = '{"hello": "world"}';
expect(request.headers['Content-Type'],
equals('application/json; charset=iso-8859-1'));
});
});
group('#finalize', () {
test('returns a stream that emits the request body', () {
var request = new http.Request('POST', dummyUrl);
request.body = "Hello, world!";
expect(request.finalize().bytesToString(),
completion(equals("Hello, world!")));
});
test('freezes #persistentConnection', () {
var request = new http.Request('POST', dummyUrl);
request.finalize();
expect(request.persistentConnection, isTrue);
expect(() => request.persistentConnection = false, throwsStateError);
});
test('freezes #followRedirects', () {
var request = new http.Request('POST', dummyUrl);
request.finalize();
expect(request.followRedirects, isTrue);
expect(() => request.followRedirects = false, throwsStateError);
});
test('freezes #maxRedirects', () {
var request = new http.Request('POST', dummyUrl);
request.finalize();
expect(request.maxRedirects, equals(5));
expect(() => request.maxRedirects = 10, throwsStateError);
});
test('freezes #encoding', () {
var request = new http.Request('POST', dummyUrl);
request.finalize();
expect(request.encoding.name, equals(UTF8.name));
expect(() => request.encoding = ASCII, throwsStateError);
});
test('freezes #bodyBytes', () {
var request = new http.Request('POST', dummyUrl);
request.bodyBytes = [1, 2, 3];
request.finalize();
expect(request.bodyBytes, equals([1, 2, 3]));
expect(() => request.bodyBytes = [4, 5, 6], throwsStateError);
});
test('freezes #body', () {
var request = new http.Request('POST', dummyUrl);
request.body = "hello";
request.finalize();
expect(request.body, equals("hello"));
expect(() => request.body = "goodbye", throwsStateError);
});
test('freezes #bodyFields', () {
var request = new http.Request('POST', dummyUrl);
request.bodyFields = {"hello": "world"};
request.finalize();
expect(request.bodyFields, equals({"hello": "world"}));
expect(() => request.bodyFields = {}, throwsStateError);
});
test("can't be called twice", () {
var request = new http.Request('POST', dummyUrl);
request.finalize();
expect(request.finalize, throwsStateError);
});
});
group('#toString()', () {
test('includes the method and URL', () {
var request = new http.Request('POST', dummyUrl);
expect(request.toString(), 'POST $dummyUrl');
});
});
}

View file

@ -1,77 +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.
library response_test;
import 'dart:async';
import 'package:http/http.dart' as http;
import 'package:unittest/unittest.dart';
void main() {
group('()', () {
test('sets body', () {
var response = new http.Response("Hello, world!", 200);
expect(response.body, equals("Hello, world!"));
});
test('sets bodyBytes', () {
var response = new http.Response("Hello, world!", 200);
expect(response.bodyBytes, equals(
[72, 101, 108, 108, 111, 44, 32, 119, 111, 114, 108, 100, 33]));
});
test('respects the inferred encoding', () {
var response = new http.Response("föøbãr", 200,
headers: {'content-type': 'text/plain; charset=iso-8859-1'});
expect(response.bodyBytes, equals(
[102, 246, 248, 98, 227, 114]));
});
});
group('.bytes()', () {
test('sets body', () {
var response = new http.Response.bytes([104, 101, 108, 108, 111], 200);
expect(response.body, equals("hello"));
});
test('sets bodyBytes', () {
var response = new http.Response.bytes([104, 101, 108, 108, 111], 200);
expect(response.bodyBytes, equals([104, 101, 108, 108, 111]));
});
test('respects the inferred encoding', () {
var response = new http.Response.bytes([102, 246, 248, 98, 227, 114], 200,
headers: {'content-type': 'text/plain; charset=iso-8859-1'});
expect(response.body, equals("föøbãr"));
});
});
group('.fromStream()', () {
test('sets body', () {
var controller = new StreamController(sync: true);
var streamResponse = new http.StreamedResponse(
controller.stream, 200, contentLength: 13);
var future = http.Response.fromStream(streamResponse)
.then((response) => response.body);
expect(future, completion(equals("Hello, world!")));
controller.add([72, 101, 108, 108, 111, 44, 32]);
controller.add([119, 111, 114, 108, 100, 33]);
controller.close();
});
test('sets bodyBytes', () {
var controller = new StreamController(sync: true);
var streamResponse = new http.StreamedResponse(
controller.stream, 200, contentLength: 5);
var future = http.Response.fromStream(streamResponse)
.then((response) => response.bodyBytes);
expect(future, completion(equals([104, 101, 108, 108, 111])));
controller.add([104, 101, 108, 108, 111]);
controller.close();
});
});
}

View file

@ -1,30 +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.
library streamed_request_test;
import 'package:http/http.dart' as http;
import 'package:unittest/unittest.dart';
import 'utils.dart';
void main() {
group('contentLength', () {
test('defaults to null', () {
var request = new http.StreamedRequest('POST', dummyUrl);
expect(request.contentLength, isNull);
});
test('disallows negative values', () {
var request = new http.StreamedRequest('POST', dummyUrl);
expect(() => request.contentLength = -1, throwsArgumentError);
});
test('is frozen by finalize()', () {
var request = new http.StreamedRequest('POST', dummyUrl);
request.finalize();
expect(() => request.contentLength = 10, throwsStateError);
});
});
}

View file

@ -1,118 +0,0 @@
// 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.
library test_utils;
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:http_parser/http_parser.dart';
import 'package:unittest/unittest.dart';
/// A dummy URL for constructing requests that won't be sent.
Uri get dummyUrl => Uri.parse('http://dartlang.org/');
/// Removes eight spaces of leading indentation from a multiline string.
///
/// Note that this is very sensitive to how the literals are styled. They should
/// be:
/// '''
/// Text starts on own line. Lines up with subsequent lines.
/// Lines are indented exactly 8 characters from the left margin.
/// Close is on the same line.'''
///
/// This does nothing if text is only a single line.
// TODO(nweiz): Make this auto-detect the indentation level from the first
// non-whitespace line.
String cleanUpLiteral(String text) {
var lines = text.split('\n');
if (lines.length <= 1) return text;
for (var j = 0; j < lines.length; j++) {
if (lines[j].length > 8) {
lines[j] = lines[j].substring(8, lines[j].length);
} else {
lines[j] = '';
}
}
return lines.join('\n');
}
/// A matcher that matches JSON that parses to a value that matches the inner
/// matcher.
Matcher parse(matcher) => new _Parse(matcher);
class _Parse extends Matcher {
final Matcher _matcher;
_Parse(this._matcher);
bool matches(item, Map matchState) {
if (item is! String) return false;
var parsed;
try {
parsed = JSON.decode(item);
} catch (e) {
return false;
}
return _matcher.matches(parsed, matchState);
}
Description describe(Description description) {
return description.add('parses to a value that ')
.addDescriptionOf(_matcher);
}
}
/// A matcher that validates the body of a multipart request after finalization.
/// The string "{{boundary}}" in [pattern] will be replaced by the boundary
/// string for the request, and LF newlines will be replaced with CRLF.
/// Indentation will be normalized.
Matcher bodyMatches(String pattern) => new _BodyMatches(pattern);
class _BodyMatches extends Matcher {
final String _pattern;
_BodyMatches(this._pattern);
bool matches(item, Map matchState) {
if (item is! http.MultipartRequest) return false;
var future = item.finalize().toBytes().then((bodyBytes) {
var body = UTF8.decode(bodyBytes);
var contentType = new MediaType.parse(item.headers['content-type']);
var boundary = contentType.parameters['boundary'];
var expected = cleanUpLiteral(_pattern)
.replaceAll("\n", "\r\n")
.replaceAll("{{boundary}}", boundary);
expect(body, equals(expected));
expect(item.contentLength, equals(bodyBytes.length));
});
return completes.matches(future, matchState);
}
Description describe(Description description) {
return description.add('has a body that matches "$_pattern"');
}
}
/// A matcher that matches a [http.ClientException] with the given [message].
///
/// [message] can be a String or a [Matcher].
Matcher isClientException(message) => predicate((error) {
expect(error, new isInstanceOf<http.ClientException>());
expect(error.message, message);
return true;
});
/// A matcher that matches function or future that throws a
/// [http.ClientException] with the given [message].
///
/// [message] can be a String or a [Matcher].
Matcher throwsClientException(message) => throwsA(isClientException(message));

View file

@ -33,54 +33,6 @@
],
},
],
},
{
'target_name': 'pub_packages',
'type': 'none',
'dependencies': [
'pkg_files.gyp:http_files_stamp',
],
'actions': [
{
'action_name': 'remove_html_imports',
'inputs': [
'../tools/remove_html_imports.py',
'<(SHARED_INTERMEDIATE_DIR)/http_files.stamp',
],
'outputs': [
'<(SHARED_INTERMEDIATE_DIR)/remove_html_imports/http/lib/http.dart',
],
'action': [
'python', '../tools/remove_html_imports.py',
'http/lib',
'<(SHARED_INTERMEDIATE_DIR)/remove_html_imports/http/lib',
],
},
{
'action_name': 'make_pub_packages',
'inputs': [
'../tools/make_links.py',
'<!@(["python", "../tools/list_pkg_directories.py", '
'"--exclude=http", "."])',
'<!@(["python", "../tools/list_pkg_directories.py", '
'"third_party"])',
'<!@(["python", "../tools/list_pkg_directories.py", '
'"../third_party/pkg"])',
'../sdk/lib/_internal',
'<(SHARED_INTERMEDIATE_DIR)/remove_html_imports/http/lib/http.dart',
],
'outputs': [
'<(SHARED_INTERMEDIATE_DIR)/pub_packages.stamp',
],
'action': [
'python', '../tools/make_links.py',
'--timestamp_file=<(SHARED_INTERMEDIATE_DIR)/pub_packages.stamp',
'<(PRODUCT_DIR)/pub_packages',
'<@(_inputs)',
'<(SHARED_INTERMEDIATE_DIR)/remove_html_imports/http/lib',
],
},
],
}
],
}

View file

@ -44,25 +44,5 @@
},
],
},
{
'target_name': 'http_files_stamp',
'type': 'none',
'actions': [
{
'action_name': 'make_http_files_stamp',
'inputs': [
'../tools/create_timestamp_file.py',
'<!@(["python", "../tools/list_files.py", "\\.dart$", "http/lib"])',
],
'outputs': [
'<(SHARED_INTERMEDIATE_DIR)/http_files.stamp',
],
'action': [
'python', '../tools/create_timestamp_file.py',
'<@(_outputs)',
],
},
],
},
],
}

View file

@ -54,7 +54,7 @@ fi
# Use the Dart binary in the built SDK so pub can find the version file next
# to it.
DART="$BUILD_DIR/dart-sdk/bin/dart"
PACKAGES_DIR="$BUILD_DIR/pub_packages/"
PACKAGES_DIR="$BUILD_DIR/packages/"
# Run the async/await compiled pub.
PUB="$SDK_DIR/lib/_internal/pub_generated/bin/pub.dart"

View file

@ -1,39 +0,0 @@
#!/usr/bin/env python
# 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.
'''Tool for removing dart:html and related imports from a library.
Copy SOURCE to TARGET, removing any lines that import dart:html.
Usage:
python tools/remove_html_imports.py SOURCE TARGET
'''
import os
import re
import shutil
import sys
HTML_IMPORT = re.compile(r'''^import ["']dart:(html|html_common|indexed_db'''
r'''|js|svg|web_(audio|gl|sql))["'];$''',
flags=re.MULTILINE)
def main(argv):
source = argv[1]
target = argv[2]
shutil.rmtree(target)
shutil.copytree(source, target, ignore=shutil.ignore_patterns('.svn'))
for root, subFolders, files in os.walk(target):
for path in files:
if not path.endswith('.dart'): next
with open(os.path.join(root, path), 'r+') as f:
contents = f.read()
f.seek(0)
f.truncate()
f.write(HTML_IMPORT.sub(r'// import "dart:\1";', contents))
if __name__ == '__main__':
sys.exit(main(sys.argv))

View file

@ -9,7 +9,7 @@
'type': 'none',
'dependencies': [
'../../runtime/dart-runtime.gyp:dart',
'../../pkg/pkg.gyp:pub_packages',
'../../pkg/pkg.gyp:pkg_packages',
'../../pkg/pkg_files.gyp:pkg_files_stamp',
'../../utils/compiler/compiler.gyp:dart2js_files_stamp',
'pub_files_stamp'
@ -23,14 +23,14 @@
'<(SHARED_INTERMEDIATE_DIR)/pub_files.stamp',
'<(SHARED_INTERMEDIATE_DIR)/dart2js_files.stamp',
'<(SHARED_INTERMEDIATE_DIR)/pkg_files.stamp',
'<(SHARED_INTERMEDIATE_DIR)/pub_packages.stamp',
'<(SHARED_INTERMEDIATE_DIR)/packages.stamp',
],
'outputs': [
'<(SHARED_INTERMEDIATE_DIR)/pub.dart.snapshot',
],
'action': [
'<(PRODUCT_DIR)/<(EXECUTABLE_PREFIX)dart<(EXECUTABLE_SUFFIX)',
'--package-root=<(PRODUCT_DIR)/pub_packages/',
'--package-root=<(PRODUCT_DIR)/packages/',
'--snapshot=<(SHARED_INTERMEDIATE_DIR)/pub.dart.snapshot',
'../../sdk/lib/_internal/pub_generated/bin/pub.dart',
]