Skip to content

Commit

Permalink
Added handling for xhr.onTimeout (cfug#1836)
Browse files Browse the repository at this point in the history
I've been trying to set connectionTimeout and receiveTimeout for my http
requests on chrome, and it didn't seem to be working. After further
investigation, it seems like the request was actually being canceled
after the timeout passed, but after the cancellation nothing happened
(the request was still blocked and the completer wasn't called).

So I found out xhr has a onTimeout method, I used it and raised the
required exception and now it works just fine.

### New Pull Request Checklist

- [x] I have read the
[Documentation](https://pub.dev/documentation/dio/latest/)
- [x] I have searched for a similar pull request in the
[project](https://github.com/cfug/dio/pulls) and found none
- [x] I have updated this branch with the latest `main` branch to avoid
conflicts (via merge from master or rebase)
- [x] I have added the required tests to prove the fix/feature I'm
adding
- [x] I have updated the documentation (if necessary)
- [x] I have run the tests without failures
- [x] I have updated the `CHANGELOG.md` in the corresponding package

---------

Signed-off-by: morhaviv <[email protected]>
Signed-off-by: Alex Li <[email protected]>
Signed-off-by: Peter Leibiger <[email protected]>
  • Loading branch information
morhaviv authored Jun 5, 2023
1 parent 193f783 commit 3e9c143
Show file tree
Hide file tree
Showing 5 changed files with 94 additions and 126 deletions.
2 changes: 2 additions & 0 deletions dio/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ See the [Migration Guide][] for the complete breaking changes list.**
- Fix `IOHttpClientAdapter.onHttpClientCreate` Repeated calls
- `IOHttpClientAdapter.onHttpClientCreate` has been deprecated and is scheduled for removal in
Dio 6.0.0 - Please use the replacement `IOHttpClientAdapter.createHttpClient` instead.
- Using `CancelToken` no longer closes and re-creates `HttpClient` for each request when `IOHttpClientAdapter` is used.
- Fix timeout handling for browser `receiveTimeout`.
- Using `CancelToken` no longer closes and re-creates `HttpClient` for each request when `IOHttpClientAdapter` is used.
- Improve performance when sending binary data (`List<int>`/`Uint8List`).

Expand Down
19 changes: 18 additions & 1 deletion dio/lib/src/adapters/browser_adapter.dart
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,12 @@ class BrowserHttpClientAdapter implements HttpClientAdapter {

final connectTimeout = options.connectTimeout;
final receiveTimeout = options.receiveTimeout;
int xhrTimeout = 0;
if (connectTimeout != null &&
receiveTimeout != null &&
receiveTimeout > Duration.zero) {
xhr.timeout = (connectTimeout + receiveTimeout).inMilliseconds;
xhrTimeout = (connectTimeout + receiveTimeout).inMilliseconds;
xhr.timeout = xhrTimeout;
}

final completer = Completer<ResponseBody>();
Expand Down Expand Up @@ -178,6 +180,21 @@ class BrowserHttpClientAdapter implements HttpClientAdapter {
);
});

xhr.onTimeout.first.then((_) {
if (connectTimeoutTimer != null) {
connectTimeoutTimer?.cancel();
}
if (!completer.isCompleted) {
completer.completeError(
DioException.receiveTimeout(
timeout: Duration(milliseconds: xhrTimeout),
requestOptions: options,
),
StackTrace.current,
);
}
});

cancelFuture?.then((_) {
if (xhr.readyState < 4 && xhr.readyState > 0) {
connectTimeoutTimer?.cancel();
Expand Down
1 change: 1 addition & 0 deletions dio/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,4 @@ dev_dependencies:
crypto: ^3.0.2
mockito: ^5.2.0
build_runner: any
http: ^0.13.0
125 changes: 0 additions & 125 deletions dio/test/readtimeout_test.dart

This file was deleted.

73 changes: 73 additions & 0 deletions dio/test/timeout_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import 'dart:io';

import 'package:dio/dio.dart';
import 'package:dio/io.dart';
import 'package:test/test.dart';

void main() {
late Dio dio;

setUp(() {
dio = Dio();
dio.options.baseUrl = 'https://httpbun.com/';
});

test('catch DioException when connect timeout', () {
dio.options.connectTimeout = Duration(milliseconds: 3);

expectLater(
dio.get('/drip-lines?delay=2'),
allOf(
throwsA(isA<DioError>()),
throwsA(predicate((DioError e) =>
e.type == DioErrorType.connectionTimeout &&
e.message!.contains('0:00:00.003000'))),
),
);
});

test('catch DioException when receiveTimeout', () {
dio.options.receiveTimeout = Duration(milliseconds: 10);

expectLater(
dio.get(
'/bytes/${1024 * 1024 * 20}',
options: Options(responseType: ResponseType.stream),
),
allOf([
throwsA(isA<DioException>()),
throwsA(predicate(
(DioException e) => e.type == DioExceptionType.receiveTimeout)),
throwsA(predicate(
(DioException e) => e.message!.contains('0:00:00.010000'))),
]),
);
}, testOn: 'vm');

test('no DioException when receiveTimeout > request duration', () async {
dio.options.receiveTimeout = Duration(seconds: 5);

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');
}

0 comments on commit 3e9c143

Please sign in to comment.