Skip to content

added onUploadProgress to MultipartRequest #691

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.github/workflows/dart.yml linguist-generated=true
tool/ci.sh linguist-generated=true
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@
.dart_tool
.packages
pubspec.lock
.idea
104 changes: 8 additions & 96 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,102 +1,14 @@
A composable, Future-based library for making HTTP requests.

[![pub package](https://img.shields.io/pub/v/http.svg)](https://pub.dev/packages/http)
[![Build Status](https://github.com/dart-lang/http/workflows/Dart%20CI/badge.svg)](https://github.com/dart-lang/http/actions?query=workflow%3A"Dart+CI"+branch%3Amaster)

This package contains a set of high-level functions and classes that make it
A composable, Future-based library for making HTTP requests.

`package:http` contains a set of high-level functions and classes that make it
easy to consume HTTP resources. It's multi-platform, and supports mobile, desktop,
and the browser.

## Using

The easiest way to use this library is via the top-level functions. They allow
you to make individual HTTP requests with minimal hassle:

```dart
import 'package:http/http.dart' as http;

var url = Uri.parse('https://example.com/whatsit/create');
var response = await http.post(url, body: {'name': 'doodle', 'color': 'blue'});
print('Response status: ${response.statusCode}');
print('Response body: ${response.body}');

print(await http.read(Uri.parse('https://example.com/foobar.txt')));
```

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 = http.Client();
try {
var response = await client.post(
Uri.https('example.com', 'whatsit/create'),
body: {'name': 'doodle', 'color': 'blue'});
var decodedResponse = jsonDecode(utf8.decode(response.bodyBytes)) as Map;
var uri = Uri.parse(decodedResponse['uri'] as String);
print(await client.get(uri));
} finally {
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://pub.dev/documentation/http/latest/http/Request-class.html
[StreamedRequest]: https://pub.dev/documentation/http/latest/http/StreamedRequest-class.html
[Client.send]: https://pub.dev/documentation/http/latest/http/Client/send.html

This package is designed to be composable. This makes it easy for external
libraries to work with one another to add behavior to it. Libraries wishing to
add behavior should create a subclass of [BaseClient][] that wraps another
[Client][] and adds the desired behavior:

[BaseClient]: https://pub.dev/documentation/http/latest/http/BaseClient-class.html
[Client]: https://pub.dev/documentation/http/latest/http/Client-class.html

```dart
class UserAgentClient extends http.BaseClient {
final String userAgent;
final http.Client _inner;

UserAgentClient(this.userAgent, this._inner);

Future<http.StreamedResponse> send(http.BaseRequest request) {
request.headers['user-agent'] = userAgent;
return _inner.send(request);
}
}
```

## Retrying requests

`package:http/retry.dart` provides a class [`RetryClient`][RetryClient] to wrap
an underlying [`http.Client`][Client] which transparently retries failing
requests.

[RetryClient]: https://pub.dev/documentation/http/latest/retry/RetryClient-class.html
[Client]: https://pub.dev/documentation/http/latest/http/Client-class.html

```dart
import 'package:http/http.dart' as http;
import 'package:http/retry.dart';

Future<void> main() async {
final client = RetryClient(http.Client());
try {
print(await client.read(Uri.parse('http://example.org')));
} finally {
client.close();
}
}
```

By default, this retries any request whose response has status code 503
Temporary Failure up to three retries. It waits 500ms before the first retry,
and increases the delay by 1.5x each time. All of this can be customized using
the [`RetryClient()`][new RetryClient] constructor.
## Packages

[new RetryClient]: https://pub.dev/documentation/http/latest/retry/RetryClient/RetryClient.html
| Package | Description | Version |
|---|---|---|
| [http](pkgs/http/) | A composable, multi-platform, Future-based API for HTTP requests. | [![pub package](https://img.shields.io/pub/v/http.svg)](https://pub.dev/packages/http) |
| [http_client_conformance_tests](pkgs/http_client_conformance_tests/) | A library that tests whether implementations of package:http's `Client` class behave as expected. | |
3 changes: 3 additions & 0 deletions mono_repo.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
merge_stages:
- analyze_and_format
- unit_test
File renamed without changes.
27 changes: 27 additions & 0 deletions pkgs/http/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
Copyright 2014, the Dart project authors.

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 LLC 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.
102 changes: 102 additions & 0 deletions pkgs/http/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
[![pub package](https://img.shields.io/pub/v/http.svg)](https://pub.dev/packages/http)
[![package publisher](https://img.shields.io/pub/publisher/http.svg)](https://pub.dev/packages/http/publisher)

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 multi-platform, and supports mobile, desktop,
and the browser.

## Using

The easiest way to use this library is via the top-level functions. They allow
you to make individual HTTP requests with minimal hassle:

```dart
import 'package:http/http.dart' as http;

var url = Uri.https('example.com', 'whatsit/create');
var response = await http.post(url, body: {'name': 'doodle', 'color': 'blue'});
print('Response status: ${response.statusCode}');
print('Response body: ${response.body}');

print(await http.read(Uri.https('example.com', 'foobar.txt')));
```

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 = http.Client();
try {
var response = await client.post(
Uri.https('example.com', 'whatsit/create'),
body: {'name': 'doodle', 'color': 'blue'});
var decodedResponse = jsonDecode(utf8.decode(response.bodyBytes)) as Map;
var uri = Uri.parse(decodedResponse['uri'] as String);
print(await client.get(uri));
} finally {
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://pub.dev/documentation/http/latest/http/Request-class.html
[StreamedRequest]: https://pub.dev/documentation/http/latest/http/StreamedRequest-class.html
[Client.send]: https://pub.dev/documentation/http/latest/http/Client/send.html

This package is designed to be composable. This makes it easy for external
libraries to work with one another to add behavior to it. Libraries wishing to
add behavior should create a subclass of [BaseClient][] that wraps another
[Client][] and adds the desired behavior:

[BaseClient]: https://pub.dev/documentation/http/latest/http/BaseClient-class.html
[Client]: https://pub.dev/documentation/http/latest/http/Client-class.html

```dart
class UserAgentClient extends http.BaseClient {
final String userAgent;
final http.Client _inner;

UserAgentClient(this.userAgent, this._inner);

Future<http.StreamedResponse> send(http.BaseRequest request) {
request.headers['user-agent'] = userAgent;
return _inner.send(request);
}
}
```

## Retrying requests

`package:http/retry.dart` provides a class [`RetryClient`][RetryClient] to wrap
an underlying [`http.Client`][Client] which transparently retries failing
requests.

[RetryClient]: https://pub.dev/documentation/http/latest/retry/RetryClient-class.html
[Client]: https://pub.dev/documentation/http/latest/http/Client-class.html

```dart
import 'package:http/http.dart' as http;
import 'package:http/retry.dart';

Future<void> main() async {
final client = RetryClient(http.Client());
try {
print(await client.read(Uri.http('example.org', '')));
} finally {
client.close();
}
}
```

By default, this retries any request whose response has status code 503
Temporary Failure up to three retries. It waits 500ms before the first retry,
and increases the delay by 1.5x each time. All of this can be customized using
the [`RetryClient()`][new RetryClient] constructor.

[new RetryClient]: https://pub.dev/documentation/http/latest/retry/RetryClient/RetryClient.html
File renamed without changes.
File renamed without changes.
File renamed without changes.
2 changes: 1 addition & 1 deletion lib/http.dart → pkgs/http/lib/http.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ 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/client.dart' hide zoneClient;
export 'src/exception.dart';
export 'src/multipart_file.dart';
export 'src/multipart_request.dart';
Expand Down
File renamed without changes.
4 changes: 3 additions & 1 deletion lib/retry.dart → pkgs/http/lib/retry.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import 'dart:math' as math;
import 'package:async/async.dart';

import 'http.dart';
import 'src/utils.dart';

/// An HTTP client wrapper that automatically retries failing requests.
class RetryClient extends BaseClient {
Expand Down Expand Up @@ -101,7 +102,8 @@ class RetryClient extends BaseClient {
);

@override
Future<StreamedResponse> send(BaseRequest request) async {
Future<StreamedResponse> send(BaseRequest request,
{OnUploadProgress? onUploadProgress}) async {
final splitter = StreamSplitter(request.finalize());

var i = 0;
Expand Down
11 changes: 10 additions & 1 deletion lib/src/base_client.dart → pkgs/http/lib/src/base_client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import 'exception.dart';
import 'request.dart';
import 'response.dart';
import 'streamed_response.dart';
import 'utils.dart';

/// The abstract base class for an HTTP client.
///
Expand Down Expand Up @@ -67,8 +68,16 @@ abstract class BaseClient implements Client {
/// state of the stream; it could have data written to it asynchronously at a
/// later point, or it could already be closed when it's returned. Any
/// internal HTTP errors should be wrapped as [ClientException]s.
///
/// If [onUploadProgress] callback is provided and length is computable,
/// [onUploadProgress] will execute for each chunk was sent.
///
/// lengthComputable :
/// library.html : xhr.lengthComputable
/// library.io : content-length is provided (MultipartRequest provide)
@override
Future<StreamedResponse> send(BaseRequest request);
Future<StreamedResponse> send(BaseRequest request,
{OnUploadProgress? onUploadProgress});

/// Sends a non-streaming [Request] and returns a non-streaming [Response].
Future<Response> _sendUnstreamed(
Expand Down
17 changes: 15 additions & 2 deletions lib/src/base_request.dart → pkgs/http/lib/src/base_request.dart
Original file line number Diff line number Diff line change
Expand Up @@ -89,14 +89,26 @@ abstract class BaseRequest {
bool _finalized = false;

static final _tokenRE = RegExp(r"^[\w!#%&'*+\-.^`|~]+$");

static String _validateMethod(String method) {
if (!_tokenRE.hasMatch(method)) {
throw ArgumentError.value(method, 'method', 'Not a valid method');
}
return method;
}

BaseRequest(String method, this.url)
/// On upload progress callback.
///
/// If defined, this callback will be called when the upload progress changes.
///
/// In browser, uses XMLHttpRequest's "xhr.upload.onLoad" event.
///
/// In IO, uses the yield length of the stream. The total length of the bytes
/// yielded by the stream at any given moment is "uploaded" and the total
/// length of the stream is "total"
final OnUploadProgress? onUploadProgress;

BaseRequest(String method, this.url, {this.onUploadProgress})
: method = _validateMethod(method),
headers = LinkedHashMap(
equals: (key1, key2) => key1.toLowerCase() == key2.toLowerCase(),
Expand Down Expand Up @@ -130,7 +142,8 @@ abstract class BaseRequest {
var client = Client();

try {
var response = await client.send(this);
var response =
await client.send(this, onUploadProgress: onUploadProgress);
var stream = onDone(response.stream, client.close);
return StreamedResponse(ByteStream(stream), response.statusCode,
contentLength: response.contentLength,
Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import 'base_request.dart';
import 'byte_stream.dart';
import 'exception.dart';
import 'streamed_response.dart';
import 'utils.dart';

/// Create a [BrowserClient].
///
Expand Down Expand Up @@ -38,8 +39,16 @@ class BrowserClient extends BaseClient {
bool withCredentials = false;

/// Sends an HTTP request and asynchronously returns the response.
///
/// If [onUploadProgress] callback is provided and length is computable,
/// [onUploadProgress] will execute for each chunk was sent.
///
/// lengthComputable :
/// library.html : xhr.lengthComputable
/// library.io : content-length is provided (MultipartRequest provide)
@override
Future<StreamedResponse> send(BaseRequest request) async {
Future<StreamedResponse> send(BaseRequest request,
{OnUploadProgress? onUploadProgress}) async {
var bytes = await request.finalize().toBytes();
var xhr = HttpRequest();
_xhrs.add(xhr);
Expand All @@ -51,6 +60,14 @@ class BrowserClient extends BaseClient {

var completer = Completer<StreamedResponse>();

if (onUploadProgress != null) {
xhr.upload.onLoad.listen((event) {
if (event.lengthComputable) {
onUploadProgress(event.total!, event.loaded!);
}
});
}

unawaited(xhr.onLoad.first.then((_) {
var body = (xhr.response as ByteBuffer).asUint8List();
completer.complete(StreamedResponse(
Expand Down
File renamed without changes.
Loading