Skip to content
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

added downloadProgress and uploadProgress #1071

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My first impression:

Adding new fields to a public class is definitely going to be breaking:
https://github.com/aloisdeniel/http_extensions/blob/122a3d85632a08e0341e4701bafd0e8780740fab/http_extensions_base_url/lib/src/request.dart#L4

Copy link
Author

@Mehmetyaz Mehmetyaz Dec 6, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mind if I add this feature by creating a new Request and Client(io & html) classes? In global functions (get, post etc), if xProgress parameters are set, this new type of Client will be instantiated.


/// 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