Skip to content

Commit a4d001d

Browse files
committed
added onUploadProgress to MultipartRequest.new and Client.send. (adjusted file movements and from new fork)
1 parent 843c5ec commit a4d001d

15 files changed

+178
-16
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@
22
.dart_tool
33
.packages
44
pubspec.lock
5+
.idea

.idea/.gitignore

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkgs/http/lib/retry.dart

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import 'dart:math' as math;
88
import 'package:async/async.dart';
99

1010
import 'http.dart';
11+
import 'src/utils.dart';
1112

1213
/// An HTTP client wrapper that automatically retries failing requests.
1314
class RetryClient extends BaseClient {
@@ -101,7 +102,8 @@ class RetryClient extends BaseClient {
101102
);
102103

103104
@override
104-
Future<StreamedResponse> send(BaseRequest request) async {
105+
Future<StreamedResponse> send(BaseRequest request,
106+
{OnUploadProgress? onUploadProgress}) async {
105107
final splitter = StreamSplitter(request.finalize());
106108

107109
var i = 0;

pkgs/http/lib/src/base_client.dart

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import 'exception.dart';
1212
import 'request.dart';
1313
import 'response.dart';
1414
import 'streamed_response.dart';
15+
import 'utils.dart';
1516

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

7382
/// Sends a non-streaming [Request] and returns a non-streaming [Response].
7483
Future<Response> _sendUnstreamed(

pkgs/http/lib/src/base_request.dart

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,14 +89,26 @@ abstract class BaseRequest {
8989
bool _finalized = false;
9090

9191
static final _tokenRE = RegExp(r"^[\w!#%&'*+\-.^`|~]+$");
92+
9293
static String _validateMethod(String method) {
9394
if (!_tokenRE.hasMatch(method)) {
9495
throw ArgumentError.value(method, 'method', 'Not a valid method');
9596
}
9697
return method;
9798
}
9899

99-
BaseRequest(String method, this.url)
100+
/// On upload progress callback.
101+
///
102+
/// If defined, this callback will be called when the upload progress changes.
103+
///
104+
/// In browser, uses XMLHttpRequest's "xhr.upload.onLoad" event.
105+
///
106+
/// In IO, uses the yield length of the stream. The total length of the bytes
107+
/// yielded by the stream at any given moment is "uploaded" and the total
108+
/// length of the stream is "total"
109+
final OnUploadProgress? onUploadProgress;
110+
111+
BaseRequest(String method, this.url, {this.onUploadProgress})
100112
: method = _validateMethod(method),
101113
headers = LinkedHashMap(
102114
equals: (key1, key2) => key1.toLowerCase() == key2.toLowerCase(),
@@ -130,7 +142,8 @@ abstract class BaseRequest {
130142
var client = Client();
131143

132144
try {
133-
var response = await client.send(this);
145+
var response =
146+
await client.send(this, onUploadProgress: onUploadProgress);
134147
var stream = onDone(response.stream, client.close);
135148
return StreamedResponse(ByteStream(stream), response.statusCode,
136149
contentLength: response.contentLength,

pkgs/http/lib/src/browser_client.dart

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import 'base_request.dart';
1111
import 'byte_stream.dart';
1212
import 'exception.dart';
1313
import 'streamed_response.dart';
14+
import 'utils.dart';
1415

1516
/// Create a [BrowserClient].
1617
///
@@ -38,8 +39,16 @@ class BrowserClient extends BaseClient {
3839
bool withCredentials = false;
3940

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

5261
var completer = Completer<StreamedResponse>();
5362

63+
if (onUploadProgress != null) {
64+
xhr.upload.onLoad.listen((event) {
65+
if (event.lengthComputable) {
66+
onUploadProgress(event.total!, event.loaded!);
67+
}
68+
});
69+
}
70+
5471
unawaited(xhr.onLoad.first.then((_) {
5572
var body = (xhr.response as ByteBuffer).asUint8List();
5673
completer.complete(StreamedResponse(

pkgs/http/lib/src/client.dart

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import 'client_stub.dart'
1717
import 'exception.dart';
1818
import 'response.dart';
1919
import 'streamed_response.dart';
20+
import 'utils.dart';
2021

2122
/// The interface for HTTP clients that take care of maintaining persistent
2223
/// connections across multiple requests to the same server.
@@ -139,7 +140,11 @@ abstract class Client {
139140
Future<Uint8List> readBytes(Uri url, {Map<String, String>? headers});
140141

141142
/// Sends an HTTP request and asynchronously returns the response.
142-
Future<StreamedResponse> send(BaseRequest request);
143+
///
144+
/// If [onUploadProgress] defined, the callback will be called when the
145+
/// upload progress changes.
146+
Future<StreamedResponse> send(BaseRequest request,
147+
{OnUploadProgress? onUploadProgress});
143148

144149
/// Closes the client and cleans up any resources associated with it.
145150
///

pkgs/http/lib/src/io_client.dart

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,15 @@
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:io';
67

8+
import '../http.dart' show ByteStream;
79
import 'base_client.dart';
810
import 'base_request.dart';
911
import 'exception.dart';
1012
import 'io_streamed_response.dart';
13+
import 'utils.dart';
1114

1215
/// Create an [IOClient].
1316
///
@@ -22,6 +25,7 @@ BaseClient createClient() => IOClient();
2225
class _ClientSocketException extends ClientException
2326
implements SocketException {
2427
final SocketException cause;
28+
2529
_ClientSocketException(SocketException e, Uri url)
2630
: cause = e,
2731
super(e.message, url);
@@ -44,26 +48,50 @@ class IOClient extends BaseClient {
4448
IOClient([HttpClient? inner]) : _inner = inner ?? HttpClient();
4549

4650
/// Sends an HTTP request and asynchronously returns the response.
51+
///
52+
/// If [onUploadProgress] callback is provided and length is computable,
53+
/// [onUploadProgress] will execute for each chunk was sent.
54+
///
55+
/// lengthComputable :
56+
/// library.html : xhr.lengthComputable
57+
/// library.io : content-length is provided (MultipartRequest provide)
4758
@override
48-
Future<IOStreamedResponse> send(BaseRequest request) async {
59+
Future<IOStreamedResponse> send(BaseRequest request,
60+
{OnUploadProgress? onUploadProgress}) async {
4961
if (_inner == null) {
5062
throw ClientException(
5163
'HTTP request failed. Client is already closed.', request.url);
5264
}
5365

5466
var stream = request.finalize();
5567

68+
ByteStream? handledStream;
69+
70+
var contentLength = request.contentLength;
71+
if (onUploadProgress != null && contentLength != null) {
72+
var load = 0;
73+
handledStream =
74+
ByteStream(stream.transform(StreamTransformer.fromBind((d) async* {
75+
await for (var data in d) {
76+
load += data.length;
77+
onUploadProgress(contentLength, load);
78+
yield data;
79+
}
80+
})));
81+
}
82+
5683
try {
5784
var ioRequest = (await _inner!.openUrl(request.method, request.url))
5885
..followRedirects = request.followRedirects
5986
..maxRedirects = request.maxRedirects
60-
..contentLength = (request.contentLength ?? -1)
87+
..contentLength = (contentLength ?? -1)
6188
..persistentConnection = request.persistentConnection;
6289
request.headers.forEach((name, value) {
6390
ioRequest.headers.set(name, value);
6491
});
6592

66-
var response = await stream.pipe(ioRequest) as HttpClientResponse;
93+
var response =
94+
await (handledStream ?? stream).pipe(ioRequest) as HttpClientResponse;
6795

6896
var headers = <String, String>{};
6997
response.headers.forEach((key, values) {

pkgs/http/lib/src/mock_client.dart

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,16 @@
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';
6+
57
import 'base_client.dart';
68
import 'base_request.dart';
79
import 'byte_stream.dart';
810
import 'request.dart';
911
import 'response.dart';
1012
import 'streamed_request.dart';
1113
import 'streamed_response.dart';
14+
import 'utils.dart';
1215

1316
// TODO(nweiz): once Dart has some sort of Rack- or WSGI-like standard for
1417
// server APIs, MockClient should conform to it.
@@ -65,9 +68,26 @@ class MockClient extends BaseClient {
6568
});
6669

6770
@override
68-
Future<StreamedResponse> send(BaseRequest request) async {
71+
Future<StreamedResponse> send(BaseRequest request,
72+
{OnUploadProgress? onUploadProgress}) async {
6973
var bodyStream = request.finalize();
70-
return await _handler(request, bodyStream);
74+
ByteStream? handledStream;
75+
76+
var contentLength = request.contentLength;
77+
78+
if (onUploadProgress != null && contentLength != null) {
79+
var load = 0;
80+
handledStream = ByteStream(
81+
bodyStream.transform(StreamTransformer.fromBind((d) async* {
82+
await for (var data in d) {
83+
load += data.length;
84+
onUploadProgress(contentLength, load);
85+
yield data;
86+
}
87+
})));
88+
}
89+
90+
return await _handler(request, handledStream ?? bodyStream);
7191
}
7292
}
7393

pkgs/http/lib/src/multipart_request.dart

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,14 @@ class MultipartRequest extends BaseRequest {
4545
/// The list of files to upload for this request.
4646
final files = <MultipartFile>[];
4747

48-
MultipartRequest(String method, Uri url) : super(method, url);
48+
/// If [onUploadProgress] callback is provided and length is computable,
49+
/// [onUploadProgress] will execute for each chunk was sent.
50+
///
51+
/// lengthComputable :
52+
/// library.html : xhr.lengthComputable
53+
/// library.io : content-length is provided (MultipartRequest provide)
54+
MultipartRequest(String method, Uri url, {OnUploadProgress? onUploadProgress})
55+
: super(method, url, onUploadProgress: onUploadProgress);
4956

5057
/// The total length of the request body, in bytes.
5158
///

0 commit comments

Comments
 (0)