Skip to content

Commit

Permalink
Tools have been added to handle progress events, covering both downlo…
Browse files Browse the repository at this point in the history
…ad and upload.
  • Loading branch information
Mehmetyaz committed Dec 5, 2023
1 parent cd748b6 commit e5fee5a
Show file tree
Hide file tree
Showing 22 changed files with 1,107 additions and 53 deletions.
12 changes: 12 additions & 0 deletions pkgs/http/example/progress.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import 'package:http/http.dart';

void main() async {
var url = Uri.parse(
'https://archive.org/download/robinson-crusoe-daniel-defoe/Robinson%20Crusoe_Daniel%20Defoe.pdf');

final progress = HttpProgress.withRecorder(print);

var request = await get(url, downloadProgress: progress);

print('Response status: ${request.statusCode}');
}
77 changes: 59 additions & 18 deletions pkgs/http/lib/http.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import 'dart:typed_data';

import 'src/client.dart';
import 'src/exception.dart';
import 'src/progress.dart';
import 'src/request.dart';
import 'src/response.dart';
import 'src/streamed_request.dart';
Expand All @@ -22,6 +23,8 @@ export 'src/client.dart' hide zoneClient;
export 'src/exception.dart';
export 'src/multipart_file.dart';
export 'src/multipart_request.dart';
export 'src/progress.dart'
hide addTransfer, getProgressTransformer, setLength, setTransferred;
export 'src/request.dart';
export 'src/response.dart';
export 'src/streamed_request.dart';
Expand All @@ -44,8 +47,10 @@ Future<Response> head(Uri url, {Map<String, String>? headers}) =>
/// 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(Uri url, {Map<String, String>? headers}) =>
_withClient((client) => client.get(url, headers: headers));
Future<Response> get(Uri url,
{Map<String, String>? headers, HttpProgress? downloadProgress}) =>
_withClient((client) =>
client.get(url, headers: headers, downloadProgress: downloadProgress));

/// Sends an HTTP POST request with the given headers and body to the given URL.
///
Expand All @@ -66,9 +71,17 @@ Future<Response> get(Uri url, {Map<String, String>? headers}) =>
/// For more fine-grained control over the request, use [Request] or
/// [StreamedRequest] instead.
Future<Response> post(Uri url,
{Map<String, String>? headers, Object? body, Encoding? encoding}) =>
_withClient((client) =>
client.post(url, headers: headers, body: body, encoding: encoding));
{Map<String, String>? headers,
Object? body,
Encoding? encoding,
HttpProgress? downloadProgress,
HttpProgress? uploadProgress}) =>
_withClient((client) => client.post(url,
headers: headers,
body: body,
encoding: encoding,
downloadProgress: downloadProgress,
uploadProgress: uploadProgress));

/// Sends an HTTP PUT request with the given headers and body to the given URL.
///
Expand All @@ -89,9 +102,17 @@ Future<Response> post(Uri url,
/// For more fine-grained control over the request, use [Request] or
/// [StreamedRequest] instead.
Future<Response> put(Uri url,
{Map<String, String>? headers, Object? body, Encoding? encoding}) =>
_withClient((client) =>
client.put(url, headers: headers, body: body, encoding: encoding));
{Map<String, String>? headers,
Object? body,
Encoding? encoding,
HttpProgress? downloadProgress,
HttpProgress? uploadProgress}) =>
_withClient((client) => client.put(url,
headers: headers,
body: body,
encoding: encoding,
downloadProgress: downloadProgress,
uploadProgress: uploadProgress));

/// Sends an HTTP PATCH request with the given headers and body to the given
/// URL.
Expand All @@ -113,9 +134,17 @@ Future<Response> put(Uri url,
/// For more fine-grained control over the request, use [Request] or
/// [StreamedRequest] instead.
Future<Response> patch(Uri url,
{Map<String, String>? headers, Object? body, Encoding? encoding}) =>
_withClient((client) =>
client.patch(url, headers: headers, body: body, encoding: encoding));
{Map<String, String>? headers,
Object? body,
Encoding? encoding,
HttpProgress? downloadProgress,
HttpProgress? uploadProgress}) =>
_withClient((client) => client.patch(url,
headers: headers,
body: body,
encoding: encoding,
downloadProgress: downloadProgress,
uploadProgress: uploadProgress));

/// Sends an HTTP DELETE request with the given headers to the given URL.
///
Expand All @@ -125,9 +154,17 @@ Future<Response> patch(Uri url,
///
/// For more fine-grained control over the request, use [Request] instead.
Future<Response> delete(Uri url,
{Map<String, String>? headers, Object? body, Encoding? encoding}) =>
_withClient((client) =>
client.delete(url, headers: headers, body: body, encoding: encoding));
{Map<String, String>? headers,
Object? body,
Encoding? encoding,
HttpProgress? downloadProgress,
HttpProgress? uploadProgress}) =>
_withClient((client) => client.delete(url,
headers: headers,
body: body,
encoding: encoding,
downloadProgress: downloadProgress,
uploadProgress: uploadProgress));

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

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

Future<T> _withClient<T>(Future<T> Function(Client) fn) async {
var client = Client();
Expand Down
64 changes: 48 additions & 16 deletions pkgs/http/lib/src/base_client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import 'base_request.dart';
import 'byte_stream.dart';
import 'client.dart';
import 'exception.dart';
import 'progress.dart';
import 'request.dart';
import 'response.dart';
import 'streamed_response.dart';
Expand All @@ -23,39 +24,66 @@ abstract mixin class BaseClient implements Client {
_sendUnstreamed('HEAD', url, headers);

@override
Future<Response> get(Uri url, {Map<String, String>? headers}) =>
_sendUnstreamed('GET', url, headers);
Future<Response> get(Uri url,
{Map<String, String>? headers,
HttpProgress? downloadProgress,
HttpProgress? uploadProgress}) =>
_sendUnstreamed('GET', url, headers, null, null, downloadProgress);

@override
Future<Response> post(Uri url,
{Map<String, String>? headers, Object? body, Encoding? encoding}) =>
_sendUnstreamed('POST', url, headers, body, encoding);
{Map<String, String>? headers,
Object? body,
Encoding? encoding,
HttpProgress? downloadProgress,
HttpProgress? uploadProgress}) =>
_sendUnstreamed('POST', url, headers, body, encoding, downloadProgress,
uploadProgress);

@override
Future<Response> put(Uri url,
{Map<String, String>? headers, Object? body, Encoding? encoding}) =>
_sendUnstreamed('PUT', url, headers, body, encoding);
{Map<String, String>? headers,
Object? body,
Encoding? encoding,
HttpProgress? downloadProgress,
HttpProgress? uploadProgress}) =>
_sendUnstreamed('PUT', url, headers, body, encoding, downloadProgress,
uploadProgress);

@override
Future<Response> patch(Uri url,
{Map<String, String>? headers, Object? body, Encoding? encoding}) =>
_sendUnstreamed('PATCH', url, headers, body, encoding);
{Map<String, String>? headers,
Object? body,
Encoding? encoding,
HttpProgress? downloadProgress,
HttpProgress? uploadProgress}) =>
_sendUnstreamed('PATCH', url, headers, body, encoding, downloadProgress,
uploadProgress);

@override
Future<Response> delete(Uri url,
{Map<String, String>? headers, Object? body, Encoding? encoding}) =>
_sendUnstreamed('DELETE', url, headers, body, encoding);
{Map<String, String>? headers,
Object? body,
Encoding? encoding,
HttpProgress? downloadProgress,
HttpProgress? uploadProgress}) =>
_sendUnstreamed('DELETE', url, headers, body, encoding, downloadProgress,
uploadProgress);

@override
Future<String> read(Uri url, {Map<String, String>? headers}) async {
final response = await get(url, headers: headers);
Future<String> read(Uri url,
{Map<String, String>? headers, HttpProgress? downloadProgress}) async {
final response =
await get(url, headers: headers, downloadProgress: downloadProgress);
_checkResponseSuccess(url, response);
return response.body;
}

@override
Future<Uint8List> readBytes(Uri url, {Map<String, String>? headers}) async {
final response = await get(url, headers: headers);
Future<Uint8List> readBytes(Uri url,
{Map<String, String>? headers, HttpProgress? downloadProgress}) async {
final response =
await get(url, headers: headers, downloadProgress: downloadProgress);
_checkResponseSuccess(url, response);
return response.bodyBytes;
}
Expand All @@ -73,8 +101,12 @@ abstract mixin class BaseClient implements Client {
/// Sends a non-streaming [Request] and returns a non-streaming [Response].
Future<Response> _sendUnstreamed(
String method, Uri url, Map<String, String>? headers,
[Object? body, Encoding? encoding]) async {
var request = Request(method, url);
[Object? body,
Encoding? encoding,
HttpProgress? downloadProgress,
HttpProgress? uploadProgress]) async {
var request = Request(method, url,
downloadProgress: downloadProgress, uploadProgress: uploadProgress);

if (headers != null) request.headers.addAll(headers);
if (encoding != null) request.encoding = encoding;
Expand Down
27 changes: 26 additions & 1 deletion pkgs/http/lib/src/base_request.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// 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.

import 'dart:async';
import 'dart:collection';

import 'package:meta/meta.dart';
Expand All @@ -11,6 +12,7 @@ import 'base_client.dart';
import 'base_response.dart';
import 'byte_stream.dart';
import 'client.dart';
import 'progress.dart';
import 'streamed_response.dart';
import 'utils.dart';

Expand Down Expand Up @@ -89,14 +91,36 @@ 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.
///
/// If defined, this [HttpProgress.handler] will be called when the upload
/// progress changes.
///
/// To see the usage of the progress handler, see [HttpProgress].
///
/// To see the progress of the download, use [downloadProgress].
final HttpProgress? uploadProgress;

/// On download progress.
///
/// If defined, this [HttpProgress.handler] will be called when the download
/// progress changes.
///
/// To see the usage of the progress handler, see [HttpProgress].
///
/// To see the progress of the upload, use [uploadProgress].
final HttpProgress? downloadProgress;

BaseRequest(String method, this.url,
{this.uploadProgress, this.downloadProgress})
: method = _validateMethod(method),
headers = LinkedHashMap(
equals: (key1, key2) => key1.toLowerCase() == key2.toLowerCase(),
Expand Down Expand Up @@ -132,6 +156,7 @@ abstract class BaseRequest {
try {
var response = await client.send(this);
var stream = onDone(response.stream, client.close);

return StreamedResponse(ByteStream(stream), response.statusCode,
contentLength: response.contentLength,
request: response.request,
Expand Down
20 changes: 20 additions & 0 deletions pkgs/http/lib/src/browser_client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import 'base_client.dart';
import 'base_request.dart';
import 'byte_stream.dart';
import 'exception.dart';
import 'progress.dart';
import 'streamed_response.dart';

final _digitRegex = RegExp(r'^\d+$');
Expand Down Expand Up @@ -57,6 +58,7 @@ class BrowserClient extends BaseClient {
}
var bytes = await request.finalize().toBytes();
var xhr = XMLHttpRequest();

_xhrs.add(xhr);
xhr
..open(request.method, '${request.url}', true)
Expand All @@ -68,6 +70,24 @@ class BrowserClient extends BaseClient {

var completer = Completer<StreamedResponse>();

if (request.uploadProgress != null) {
setLength(request.uploadProgress!, bytes.length);
const EventStreamProvider('progress')
.forTarget(xhr.upload)
.listen((event) {
setTransferred(
request.uploadProgress!, (event as ProgressEvent).loaded);
});
}

if (request.downloadProgress != null) {
const EventStreamProvider('progress').forTarget(xhr).listen((event) {
setLength(request.downloadProgress!,
(event as ProgressEvent).lengthComputable ? event.total : null);
setTransferred(request.downloadProgress!, event.loaded);
});
}

unawaited(xhr.onLoad.first.then((_) {
if (xhr.responseHeaders['content-length'] case final contentLengthHeader
when contentLengthHeader != null &&
Expand Down
4 changes: 4 additions & 0 deletions pkgs/http/lib/src/byte_stream.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ final class ByteStream extends StreamView<List<int>> {
factory ByteStream.fromBytes(List<int> bytes) =>
ByteStream(Stream.value(bytes));

factory ByteStream.withTransformer(Stream<List<int>> stream,
StreamTransformer<List<int>, List<int>> transformer) =>
ByteStream(stream.transform(transformer));

/// Collects the data of this stream in a [Uint8List].
Future<Uint8List> toBytes() {
var completer = Completer<Uint8List>();
Expand Down
Loading

0 comments on commit e5fee5a

Please sign in to comment.