-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Extract progress & cancellation for streamed responses to DioMixin
- Loading branch information
Showing
12 changed files
with
360 additions
and
58 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
import 'dart:async'; | ||
import 'dart:typed_data'; | ||
|
||
import 'package:dio/dio.dart'; | ||
|
||
/// An internal helper function to handle things around | ||
/// streamed responses. | ||
Stream<Uint8List> handleResponseStream( | ||
RequestOptions options, | ||
ResponseBody response, | ||
) { | ||
final source = response.stream; | ||
|
||
// Use a StreamController to explicitly handle receive timeouts. | ||
final responseSink = StreamController<Uint8List>(); | ||
late StreamSubscription<List<int>> responseSubscription; | ||
|
||
late int totalLength; | ||
if (options.onReceiveProgress != null) { | ||
totalLength = response.contentLength; | ||
} | ||
|
||
int dataLength = 0; | ||
responseSubscription = source.listen( | ||
(data) { | ||
responseSink.add(data); | ||
options.onReceiveProgress?.call( | ||
dataLength += data.length, | ||
totalLength, | ||
); | ||
}, | ||
onError: (error, stackTrace) { | ||
responseSink.addError(error, stackTrace); | ||
responseSink.close(); | ||
}, | ||
onDone: () { | ||
responseSubscription.cancel(); | ||
responseSink.close(); | ||
}, | ||
cancelOnError: true, | ||
); | ||
|
||
options.cancelToken?.whenCancel.whenComplete(() { | ||
/// Close the response stream upon a cancellation. | ||
responseSubscription.cancel(); | ||
if (!responseSink.isClosed) { | ||
responseSink.addError(options.cancelToken!.cancelError!); | ||
responseSink.close(); | ||
} | ||
}); | ||
return responseSink.stream; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,207 @@ | ||
import 'dart:async'; | ||
import 'dart:typed_data'; | ||
|
||
import 'package:dio/dio.dart'; | ||
import 'package:dio/src/response/response_stream_handler.dart'; | ||
import 'package:test/test.dart'; | ||
|
||
import '../utils.dart'; | ||
|
||
void main() { | ||
group(handleResponseStream, () { | ||
late StreamController<Uint8List> source; | ||
|
||
setUp(() { | ||
source = StreamController<Uint8List>(); | ||
}); | ||
|
||
test('completes', () async { | ||
final stream = handleResponseStream( | ||
RequestOptions( | ||
cancelToken: CancelToken(), | ||
), | ||
ResponseBody( | ||
source.stream, | ||
200, | ||
), | ||
); | ||
|
||
expectLater( | ||
stream, | ||
emitsInOrder([ | ||
Uint8List.fromList([0]), | ||
Uint8List.fromList([1, 2]), | ||
emitsDone, | ||
]), | ||
); | ||
|
||
source.add(Uint8List.fromList([0])); | ||
source.add(Uint8List.fromList([1, 2])); | ||
source.close(); | ||
}); | ||
|
||
test('unsubscribes from source on cancel', () async { | ||
final cancelToken = CancelToken(); | ||
final stream = handleResponseStream( | ||
RequestOptions( | ||
cancelToken: cancelToken, | ||
), | ||
ResponseBody( | ||
source.stream, | ||
200, | ||
), | ||
); | ||
|
||
expectLater( | ||
stream, | ||
emitsInOrder([ | ||
Uint8List.fromList([0]), | ||
emitsError(matchesDioException( | ||
DioExceptionType.cancel, | ||
stackTraceContains: 'test/response/response_stream_test.dart', | ||
)), | ||
emitsDone, | ||
]), | ||
); | ||
|
||
source.add(Uint8List.fromList([0])); | ||
|
||
expect(source.hasListener, isTrue); | ||
cancelToken.cancel(); | ||
|
||
await Future.delayed(Duration(milliseconds: 100), () { | ||
expect(source.hasListener, isFalse); | ||
}); | ||
}); | ||
|
||
test('sends progress with total', () async { | ||
int count = 0; | ||
int total = 0; | ||
|
||
final stream = handleResponseStream( | ||
RequestOptions( | ||
cancelToken: CancelToken(), | ||
onReceiveProgress: (c, t) { | ||
count = c; | ||
total = t; | ||
}, | ||
), | ||
ResponseBody( | ||
source.stream, | ||
200, | ||
headers: { | ||
Headers.contentLengthHeader: ['6'], | ||
}, | ||
), | ||
); | ||
|
||
expectLater( | ||
stream, | ||
emitsInOrder([ | ||
Uint8List.fromList([0]), | ||
Uint8List.fromList([1, 2]), | ||
Uint8List.fromList([3, 4, 5]), | ||
emitsDone, | ||
]), | ||
); | ||
|
||
source.add(Uint8List.fromList([0])); | ||
await Future.delayed(Duration(milliseconds: 100), () { | ||
expect(count, 1); | ||
expect(total, 6); | ||
}); | ||
|
||
source.add(Uint8List.fromList([1, 2])); | ||
await Future.delayed(Duration(milliseconds: 100), () { | ||
expect(count, 3); | ||
expect(total, 6); | ||
}); | ||
|
||
source.add(Uint8List.fromList([3, 4, 5])); | ||
await Future.delayed(Duration(milliseconds: 100), () { | ||
expect(count, 6); | ||
expect(total, 6); | ||
}); | ||
|
||
source.close(); | ||
}); | ||
|
||
test('sends progress without total', () async { | ||
int count = 0; | ||
int total = 0; | ||
|
||
final stream = handleResponseStream( | ||
RequestOptions( | ||
cancelToken: CancelToken(), | ||
onReceiveProgress: (c, t) { | ||
count = c; | ||
total = t; | ||
}, | ||
), | ||
ResponseBody( | ||
source.stream, | ||
200, | ||
), | ||
); | ||
|
||
expectLater( | ||
stream, | ||
emitsInOrder([ | ||
Uint8List.fromList([0]), | ||
Uint8List.fromList([1, 2]), | ||
Uint8List.fromList([3, 4, 5]), | ||
emitsDone, | ||
]), | ||
); | ||
|
||
source.add(Uint8List.fromList([0])); | ||
await Future.delayed(Duration(milliseconds: 100), () { | ||
expect(count, 1); | ||
expect(total, -1); | ||
}); | ||
|
||
source.add(Uint8List.fromList([1, 2])); | ||
await Future.delayed(Duration(milliseconds: 100), () { | ||
expect(count, 3); | ||
expect(total, -1); | ||
}); | ||
|
||
source.add(Uint8List.fromList([3, 4, 5])); | ||
await Future.delayed(Duration(milliseconds: 100), () { | ||
expect(count, 6); | ||
expect(total, -1); | ||
}); | ||
|
||
source.close(); | ||
}); | ||
|
||
test('emits error on source error', () async { | ||
final stream = handleResponseStream( | ||
RequestOptions( | ||
cancelToken: CancelToken(), | ||
), | ||
ResponseBody( | ||
source.stream, | ||
200, | ||
), | ||
); | ||
|
||
expectLater( | ||
stream, | ||
emitsInOrder([ | ||
Uint8List.fromList([0]), | ||
emitsError(isA<FormatException>()), | ||
emitsDone, | ||
]), | ||
); | ||
|
||
source.add(Uint8List.fromList([0])); | ||
source.addError(FormatException()); | ||
source.close(); | ||
|
||
await Future.delayed(Duration(milliseconds: 100), () { | ||
expect(source.hasListener, isFalse); | ||
}); | ||
}); | ||
}); | ||
} |
Oops, something went wrong.