mirror of
https://github.com/dart-lang/sdk
synced 2024-09-05 00:13:50 +00:00
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:
parent
0935b930ea
commit
80e6b5be2c
|
@ -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.
|
|
@ -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.
|
|
@ -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
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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() {}
|
||||
}
|
|
@ -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";
|
||||
}
|
|
@ -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.");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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)}";
|
||||
}
|
||||
}
|
|
@ -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.");
|
||||
}
|
||||
}
|
|
@ -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");
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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));
|
|
@ -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';
|
|
@ -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"
|
|
@ -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();
|
||||
});
|
||||
}
|
|
@ -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])));
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
|
@ -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');
|
|
@ -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);
|
||||
});
|
||||
}
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
}
|
|
@ -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);
|
||||
});
|
||||
}
|
|
@ -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);
|
||||
});
|
||||
}
|
|
@ -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);
|
||||
});
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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')));
|
||||
});
|
||||
}
|
|
@ -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}}--
|
||||
'''));
|
||||
});
|
||||
}
|
|
@ -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');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
}
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
}
|
|
@ -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));
|
48
pkg/pkg.gyp
48
pkg/pkg.gyp
|
@ -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',
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
],
|
||||
}
|
||||
|
|
|
@ -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)',
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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))
|
|
@ -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',
|
||||
]
|
||||
|
|
Loading…
Reference in a new issue