Skip to content

Commit e5fee5a

Browse files
committed
Tools have been added to handle progress events, covering both download and upload.
1 parent cd748b6 commit e5fee5a

22 files changed

+1107
-53
lines changed

pkgs/http/example/progress.dart

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import 'package:http/http.dart';
2+
3+
void main() async {
4+
var url = Uri.parse(
5+
'https://archive.org/download/robinson-crusoe-daniel-defoe/Robinson%20Crusoe_Daniel%20Defoe.pdf');
6+
7+
final progress = HttpProgress.withRecorder(print);
8+
9+
var request = await get(url, downloadProgress: progress);
10+
11+
print('Response status: ${request.statusCode}');
12+
}

pkgs/http/lib/http.dart

Lines changed: 59 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import 'dart:typed_data';
1010

1111
import 'src/client.dart';
1212
import 'src/exception.dart';
13+
import 'src/progress.dart';
1314
import 'src/request.dart';
1415
import 'src/response.dart';
1516
import 'src/streamed_request.dart';
@@ -22,6 +23,8 @@ export 'src/client.dart' hide zoneClient;
2223
export 'src/exception.dart';
2324
export 'src/multipart_file.dart';
2425
export 'src/multipart_request.dart';
26+
export 'src/progress.dart'
27+
hide addTransfer, getProgressTransformer, setLength, setTransferred;
2528
export 'src/request.dart';
2629
export 'src/response.dart';
2730
export 'src/streamed_request.dart';
@@ -44,8 +47,10 @@ Future<Response> head(Uri url, {Map<String, String>? headers}) =>
4447
/// the same server, you should use a single [Client] for all of those requests.
4548
///
4649
/// For more fine-grained control over the request, use [Request] instead.
47-
Future<Response> get(Uri url, {Map<String, String>? headers}) =>
48-
_withClient((client) => client.get(url, headers: headers));
50+
Future<Response> get(Uri url,
51+
{Map<String, String>? headers, HttpProgress? downloadProgress}) =>
52+
_withClient((client) =>
53+
client.get(url, headers: headers, downloadProgress: downloadProgress));
4954

5055
/// Sends an HTTP POST request with the given headers and body to the given URL.
5156
///
@@ -66,9 +71,17 @@ Future<Response> get(Uri url, {Map<String, String>? headers}) =>
6671
/// For more fine-grained control over the request, use [Request] or
6772
/// [StreamedRequest] instead.
6873
Future<Response> post(Uri url,
69-
{Map<String, String>? headers, Object? body, Encoding? encoding}) =>
70-
_withClient((client) =>
71-
client.post(url, headers: headers, body: body, encoding: encoding));
74+
{Map<String, String>? headers,
75+
Object? body,
76+
Encoding? encoding,
77+
HttpProgress? downloadProgress,
78+
HttpProgress? uploadProgress}) =>
79+
_withClient((client) => client.post(url,
80+
headers: headers,
81+
body: body,
82+
encoding: encoding,
83+
downloadProgress: downloadProgress,
84+
uploadProgress: uploadProgress));
7285

7386
/// Sends an HTTP PUT request with the given headers and body to the given URL.
7487
///
@@ -89,9 +102,17 @@ Future<Response> post(Uri url,
89102
/// For more fine-grained control over the request, use [Request] or
90103
/// [StreamedRequest] instead.
91104
Future<Response> put(Uri url,
92-
{Map<String, String>? headers, Object? body, Encoding? encoding}) =>
93-
_withClient((client) =>
94-
client.put(url, headers: headers, body: body, encoding: encoding));
105+
{Map<String, String>? headers,
106+
Object? body,
107+
Encoding? encoding,
108+
HttpProgress? downloadProgress,
109+
HttpProgress? uploadProgress}) =>
110+
_withClient((client) => client.put(url,
111+
headers: headers,
112+
body: body,
113+
encoding: encoding,
114+
downloadProgress: downloadProgress,
115+
uploadProgress: uploadProgress));
95116

96117
/// Sends an HTTP PATCH request with the given headers and body to the given
97118
/// URL.
@@ -113,9 +134,17 @@ Future<Response> put(Uri url,
113134
/// For more fine-grained control over the request, use [Request] or
114135
/// [StreamedRequest] instead.
115136
Future<Response> patch(Uri url,
116-
{Map<String, String>? headers, Object? body, Encoding? encoding}) =>
117-
_withClient((client) =>
118-
client.patch(url, headers: headers, body: body, encoding: encoding));
137+
{Map<String, String>? headers,
138+
Object? body,
139+
Encoding? encoding,
140+
HttpProgress? downloadProgress,
141+
HttpProgress? uploadProgress}) =>
142+
_withClient((client) => client.patch(url,
143+
headers: headers,
144+
body: body,
145+
encoding: encoding,
146+
downloadProgress: downloadProgress,
147+
uploadProgress: uploadProgress));
119148

120149
/// Sends an HTTP DELETE request with the given headers to the given URL.
121150
///
@@ -125,9 +154,17 @@ Future<Response> patch(Uri url,
125154
///
126155
/// For more fine-grained control over the request, use [Request] instead.
127156
Future<Response> delete(Uri url,
128-
{Map<String, String>? headers, Object? body, Encoding? encoding}) =>
129-
_withClient((client) =>
130-
client.delete(url, headers: headers, body: body, encoding: encoding));
157+
{Map<String, String>? headers,
158+
Object? body,
159+
Encoding? encoding,
160+
HttpProgress? downloadProgress,
161+
HttpProgress? uploadProgress}) =>
162+
_withClient((client) => client.delete(url,
163+
headers: headers,
164+
body: body,
165+
encoding: encoding,
166+
downloadProgress: downloadProgress,
167+
uploadProgress: uploadProgress));
131168

132169
/// Sends an HTTP GET request with the given headers to the given URL and
133170
/// returns a Future that completes to the body of the response as a [String].
@@ -141,8 +178,10 @@ Future<Response> delete(Uri url,
141178
///
142179
/// For more fine-grained control over the request and response, use [Request]
143180
/// instead.
144-
Future<String> read(Uri url, {Map<String, String>? headers}) =>
145-
_withClient((client) => client.read(url, headers: headers));
181+
Future<String> read(Uri url,
182+
{Map<String, String>? headers, HttpProgress? downloadProgress}) =>
183+
_withClient((client) =>
184+
client.read(url, headers: headers, downloadProgress: downloadProgress));
146185

147186
/// Sends an HTTP GET request with the given headers to the given URL and
148187
/// returns a Future that completes to the body of the response as a list of
@@ -157,8 +196,10 @@ Future<String> read(Uri url, {Map<String, String>? headers}) =>
157196
///
158197
/// For more fine-grained control over the request and response, use [Request]
159198
/// instead.
160-
Future<Uint8List> readBytes(Uri url, {Map<String, String>? headers}) =>
161-
_withClient((client) => client.readBytes(url, headers: headers));
199+
Future<Uint8List> readBytes(Uri url,
200+
{Map<String, String>? headers, HttpProgress? downloadProgress}) =>
201+
_withClient((client) => client.readBytes(url,
202+
headers: headers, downloadProgress: downloadProgress));
162203

163204
Future<T> _withClient<T>(Future<T> Function(Client) fn) async {
164205
var client = Client();

pkgs/http/lib/src/base_client.dart

Lines changed: 48 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import 'base_request.dart';
99
import 'byte_stream.dart';
1010
import 'client.dart';
1111
import 'exception.dart';
12+
import 'progress.dart';
1213
import 'request.dart';
1314
import 'response.dart';
1415
import 'streamed_response.dart';
@@ -23,39 +24,66 @@ abstract mixin class BaseClient implements Client {
2324
_sendUnstreamed('HEAD', url, headers);
2425

2526
@override
26-
Future<Response> get(Uri url, {Map<String, String>? headers}) =>
27-
_sendUnstreamed('GET', url, headers);
27+
Future<Response> get(Uri url,
28+
{Map<String, String>? headers,
29+
HttpProgress? downloadProgress,
30+
HttpProgress? uploadProgress}) =>
31+
_sendUnstreamed('GET', url, headers, null, null, downloadProgress);
2832

2933
@override
3034
Future<Response> post(Uri url,
31-
{Map<String, String>? headers, Object? body, Encoding? encoding}) =>
32-
_sendUnstreamed('POST', url, headers, body, encoding);
35+
{Map<String, String>? headers,
36+
Object? body,
37+
Encoding? encoding,
38+
HttpProgress? downloadProgress,
39+
HttpProgress? uploadProgress}) =>
40+
_sendUnstreamed('POST', url, headers, body, encoding, downloadProgress,
41+
uploadProgress);
3342

3443
@override
3544
Future<Response> put(Uri url,
36-
{Map<String, String>? headers, Object? body, Encoding? encoding}) =>
37-
_sendUnstreamed('PUT', url, headers, body, encoding);
45+
{Map<String, String>? headers,
46+
Object? body,
47+
Encoding? encoding,
48+
HttpProgress? downloadProgress,
49+
HttpProgress? uploadProgress}) =>
50+
_sendUnstreamed('PUT', url, headers, body, encoding, downloadProgress,
51+
uploadProgress);
3852

3953
@override
4054
Future<Response> patch(Uri url,
41-
{Map<String, String>? headers, Object? body, Encoding? encoding}) =>
42-
_sendUnstreamed('PATCH', url, headers, body, encoding);
55+
{Map<String, String>? headers,
56+
Object? body,
57+
Encoding? encoding,
58+
HttpProgress? downloadProgress,
59+
HttpProgress? uploadProgress}) =>
60+
_sendUnstreamed('PATCH', url, headers, body, encoding, downloadProgress,
61+
uploadProgress);
4362

4463
@override
4564
Future<Response> delete(Uri url,
46-
{Map<String, String>? headers, Object? body, Encoding? encoding}) =>
47-
_sendUnstreamed('DELETE', url, headers, body, encoding);
65+
{Map<String, String>? headers,
66+
Object? body,
67+
Encoding? encoding,
68+
HttpProgress? downloadProgress,
69+
HttpProgress? uploadProgress}) =>
70+
_sendUnstreamed('DELETE', url, headers, body, encoding, downloadProgress,
71+
uploadProgress);
4872

4973
@override
50-
Future<String> read(Uri url, {Map<String, String>? headers}) async {
51-
final response = await get(url, headers: headers);
74+
Future<String> read(Uri url,
75+
{Map<String, String>? headers, HttpProgress? downloadProgress}) async {
76+
final response =
77+
await get(url, headers: headers, downloadProgress: downloadProgress);
5278
_checkResponseSuccess(url, response);
5379
return response.body;
5480
}
5581

5682
@override
57-
Future<Uint8List> readBytes(Uri url, {Map<String, String>? headers}) async {
58-
final response = await get(url, headers: headers);
83+
Future<Uint8List> readBytes(Uri url,
84+
{Map<String, String>? headers, HttpProgress? downloadProgress}) async {
85+
final response =
86+
await get(url, headers: headers, downloadProgress: downloadProgress);
5987
_checkResponseSuccess(url, response);
6088
return response.bodyBytes;
6189
}
@@ -73,8 +101,12 @@ abstract mixin class BaseClient implements Client {
73101
/// Sends a non-streaming [Request] and returns a non-streaming [Response].
74102
Future<Response> _sendUnstreamed(
75103
String method, Uri url, Map<String, String>? headers,
76-
[Object? body, Encoding? encoding]) async {
77-
var request = Request(method, url);
104+
[Object? body,
105+
Encoding? encoding,
106+
HttpProgress? downloadProgress,
107+
HttpProgress? uploadProgress]) async {
108+
var request = Request(method, url,
109+
downloadProgress: downloadProgress, uploadProgress: uploadProgress);
78110

79111
if (headers != null) request.headers.addAll(headers);
80112
if (encoding != null) request.encoding = encoding;

pkgs/http/lib/src/base_request.dart

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// for details. All rights reserved. Use of this source code is governed by a
33
// BSD-style license that can be found in the LICENSE file.
44

5+
import 'dart:async';
56
import 'dart:collection';
67

78
import 'package:meta/meta.dart';
@@ -11,6 +12,7 @@ import 'base_client.dart';
1112
import 'base_response.dart';
1213
import 'byte_stream.dart';
1314
import 'client.dart';
15+
import 'progress.dart';
1416
import 'streamed_response.dart';
1517
import 'utils.dart';
1618

@@ -89,14 +91,36 @@ abstract class BaseRequest {
8991
bool _finalized = false;
9092

9193
static final _tokenRE = RegExp(r"^[\w!#%&'*+\-.^`|~]+$");
94+
9295
static String _validateMethod(String method) {
9396
if (!_tokenRE.hasMatch(method)) {
9497
throw ArgumentError.value(method, 'method', 'Not a valid method');
9598
}
9699
return method;
97100
}
98101

99-
BaseRequest(String method, this.url)
102+
/// On upload progress.
103+
///
104+
/// If defined, this [HttpProgress.handler] will be called when the upload
105+
/// progress changes.
106+
///
107+
/// To see the usage of the progress handler, see [HttpProgress].
108+
///
109+
/// To see the progress of the download, use [downloadProgress].
110+
final HttpProgress? uploadProgress;
111+
112+
/// On download progress.
113+
///
114+
/// If defined, this [HttpProgress.handler] will be called when the download
115+
/// progress changes.
116+
///
117+
/// To see the usage of the progress handler, see [HttpProgress].
118+
///
119+
/// To see the progress of the upload, use [uploadProgress].
120+
final HttpProgress? downloadProgress;
121+
122+
BaseRequest(String method, this.url,
123+
{this.uploadProgress, this.downloadProgress})
100124
: method = _validateMethod(method),
101125
headers = LinkedHashMap(
102126
equals: (key1, key2) => key1.toLowerCase() == key2.toLowerCase(),
@@ -132,6 +156,7 @@ abstract class BaseRequest {
132156
try {
133157
var response = await client.send(this);
134158
var stream = onDone(response.stream, client.close);
159+
135160
return StreamedResponse(ByteStream(stream), response.statusCode,
136161
contentLength: response.contentLength,
137162
request: response.request,

pkgs/http/lib/src/browser_client.dart

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import 'base_client.dart';
1111
import 'base_request.dart';
1212
import 'byte_stream.dart';
1313
import 'exception.dart';
14+
import 'progress.dart';
1415
import 'streamed_response.dart';
1516

1617
final _digitRegex = RegExp(r'^\d+$');
@@ -57,6 +58,7 @@ class BrowserClient extends BaseClient {
5758
}
5859
var bytes = await request.finalize().toBytes();
5960
var xhr = XMLHttpRequest();
61+
6062
_xhrs.add(xhr);
6163
xhr
6264
..open(request.method, '${request.url}', true)
@@ -68,6 +70,24 @@ class BrowserClient extends BaseClient {
6870

6971
var completer = Completer<StreamedResponse>();
7072

73+
if (request.uploadProgress != null) {
74+
setLength(request.uploadProgress!, bytes.length);
75+
const EventStreamProvider('progress')
76+
.forTarget(xhr.upload)
77+
.listen((event) {
78+
setTransferred(
79+
request.uploadProgress!, (event as ProgressEvent).loaded);
80+
});
81+
}
82+
83+
if (request.downloadProgress != null) {
84+
const EventStreamProvider('progress').forTarget(xhr).listen((event) {
85+
setLength(request.downloadProgress!,
86+
(event as ProgressEvent).lengthComputable ? event.total : null);
87+
setTransferred(request.downloadProgress!, event.loaded);
88+
});
89+
}
90+
7191
unawaited(xhr.onLoad.first.then((_) {
7292
if (xhr.responseHeaders['content-length'] case final contentLengthHeader
7393
when contentLengthHeader != null &&

pkgs/http/lib/src/byte_stream.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ final class ByteStream extends StreamView<List<int>> {
1515
factory ByteStream.fromBytes(List<int> bytes) =>
1616
ByteStream(Stream.value(bytes));
1717

18+
factory ByteStream.withTransformer(Stream<List<int>> stream,
19+
StreamTransformer<List<int>, List<int>> transformer) =>
20+
ByteStream(stream.transform(transformer));
21+
1822
/// Collects the data of this stream in a [Uint8List].
1923
Future<Uint8List> toBytes() {
2024
var completer = Completer<Uint8List>();

0 commit comments

Comments
 (0)