Skip to content

Commit 405395b

Browse files
authored
Add a whenError() callback to retry requests that error (#3)
See dart-lang/pub#1826
1 parent 445c47a commit 405395b

File tree

4 files changed

+60
-9
lines changed

4 files changed

+60
-9
lines changed

CHANGELOG.md

+5
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
## 0.1.1
2+
3+
* Add a `whenError()` parameter to allow requests to be retried when they
4+
encounter networking errors.
5+
16
## 0.1.0
27

38
* Initial version.

lib/http_retry.dart

+29-8
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ class RetryClient extends BaseClient {
1919
/// The callback that determines whether a request should be retried.
2020
final bool Function(BaseResponse) _when;
2121

22+
/// The callback that determines whether a request when an error is thrown.
23+
final bool Function(dynamic, StackTrace) _whenError;
24+
2225
/// The callback that determines how long to wait before retrying a request.
2326
final Duration Function(int) _delay;
2427

@@ -32,22 +35,27 @@ class RetryClient extends BaseClient {
3235
///
3336
/// By default, this retries requests whose responses have status code 503
3437
/// Temporary Failure. If [when] is passed, it retries any request for whose
35-
/// response [when] returns `true`.
38+
/// response [when] returns `true`. If [whenError] is passed, it also retries
39+
/// any request that throws an error for which [whenError] returns `true`.
3640
///
3741
/// By default, this waits 500ms between the original request and the first
3842
/// retry, then increases the delay by 1.5x for each subsequent retry. If
3943
/// [delay] is passed, it's used to determine the time to wait before the
4044
/// given (zero-based) retry.
4145
///
4246
/// If [onRetry] is passed, it's called immediately before each retry so that
43-
/// the client has a chance to perform side effects like logging.
47+
/// the client has a chance to perform side effects like logging. The
48+
/// `response` parameter will be null if the request was retried due to an
49+
/// error for which [whenError] returned `true`.
4450
RetryClient(this._inner,
4551
{int retries,
4652
bool when(BaseResponse response),
53+
bool whenError(error, StackTrace stackTrace),
4754
Duration delay(int retryCount),
4855
void onRetry(BaseRequest request, BaseResponse response, int retryCount)})
4956
: _retries = retries ?? 3,
5057
_when = when ?? ((response) => response.statusCode == 503),
58+
_whenError = whenError ?? ((_, __) => false),
5159
_delay = delay ??
5260
((retryCount) =>
5361
new Duration(milliseconds: 500) * math.pow(1.5, retryCount)),
@@ -63,29 +71,42 @@ class RetryClient extends BaseClient {
6371
/// `delays[1]` after the first retry, and so on.
6472
RetryClient.withDelays(Client inner, Iterable<Duration> delays,
6573
{bool when(BaseResponse response),
74+
bool whenError(error, StackTrace stackTrace),
6675
void onRetry(BaseRequest request, BaseResponse response, int retryCount)})
67-
: this._withDelays(inner, delays.toList(), when: when, onRetry: onRetry);
76+
: this._withDelays(inner, delays.toList(),
77+
when: when, whenError: whenError, onRetry: onRetry);
6878

6979
RetryClient._withDelays(Client inner, List<Duration> delays,
7080
{bool when(BaseResponse response),
81+
bool whenError(error, StackTrace stackTrace),
7182
void onRetry(BaseRequest request, BaseResponse response, int retryCount)})
7283
: this(inner,
7384
retries: delays.length,
7485
delay: (retryCount) => delays[retryCount],
7586
when: when,
87+
whenError: whenError,
7688
onRetry: onRetry);
7789

7890
Future<StreamedResponse> send(BaseRequest request) async {
7991
var splitter = new StreamSplitter(request.finalize());
8092

8193
var i = 0;
8294
while (true) {
83-
var response = await _inner.send(_copyRequest(request, splitter.split()));
84-
if (i == _retries || !_when(response)) return response;
95+
StreamedResponse response;
96+
try {
97+
response = await _inner.send(_copyRequest(request, splitter.split()));
98+
} catch (error, stackTrace) {
99+
if (i == _retries || !_whenError(error, stackTrace)) rethrow;
100+
}
101+
102+
if (response != null) {
103+
if (i == _retries || !_when(response)) return response;
104+
105+
// Make sure the response stream is listened to so that we don't leave
106+
// dangling connections.
107+
response.stream.listen((_) {}).cancel()?.catchError((_) {});
108+
}
85109

86-
// Make sure the response stream is listened to so that we don't leave
87-
// dangling connections.
88-
response.stream.listen((_) {}).cancel()?.catchError((_) {});
89110
await new Future.delayed(_delay(i));
90111
if (_onRetry != null) _onRetry(request, response, i);
91112
i++;

pubspec.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
name: http_retry
2-
version: 0.1.0
2+
version: 0.1.1
33
description: HTTP client middleware that automatically retries requests.
44
author: Dart Team <[email protected]>
55
homepage: https://github.com/dart-lang/http_retry

test/http_retry_test.dart

+25
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,31 @@ void main() {
6666
expect(response.statusCode, equals(503));
6767
});
6868

69+
test("retries on any request where whenError() returns true", () async {
70+
var count = 0;
71+
var client = new RetryClient(
72+
new MockClient(expectAsync1((request) async {
73+
count++;
74+
if (count < 2) throw "oh no";
75+
return new Response("", 200);
76+
}, count: 2)),
77+
whenError: (error, _) => error == "oh no",
78+
delay: (_) => Duration.ZERO);
79+
80+
var response = await client.get("http://example.org");
81+
expect(response.statusCode, equals(200));
82+
});
83+
84+
test("doesn't retry a request where whenError() returns false", () async {
85+
var count = 0;
86+
var client = new RetryClient(
87+
new MockClient(expectAsync1((request) async => throw "oh no")),
88+
whenError: (error, _) => error == "oh yeah",
89+
delay: (_) => Duration.ZERO);
90+
91+
expect(client.get("http://example.org"), throwsA("oh no"));
92+
});
93+
6994
test("retries three times by default", () async {
7095
var client = new RetryClient(
7196
new MockClient(

0 commit comments

Comments
 (0)