From 099d358bea86d0691152ad1e12f7c603299a3aa1 Mon Sep 17 00:00:00 2001 From: Alex Li Date: Sun, 3 Dec 2023 15:01:50 +0800 Subject: [PATCH 01/12] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Use=20`StreamControl?= =?UTF-8?q?ler`=20to=20handle=20the=20response=20stream?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dio/lib/src/adapters/io_adapter.dart | 45 +++++++++++++++++----------- 1 file changed, 27 insertions(+), 18 deletions(-) diff --git a/dio/lib/src/adapters/io_adapter.dart b/dio/lib/src/adapters/io_adapter.dart index 85de89359..adaf1953c 100644 --- a/dio/lib/src/adapters/io_adapter.dart +++ b/dio/lib/src/adapters/io_adapter.dart @@ -202,6 +202,10 @@ class IOHttpClientAdapter implements HttpClientAdapter { } } + // Use a StreamController to explicitly handle receive timeouts. + final responseSink = StreamController.broadcast(); + late StreamSubscription> responseSubscription; + final receiveStopwatch = Stopwatch(); Timer? receiveTimer; @@ -211,7 +215,7 @@ class IOHttpClientAdapter implements HttpClientAdapter { receiveStopwatch.stop(); } - void watchReceiveTimeout(EventSink sink) { + void watchReceiveTimeout() { if (receiveTimeout <= Duration.zero) { return; } @@ -221,32 +225,37 @@ class IOHttpClientAdapter implements HttpClientAdapter { } receiveTimer?.cancel(); receiveTimer = Timer(receiveTimeout, () { - sink.addError( + responseSink.addError( DioException.receiveTimeout( timeout: receiveTimeout, requestOptions: options, ), ); - sink.close(); + responseSink.close(); + responseSubscription.cancel(); responseStream.detachSocket().then((socket) => socket.destroy()); stopWatchReceiveTimeout(); }); } - final stream = responseStream.transform( - StreamTransformer.fromHandlers( - handleData: (data, sink) { - watchReceiveTimeout(sink); - // Always true if the receive timeout was not set. - if (receiveStopwatch.elapsed <= receiveTimeout) { - sink.add(data is Uint8List ? data : Uint8List.fromList(data)); - } - }, - handleDone: (sink) { - stopWatchReceiveTimeout(); - sink.close(); - }, - ), + responseSubscription = responseStream.listen( + (data) { + watchReceiveTimeout(); + // Always true if the receive timeout was not set. + if (receiveStopwatch.elapsed <= receiveTimeout) { + responseSink.add(data is Uint8List ? data : Uint8List.fromList(data)); + } + }, + onError: (error, stackTrace) { + responseSink.addError(error, stackTrace); + responseSink.close(); + }, + onDone: () { + stopWatchReceiveTimeout(); + responseSubscription.cancel(); + responseSink.close(); + }, + cancelOnError: true, ); final headers = >{}; @@ -254,7 +263,7 @@ class IOHttpClientAdapter implements HttpClientAdapter { headers[key] = values; }); return ResponseBody( - stream, + responseSink.stream, responseStream.statusCode, headers: headers, isRedirect: From 1f9eab837a9ff821b755f1dc123f3e8ca453e3db Mon Sep 17 00:00:00 2001 From: Alex Li Date: Sun, 3 Dec 2023 15:02:06 +0800 Subject: [PATCH 02/12] =?UTF-8?q?=E2=9A=A1=EF=B8=8F=20More=20cancellation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- plugins/http2_adapter/lib/src/http2_adapter.dart | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/plugins/http2_adapter/lib/src/http2_adapter.dart b/plugins/http2_adapter/lib/src/http2_adapter.dart index a8eff0bcf..05ee82306 100644 --- a/plugins/http2_adapter/lib/src/http2_adapter.dart +++ b/plugins/http2_adapter/lib/src/http2_adapter.dart @@ -116,6 +116,7 @@ class Http2Adapter implements HttpClientAdapter { final sc = StreamController(); final responseHeaders = Headers(); final responseCompleter = Completer(); + late StreamSubscription subscription; bool needRedirect = false; bool needResponse = false; @@ -152,7 +153,6 @@ class Http2Adapter implements HttpClientAdapter { } late int statusCode; - late StreamSubscription subscription; subscription = stream.incomingMessages.listen( (StreamMessage message) async { if (message is HeadersStreamMessage) { @@ -191,6 +191,7 @@ class Http2Adapter implements HttpClientAdapter { }, onDone: () { stopWatchReceiveTimeout(); + subscription.cancel(); sc.close(); }, onError: (Object error, StackTrace stackTrace) { @@ -204,6 +205,8 @@ class Http2Adapter implements HttpClientAdapter { sc.addError(error, stackTrace); } stopWatchReceiveTimeout(); + subscription.cancel(); + sc.close(); }, cancelOnError: true, ); From 5af6b369480a08dc751ceaf6dc40bb1150041af7 Mon Sep 17 00:00:00 2001 From: Alex Li Date: Sun, 3 Dec 2023 15:05:57 +0800 Subject: [PATCH 03/12] =?UTF-8?q?=E2=9C=85=20Fix=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dio/test/timeout_test.dart | 28 +++++++++++++++----- plugins/http2_adapter/test/timeout_test.dart | 25 ++++++++++++++++- 2 files changed, 45 insertions(+), 8 deletions(-) diff --git a/dio/test/timeout_test.dart b/dio/test/timeout_test.dart index 7626baade..0cc567ad6 100644 --- a/dio/test/timeout_test.dart +++ b/dio/test/timeout_test.dart @@ -1,3 +1,4 @@ +import 'dart:async'; import 'dart:io'; import 'package:dio/dio.dart'; @@ -47,14 +48,27 @@ void main() { ), matcher, ); - await expectLater( - dio.get( - '/drip', - queryParameters: {'delay': 0, 'duration:': 1}, - options: Options(responseType: ResponseType.stream), - ), - matcher, + + final completer = Completer(); + final streamedResponse = await dio.get( + '/drip', + queryParameters: {'delay': 0, 'duration': 20}, + options: Options(responseType: ResponseType.stream), + ); + (streamedResponse.data as ResponseBody).stream.listen( + (event) {}, + onError: (error) { + if (!completer.isCompleted) { + completer.completeError(error); + } + }, + onDone: () { + if (!completer.isCompleted) { + completer.complete(); + } + }, ); + await expectLater(completer.future, matcher); }); test('no DioException when receiveTimeout > request duration', () async { diff --git a/plugins/http2_adapter/test/timeout_test.dart b/plugins/http2_adapter/test/timeout_test.dart index 507c5c201..bdc0b62d6 100644 --- a/plugins/http2_adapter/test/timeout_test.dart +++ b/plugins/http2_adapter/test/timeout_test.dart @@ -1,4 +1,6 @@ @TestOn('vm') +import 'dart:async'; + import 'package:dio/dio.dart'; import 'package:dio_http2_adapter/dio_http2_adapter.dart'; import 'package:test/test.dart'; @@ -49,10 +51,31 @@ void main() { ), matcher, ); + + final completer = Completer(); + final streamedResponse = await dio.get( + '/drip', + queryParameters: {'delay': 0, 'duration': 20}, + options: Options(responseType: ResponseType.stream), + ); + (streamedResponse.data as ResponseBody).stream.listen( + (event) {}, + onError: (error) { + if (!completer.isCompleted) { + completer.completeError(error); + } + }, + onDone: () { + if (!completer.isCompleted) { + completer.complete(); + } + }, + ); + await expectLater(completer.future, matcher); await expectLater( dio.get( '/drip', - queryParameters: {'delay': 0, 'duration:': 1}, + queryParameters: {'delay': 0, 'duration': 2}, options: Options(responseType: ResponseType.stream), ), matcher, From eae5a1a6e39dbdff13dfe713c54745155aceefa0 Mon Sep 17 00:00:00 2001 From: Alex Li Date: Sun, 3 Dec 2023 15:06:40 +0800 Subject: [PATCH 04/12] =?UTF-8?q?=F0=9F=93=9D=20CHANGELOG?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dio/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dio/CHANGELOG.md b/dio/CHANGELOG.md index a39a21f1d..7b3327b6f 100644 --- a/dio/CHANGELOG.md +++ b/dio/CHANGELOG.md @@ -5,7 +5,7 @@ See the [Migration Guide][] for the complete breaking changes list.** ## Unreleased -*None.* +- Fix `receiveTimeout` for streamed responses. ## 5.4.0 From 2d246997c411ccbb7eda4c9d5425b03ecbe9a592 Mon Sep 17 00:00:00 2001 From: Alex Li Date: Sun, 3 Dec 2023 15:15:37 +0800 Subject: [PATCH 05/12] =?UTF-8?q?=F0=9F=8E=A8=20--?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- plugins/http2_adapter/test/timeout_test.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/http2_adapter/test/timeout_test.dart b/plugins/http2_adapter/test/timeout_test.dart index bdc0b62d6..8c31ac481 100644 --- a/plugins/http2_adapter/test/timeout_test.dart +++ b/plugins/http2_adapter/test/timeout_test.dart @@ -59,7 +59,7 @@ void main() { options: Options(responseType: ResponseType.stream), ); (streamedResponse.data as ResponseBody).stream.listen( - (event) {}, + (event) {}, onError: (error) { if (!completer.isCompleted) { completer.completeError(error); From 9f26ae22b652aac5b52696cf00d10d0dcc8ade7f Mon Sep 17 00:00:00 2001 From: Alex Li Date: Sun, 3 Dec 2023 15:47:44 +0800 Subject: [PATCH 06/12] =?UTF-8?q?=F0=9F=90=9B=20Fix=20listen?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dio/lib/src/adapters/io_adapter.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dio/lib/src/adapters/io_adapter.dart b/dio/lib/src/adapters/io_adapter.dart index adaf1953c..795c0fe61 100644 --- a/dio/lib/src/adapters/io_adapter.dart +++ b/dio/lib/src/adapters/io_adapter.dart @@ -203,7 +203,7 @@ class IOHttpClientAdapter implements HttpClientAdapter { } // Use a StreamController to explicitly handle receive timeouts. - final responseSink = StreamController.broadcast(); + final responseSink = StreamController(); late StreamSubscription> responseSubscription; final receiveStopwatch = Stopwatch(); From c0a3150d7588c73fb9289fca5ec30bca14842e25 Mon Sep 17 00:00:00 2001 From: Alex Li Date: Sun, 3 Dec 2023 16:25:59 +0800 Subject: [PATCH 07/12] =?UTF-8?q?=E2=9C=85=20++?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dio/lib/src/adapters/io_adapter.dart | 4 ++-- dio/test/options_test.dart | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/dio/lib/src/adapters/io_adapter.dart b/dio/lib/src/adapters/io_adapter.dart index 795c0fe61..50feaff6d 100644 --- a/dio/lib/src/adapters/io_adapter.dart +++ b/dio/lib/src/adapters/io_adapter.dart @@ -238,12 +238,12 @@ class IOHttpClientAdapter implements HttpClientAdapter { }); } - responseSubscription = responseStream.listen( + responseSubscription = responseStream.cast().listen( (data) { watchReceiveTimeout(); // Always true if the receive timeout was not set. if (receiveStopwatch.elapsed <= receiveTimeout) { - responseSink.add(data is Uint8List ? data : Uint8List.fromList(data)); + responseSink.add(data); } }, onError: (error, stackTrace) { diff --git a/dio/test/options_test.dart b/dio/test/options_test.dart index 086fe8b34..f7d7a8464 100644 --- a/dio/test/options_test.dart +++ b/dio/test/options_test.dart @@ -561,8 +561,7 @@ void main() { when(response.reasonPhrase).thenReturn('OK'); when(response.isRedirect).thenReturn(false); when(response.redirects).thenReturn([]); - when(response.transform(any)) - .thenAnswer((_) => Stream.empty()); + when(response.cast()).thenAnswer((_) => Stream.empty()); return Future.value(request); }); From 0da5df0910675922056abacccda8984bf54d92c9 Mon Sep 17 00:00:00 2001 From: Alex Li Date: Sun, 3 Dec 2023 16:27:03 +0800 Subject: [PATCH 08/12] =?UTF-8?q?=E2=9C=85=20--?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- plugins/http2_adapter/test/timeout_test.dart | 8 -------- 1 file changed, 8 deletions(-) diff --git a/plugins/http2_adapter/test/timeout_test.dart b/plugins/http2_adapter/test/timeout_test.dart index 8c31ac481..ec5a4301f 100644 --- a/plugins/http2_adapter/test/timeout_test.dart +++ b/plugins/http2_adapter/test/timeout_test.dart @@ -72,13 +72,5 @@ void main() { }, ); await expectLater(completer.future, matcher); - await expectLater( - dio.get( - '/drip', - queryParameters: {'delay': 0, 'duration': 2}, - options: Options(responseType: ResponseType.stream), - ), - matcher, - ); }); } From 3431cec46e45999a417a8f004ccf14b7b0314252 Mon Sep 17 00:00:00 2001 From: Alex Li Date: Sun, 3 Dec 2023 16:43:47 +0800 Subject: [PATCH 09/12] =?UTF-8?q?=E2=9C=85=20++?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dio/lib/src/adapters/io_adapter.dart | 1 + dio/test/timeout_test.dart | 4 +- .../http2_adapter/lib/src/http2_adapter.dart | 39 ++++++++++--------- plugins/http2_adapter/test/timeout_test.dart | 4 +- 4 files changed, 28 insertions(+), 20 deletions(-) diff --git a/dio/lib/src/adapters/io_adapter.dart b/dio/lib/src/adapters/io_adapter.dart index 50feaff6d..ab1d76dc9 100644 --- a/dio/lib/src/adapters/io_adapter.dart +++ b/dio/lib/src/adapters/io_adapter.dart @@ -247,6 +247,7 @@ class IOHttpClientAdapter implements HttpClientAdapter { } }, onError: (error, stackTrace) { + stopWatchReceiveTimeout(); responseSink.addError(error, stackTrace); responseSink.close(); }, diff --git a/dio/test/timeout_test.dart b/dio/test/timeout_test.dart index 0cc567ad6..85d130f59 100644 --- a/dio/test/timeout_test.dart +++ b/dio/test/timeout_test.dart @@ -38,7 +38,9 @@ void main() { ), ), throwsA( - predicate((e) => e.message!.contains('0:00:01.000000')), + predicate( + (e) => e.message!.contains(dio.options.receiveTimeout.toString()), + ), ), ]); await expectLater( diff --git a/plugins/http2_adapter/lib/src/http2_adapter.dart b/plugins/http2_adapter/lib/src/http2_adapter.dart index 05ee82306..fbfa3d03f 100644 --- a/plugins/http2_adapter/lib/src/http2_adapter.dart +++ b/plugins/http2_adapter/lib/src/http2_adapter.dart @@ -113,10 +113,10 @@ class Http2Adapter implements HttpClientAdapter { } await stream.outgoingMessages.close(); - final sc = StreamController(); + final responseSink = StreamController(); final responseHeaders = Headers(); final responseCompleter = Completer(); - late StreamSubscription subscription; + late StreamSubscription responseSubscription; bool needRedirect = false; bool needResponse = false; @@ -140,20 +140,21 @@ class Http2Adapter implements HttpClientAdapter { } receiveTimer?.cancel(); receiveTimer = Timer(receiveTimeout, () { - sc.addError( + responseSink.addError( DioException.receiveTimeout( timeout: receiveTimeout, requestOptions: options, ), ); - sc.close(); + responseSink.close(); + responseSubscription.cancel(); stream.terminate(); stopWatchReceiveTimeout(); }); } late int statusCode; - subscription = stream.incomingMessages.listen( + responseSubscription = stream.incomingMessages.listen( (StreamMessage message) async { if (message is HeadersStreamMessage) { for (final header in message.headers) { @@ -175,25 +176,20 @@ class Http2Adapter implements HttpClientAdapter { } else if (message is DataStreamMessage) { if (needResponse) { watchReceiveTimeout(); - sc.add( + responseSink.add( message.bytes is Uint8List ? message.bytes as Uint8List : Uint8List.fromList(message.bytes), ); } else { stopWatchReceiveTimeout(); - subscription.cancel().whenComplete(() { + responseSubscription.cancel().whenComplete(() { stream.terminate(); - sc.close(); + responseSink.close(); }); } } }, - onDone: () { - stopWatchReceiveTimeout(); - subscription.cancel(); - sc.close(); - }, onError: (Object error, StackTrace stackTrace) { // If connection is being forcefully terminated, remove the connection. if (error is TransportConnectionException) { @@ -202,11 +198,16 @@ class Http2Adapter implements HttpClientAdapter { if (!responseCompleter.isCompleted) { responseCompleter.completeError(error, stackTrace); } else { - sc.addError(error, stackTrace); + responseSink.addError(error, stackTrace); } stopWatchReceiveTimeout(); - subscription.cancel(); - sc.close(); + responseSubscription.cancel(); + responseSink.close(); + }, + onDone: () { + stopWatchReceiveTimeout(); + responseSubscription.cancel(); + responseSink.close(); }, cancelOnError: true, ); @@ -216,7 +217,9 @@ class Http2Adapter implements HttpClientAdapter { responseFuture = responseFuture.timeout( receiveTimeout, onTimeout: () { - subscription.cancel().whenComplete(() => sc.close()); + responseSubscription + .cancel() + .whenComplete(() => responseSink.close()); throw DioException.receiveTimeout( timeout: receiveTimeout, requestOptions: options, @@ -240,7 +243,7 @@ class Http2Adapter implements HttpClientAdapter { ); } return ResponseBody( - sc.stream, + responseSink.stream, statusCode, headers: responseHeaders.map, redirects: redirects, diff --git a/plugins/http2_adapter/test/timeout_test.dart b/plugins/http2_adapter/test/timeout_test.dart index ec5a4301f..367ea9d7c 100644 --- a/plugins/http2_adapter/test/timeout_test.dart +++ b/plugins/http2_adapter/test/timeout_test.dart @@ -41,7 +41,9 @@ void main() { ), ), throwsA( - predicate((e) => e.message!.contains('0:00:01.000000')), + predicate( + (e) => e.message!.contains(dio.options.receiveTimeout.toString()), + ), ), ]); await expectLater( From b4ea7bda02cf10da94a3b1a0106f1c9f82c130d6 Mon Sep 17 00:00:00 2001 From: Alex Li Date: Sun, 3 Dec 2023 17:10:42 +0800 Subject: [PATCH 10/12] =?UTF-8?q?=E2=9C=85=20++?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dio/test/timeout_test.dart | 175 +++++++++++-------- plugins/http2_adapter/test/timeout_test.dart | 152 ++++++++++------ 2 files changed, 196 insertions(+), 131 deletions(-) diff --git a/dio/test/timeout_test.dart b/dio/test/timeout_test.dart index 85d130f59..77759f3e7 100644 --- a/dio/test/timeout_test.dart +++ b/dio/test/timeout_test.dart @@ -1,8 +1,6 @@ import 'dart:async'; -import 'dart:io'; import 'package:dio/dio.dart'; -import 'package:dio/io.dart'; import 'package:test/test.dart'; void main() { @@ -13,64 +11,108 @@ void main() { dio.options.baseUrl = 'https://httpbun.com/'; }); - test('catch DioException when connectTimeout', () { - dio.options.connectTimeout = Duration(milliseconds: 3); + group('Timeout exception of', () { + group('connectTimeout', () { + final dio = Dio()..options.baseUrl = 'http://127.1.2.3:1234'; - expectLater( - dio.get('/drip-lines?delay=2'), - allOf( - throwsA(isA()), - throwsA(predicate((DioException e) => - e.type == DioExceptionType.connectionTimeout && - e.message!.contains('0:00:00.003000'))), - ), - ); - }); + test('with response', () async { + dio.options.connectTimeout = Duration(milliseconds: 3); + await expectLater( + dio.get('/'), + allOf( + throwsA(isA()), + throwsA(predicate((DioException e) => + e.type == DioExceptionType.connectionTimeout && + e.message!.contains('${dio.options.connectTimeout}'))), + ), + ); + }); - test('catch DioException when receiveTimeout', () async { - dio.options.receiveTimeout = Duration(seconds: 1); + test('update between calls', () async { + dio.options.connectTimeout = Duration(milliseconds: 5); + await expectLater( + dio.get('/'), + allOf( + throwsA(isA()), + throwsA(predicate((DioException e) => + e.type == DioExceptionType.connectionTimeout && + e.message!.contains('${dio.options.connectTimeout}'))), + ), + ); + dio.options.connectTimeout = Duration(milliseconds: 10); + await expectLater( + dio.get('/'), + allOf( + throwsA(isA()), + throwsA(predicate((DioException e) => + e.type == DioExceptionType.connectionTimeout && + e.message!.contains('${dio.options.connectTimeout}'))), + ), + ); + }, testOn: 'vm'); + }); - final matcher = allOf([ - throwsA(isA()), - throwsA( - predicate( - (e) => e.type == DioExceptionType.receiveTimeout, - ), - ), - throwsA( - predicate( - (e) => e.message!.contains(dio.options.receiveTimeout.toString()), - ), - ), - ]); - await expectLater( - dio.get( - '/drip', - queryParameters: {'delay': 2}, - ), - matcher, - ); + group('receiveTimeout', () { + test('with normal response', () async { + dio.options.receiveTimeout = Duration(seconds: 1); + await expectLater( + dio.get('/drip', queryParameters: {'delay': 2}), + allOf([ + throwsA(isA()), + throwsA( + predicate( + (e) => e.type == DioExceptionType.receiveTimeout, + ), + ), + throwsA( + predicate( + (e) => + e.message!.contains(dio.options.receiveTimeout.toString()), + ), + ), + ]), + ); + }); - final completer = Completer(); - final streamedResponse = await dio.get( - '/drip', - queryParameters: {'delay': 0, 'duration': 20}, - options: Options(responseType: ResponseType.stream), - ); - (streamedResponse.data as ResponseBody).stream.listen( - (event) {}, - onError: (error) { - if (!completer.isCompleted) { - completer.completeError(error); - } - }, - onDone: () { - if (!completer.isCompleted) { - completer.complete(); - } - }, - ); - await expectLater(completer.future, matcher); + test('with streamed response', () async { + dio.options.receiveTimeout = Duration(seconds: 1); + final completer = Completer(); + final streamedResponse = await dio.get( + '/drip', + queryParameters: {'delay': 0, 'duration': 20}, + options: Options(responseType: ResponseType.stream), + ); + (streamedResponse.data as ResponseBody).stream.listen( + (event) {}, + onError: (error) { + if (!completer.isCompleted) { + completer.completeError(error); + } + }, + onDone: () { + if (!completer.isCompleted) { + completer.complete(); + } + }, + ); + await expectLater( + completer.future, + allOf([ + throwsA(isA()), + throwsA( + predicate( + (e) => e.type == DioExceptionType.receiveTimeout, + ), + ), + throwsA( + predicate( + (e) => e.message! + .contains(dio.options.receiveTimeout.toString()), + ), + ), + ])); + }, testOn: 'vm'); + }); }); test('no DioException when receiveTimeout > request duration', () async { @@ -79,27 +121,6 @@ void main() { await dio.get('/drip?delay=1&numbytes=1'); }); - test('change connectTimeout in run time ', () async { - final dio = Dio(); - final adapter = IOHttpClientAdapter(); - final http = HttpClient(); - - adapter.createHttpClient = () => http; - dio.httpClientAdapter = adapter; - dio.options.connectTimeout = Duration(milliseconds: 200); - - try { - await dio.get('/'); - } on DioException catch (_) {} - expect(http.connectionTimeout?.inMilliseconds == 200, isTrue); - - try { - dio.options.connectTimeout = Duration(seconds: 1); - await dio.get('/'); - } on DioException catch (_) {} - expect(http.connectionTimeout?.inSeconds == 1, isTrue); - }, testOn: 'vm'); - test('ignores zero duration timeouts', () async { final dio = Dio( BaseOptions( diff --git a/plugins/http2_adapter/test/timeout_test.dart b/plugins/http2_adapter/test/timeout_test.dart index 367ea9d7c..4798f7f8f 100644 --- a/plugins/http2_adapter/test/timeout_test.dart +++ b/plugins/http2_adapter/test/timeout_test.dart @@ -16,63 +16,107 @@ void main() { ); }); - test('catch DioException when connectTimeout', () { - dio.options.connectTimeout = Duration(milliseconds: 3); + group('Timeout exception of', () { + group('connectTimeout', () { + final dio = Dio()..options.baseUrl = 'http://127.1.2.3:1234'; - expectLater( - dio.get('/drip-lines?delay=2'), - allOf( - throwsA(isA()), - throwsA(predicate((DioException e) => - e.type == DioExceptionType.connectionTimeout && - e.message!.contains('0:00:00.003000'))), - ), - ); - }); + test('with response', () async { + dio.options.connectTimeout = Duration(milliseconds: 3); + await expectLater( + dio.get('/'), + allOf( + throwsA(isA()), + throwsA(predicate((DioException e) => + e.type == DioExceptionType.connectionTimeout && + e.message!.contains('${dio.options.connectTimeout}'))), + ), + ); + }); - test('catch DioException when receiveTimeout', () async { - dio.options.receiveTimeout = Duration(seconds: 1); + test('update between calls', () async { + dio.options.connectTimeout = Duration(milliseconds: 5); + await expectLater( + dio.get('/'), + allOf( + throwsA(isA()), + throwsA(predicate((DioException e) => + e.type == DioExceptionType.connectionTimeout && + e.message!.contains('${dio.options.connectTimeout}'))), + ), + ); + dio.options.connectTimeout = Duration(milliseconds: 10); + await expectLater( + dio.get('/'), + allOf( + throwsA(isA()), + throwsA(predicate((DioException e) => + e.type == DioExceptionType.connectionTimeout && + e.message!.contains('${dio.options.connectTimeout}'))), + ), + ); + }); + }); - final matcher = allOf([ - throwsA(isA()), - throwsA( - predicate( - (e) => e.type == DioExceptionType.receiveTimeout, - ), - ), - throwsA( - predicate( - (e) => e.message!.contains(dio.options.receiveTimeout.toString()), - ), - ), - ]); - await expectLater( - dio.get( - '/drip', - queryParameters: {'delay': 2}, - ), - matcher, - ); + group('receiveTimeout', () { + test('with normal response', () async { + dio.options.receiveTimeout = Duration(seconds: 1); + await expectLater( + dio.get('/drip', queryParameters: {'delay': 2}), + allOf([ + throwsA(isA()), + throwsA( + predicate( + (e) => e.type == DioExceptionType.receiveTimeout, + ), + ), + throwsA( + predicate( + (e) => + e.message!.contains(dio.options.receiveTimeout.toString()), + ), + ), + ]), + ); + }); - final completer = Completer(); - final streamedResponse = await dio.get( - '/drip', - queryParameters: {'delay': 0, 'duration': 20}, - options: Options(responseType: ResponseType.stream), - ); - (streamedResponse.data as ResponseBody).stream.listen( - (event) {}, - onError: (error) { - if (!completer.isCompleted) { - completer.completeError(error); - } - }, - onDone: () { - if (!completer.isCompleted) { - completer.complete(); - } - }, - ); - await expectLater(completer.future, matcher); + test('with streamed response', () async { + dio.options.receiveTimeout = Duration(seconds: 1); + final completer = Completer(); + final streamedResponse = await dio.get( + '/drip', + queryParameters: {'delay': 0, 'duration': 20}, + options: Options(responseType: ResponseType.stream), + ); + (streamedResponse.data as ResponseBody).stream.listen( + (event) {}, + onError: (error) { + if (!completer.isCompleted) { + completer.completeError(error); + } + }, + onDone: () { + if (!completer.isCompleted) { + completer.complete(); + } + }, + ); + await expectLater( + completer.future, + allOf([ + throwsA(isA()), + throwsA( + predicate( + (e) => e.type == DioExceptionType.receiveTimeout, + ), + ), + throwsA( + predicate( + (e) => e.message! + .contains(dio.options.receiveTimeout.toString()), + ), + ), + ])); + }); + }); }); } From cbdc74ba18ee41afd490abb83a16799db15f4885 Mon Sep 17 00:00:00 2001 From: Alex Li Date: Sun, 3 Dec 2023 17:33:24 +0800 Subject: [PATCH 11/12] =?UTF-8?q?=E2=9C=85=20--?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dio/test/timeout_test.dart | 2 -- plugins/http2_adapter/test/timeout_test.dart | 25 -------------------- 2 files changed, 27 deletions(-) diff --git a/dio/test/timeout_test.dart b/dio/test/timeout_test.dart index 77759f3e7..92316a39f 100644 --- a/dio/test/timeout_test.dart +++ b/dio/test/timeout_test.dart @@ -13,8 +13,6 @@ void main() { group('Timeout exception of', () { group('connectTimeout', () { - final dio = Dio()..options.baseUrl = 'http://127.1.2.3:1234'; - test('with response', () async { dio.options.connectTimeout = Duration(milliseconds: 3); await expectLater( diff --git a/plugins/http2_adapter/test/timeout_test.dart b/plugins/http2_adapter/test/timeout_test.dart index 4798f7f8f..3c2a198d3 100644 --- a/plugins/http2_adapter/test/timeout_test.dart +++ b/plugins/http2_adapter/test/timeout_test.dart @@ -18,8 +18,6 @@ void main() { group('Timeout exception of', () { group('connectTimeout', () { - final dio = Dio()..options.baseUrl = 'http://127.1.2.3:1234'; - test('with response', () async { dio.options.connectTimeout = Duration(milliseconds: 3); await expectLater( @@ -32,29 +30,6 @@ void main() { ), ); }); - - test('update between calls', () async { - dio.options.connectTimeout = Duration(milliseconds: 5); - await expectLater( - dio.get('/'), - allOf( - throwsA(isA()), - throwsA(predicate((DioException e) => - e.type == DioExceptionType.connectionTimeout && - e.message!.contains('${dio.options.connectTimeout}'))), - ), - ); - dio.options.connectTimeout = Duration(milliseconds: 10); - await expectLater( - dio.get('/'), - allOf( - throwsA(isA()), - throwsA(predicate((DioException e) => - e.type == DioExceptionType.connectionTimeout && - e.message!.contains('${dio.options.connectTimeout}'))), - ), - ); - }); }); group('receiveTimeout', () { From 554b75551d29d1a925a525e79986ddf3b382d82a Mon Sep 17 00:00:00 2001 From: Alex Li Date: Sun, 3 Dec 2023 18:17:11 +0800 Subject: [PATCH 12/12] =?UTF-8?q?=E2=9C=85=20--?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dio/test/timeout_test.dart | 64 ++++++++++++++++++-------------------- 1 file changed, 31 insertions(+), 33 deletions(-) diff --git a/dio/test/timeout_test.dart b/dio/test/timeout_test.dart index 92316a39f..5735da650 100644 --- a/dio/test/timeout_test.dart +++ b/dio/test/timeout_test.dart @@ -1,6 +1,8 @@ import 'dart:async'; +import 'dart:io'; import 'package:dio/dio.dart'; +import 'package:dio/io.dart'; import 'package:test/test.dart'; void main() { @@ -27,26 +29,23 @@ void main() { }); test('update between calls', () async { + final client = HttpClient(); + final dio = Dio() + ..options.baseUrl = 'https://httpbun.com' + ..httpClientAdapter = IOHttpClientAdapter( + createHttpClient: () => client, + ); + dio.options.connectTimeout = Duration(milliseconds: 5); - await expectLater( - dio.get('/'), - allOf( - throwsA(isA()), - throwsA(predicate((DioException e) => - e.type == DioExceptionType.connectionTimeout && - e.message!.contains('${dio.options.connectTimeout}'))), - ), - ); + await dio + .get('/') + .catchError((e) => Response(requestOptions: RequestOptions())); + expect(client.connectionTimeout, dio.options.connectTimeout); dio.options.connectTimeout = Duration(milliseconds: 10); - await expectLater( - dio.get('/'), - allOf( - throwsA(isA()), - throwsA(predicate((DioException e) => - e.type == DioExceptionType.connectionTimeout && - e.message!.contains('${dio.options.connectTimeout}'))), - ), - ); + await dio + .get('/') + .catchError((e) => Response(requestOptions: RequestOptions())); + expect(client.connectionTimeout, dio.options.connectTimeout); }, testOn: 'vm'); }); @@ -64,8 +63,7 @@ void main() { ), throwsA( predicate( - (e) => - e.message!.contains(dio.options.receiveTimeout.toString()), + (e) => e.message!.contains('${dio.options.receiveTimeout}'), ), ), ]), @@ -94,21 +92,21 @@ void main() { }, ); await expectLater( - completer.future, - allOf([ - throwsA(isA()), - throwsA( - predicate( - (e) => e.type == DioExceptionType.receiveTimeout, - ), + completer.future, + allOf([ + throwsA(isA()), + throwsA( + predicate( + (e) => e.type == DioExceptionType.receiveTimeout, ), - throwsA( - predicate( - (e) => e.message! - .contains(dio.options.receiveTimeout.toString()), - ), + ), + throwsA( + predicate( + (e) => e.message!.contains('${dio.options.receiveTimeout}'), ), - ])); + ), + ]), + ); }, testOn: 'vm'); }); });