>> I want to know is it possible to cancel upload/download request without closing client client.close?
Yes, of course.
This is possible if needed.
Sequence of actions:
- Send request
- Await response
- Get the response from the request
- Get the stream from the response
- Listen to the stream
After completing these steps, you can instantly unsubscribe from the stream and everything will work fine.
```dart
import 'dart:async';
import 'package:http/http.dart';
import 'package:multitasking/misc/progress.dart';
import 'package:multitasking/multitasking.dart';
Future<void> main() async {
final cts = CancellationTokenSource();
final token = cts.token;
// User request to cancel
Timer(Duration(seconds: 3), () {
print('Canceling...');
cts.cancel();
});
int? percent;
final progress = Progress(((int count, int total) report) {
final (count, total) = report;
final newPercent = total == 0 ? 0 : count * 100 ~/ total;
if (percent != newPercent) {
percent = newPercent;
print('$percent% ${count.mb} MB of ${total.mb} MB');
}
});
final uri = Uri.parse(
'https://storage.googleapis.com/dart-archive/channels/stable/release/3.0.0/sdk/dartsdk-windows-x64-release.zip');
final client = Client();
final request = Request('GET', uri);
final response = await client.send(request);
final statusCode = response.statusCode;
if (statusCode != 200) {
throw Exception('Http error ($statusCode)');
}
final contentLength = response.contentLength;
var bytes = 0;
final stream = response.stream;
var isCanceled = false;
try {
await for (final event
in stream.asCancelable(token, throwIfCanceled: true)) {
bytes += event.length;
progress.report((bytes, contentLength ?? 0));
}
} on TaskCanceledException {
isCanceled = true;
}
if (isCanceled) {
print('Canceled');
}
}
extension on int {
String get mb => (this * 1.0 / (1024 * 1024)).toStringAsFixed(2);
}
```
Result:
```txt
0% 0.00 MB of 201.13 MB
1% 2.02 MB of 201.13 MB
2% 4.02 MB of 201.13 MB
3% 6.04 MB of 201.13 MB
4% 8.05 MB of 201.13 MB
Canceling...
Canceled
Exited.
```
Http request can be cancelled in the following cases:
- The server has not yet responded
- The server's response has already been received, but data has not yet begun to be received
- At the time of receiving the data
- In the interval between receiving the next portion of data
That is, at any moment.
Below is an example that implements all of this.
```dart
import 'dart:async';
import 'package:http/http.dart';
import 'package:multitasking/misc/progress.dart';
import 'package:multitasking/multitasking.dart';
import 'package:typed_data/typed_data.dart';
Future<void> main() async {
final cts = CancellationTokenSource();
final token = cts.token;
// User request to cancel
Timer(Duration(seconds: 3), () {
print('Canceling...');
cts.cancel();
});
var percent = 0;
final progress = Progress(((int count, int total) report) {
final (count, total) = report;
final newPercent = total == 0 ? 0 : count * 100 ~/ total;
if (percent != newPercent) {
percent = newPercent;
print('Progress: $percent% (${count.mb}MB of ${total.mb}MB)');
}
});
final uri = Uri.parse(
'https://storage.googleapis.com/dart-archive/channels/stable/release/3.0.0/sdk/dartsdk-windows-x64-release.zip');
final task = _download(uri, token, progress: progress);
try {
await task;
} catch (e) {
print('$e');
}
if (task.isCompleted) {
final response = task.result;
print('Done: ${response.bodyBytes.length}');
} else {
print('Unsuccessful: task ${task.state.name}');
}
}
Task<Response> _download(
Uri uri,
CancellationToken token, {
Progress<(int count, int total)>? progress,
}) {
return Task.run(() async {
final bytes = Uint8Buffer();
Task.onExit((task) {
print('${task.toString()}: ${task.state.name}');
_message('Downloaded: ${bytes.length}');
});
token.throwIfCanceled();
final client = Client();
final request = Request('GET', uri);
final response = await token.runCancelable(client.close, () async {
try {
return await client.send(request);
} on ClientException catch (e) {
if (e.message.startsWith('Connection attempt cancelled')) {
throw TaskCanceledException();
}
rethrow;
}
});
final statusCode = response.statusCode;
if (statusCode != 200) {
throw Exception('Http error ($statusCode)');
}
final contentLength = response.contentLength;
final stream = response.stream;
await for (final event
in stream.asCancelable(token, throwIfCanceled: true)) {
bytes.addAll(event);
progress?.report((bytes.length, contentLength ?? 0));
}
return Response.bytes(
bytes,
response.statusCode,
request: response.request,
headers: response.headers,
isRedirect: response.isRedirect,
persistentConnection: response.persistentConnection,
reasonPhrase: response.reasonPhrase,
);
});
}
void _message(String text) {
final task = Task.current.name ?? '${Task.current}';
print('$task: $text');
}
extension on int {
String get mb => (this * 1.0 / (1024 * 1024)).toStringAsFixed(2);
}
```
Result of work:
```txt
Progress: 1% (2.02MB of 201.13MB)
Progress: 2% (4.02MB of 201.13MB)
Progress: 3% (6.04MB of 201.13MB)
Progress: 4% (8.05MB of 201.13MB)
Canceling...
Task(1): canceled
Task(1): Downloaded: 8764417
TaskCanceledException
Unsuccessful: task canceled
Exited.
```
Some native socket streams are not cancelled immediately. This can take some time (usually a few seconds). What is important is that they release all resources (that is, they close safely).