Skip to content

Commit 69d6064

Browse files
committed
Merge package:http_retry
Update all references to `http_retry` to `http/retry`. Shorten the README content a bit and put it in it's own section.
2 parents a8cba05 + 97b31c5 commit 69d6064

File tree

5 files changed

+430
-0
lines changed

5 files changed

+430
-0
lines changed

README.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,3 +67,33 @@ class UserAgentClient extends http.BaseClient {
6767
}
6868
}
6969
```
70+
71+
## Retrying requests
72+
73+
`package:http/retry.dart` provides a class [`RetryClient`][RetryClient] to wrap
74+
an underlying [`http.Client`][Client] which transparently retries failing
75+
requests.
76+
77+
[RetryClient]: https://pub.dev/documentation/http/latest/retry/RetryClient-class.html
78+
[Client]: https://pub.dev/documentation/http/latest/http/Client-class.html
79+
80+
```dart
81+
import 'package:http/http.dart' as http;
82+
import 'package:http/retry.dart';
83+
84+
Future<void> main() async {
85+
final client = RetryClient(http.Client());
86+
try {
87+
print(await client.read('http://example.org'));
88+
} finally {
89+
client.close();
90+
}
91+
}
92+
```
93+
94+
By default, this retries any request whose response has status code 503
95+
Temporary Failure up to three retries. It waits 500ms before the first retry,
96+
and increases the delay by 1.5x each time. All of this can be customized using
97+
the [`RetryClient()`][new RetryClient] constructor.
98+
99+
[new RetryClient]: https://pub.dev/documentation/http/latest/retry/RetryClient/RetryClient.html

example/retry.dart

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'package:http/http.dart' as http;
6+
import 'package:http/retry.dart';
7+
8+
Future<void> main() async {
9+
final client = RetryClient(http.Client());
10+
try {
11+
print(await client.read(Uri.http('example.org', '')));
12+
} finally {
13+
client.close();
14+
}
15+
}

lib/retry.dart

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'dart:async';
6+
import 'dart:math' as math;
7+
8+
import 'package:async/async.dart';
9+
10+
import 'http.dart';
11+
12+
/// An HTTP client wrapper that automatically retries failing requests.
13+
class RetryClient extends BaseClient {
14+
/// The wrapped client.
15+
final Client _inner;
16+
17+
/// The number of times a request should be retried.
18+
final int _retries;
19+
20+
/// The callback that determines whether a request should be retried.
21+
final bool Function(BaseResponse) _when;
22+
23+
/// The callback that determines whether a request when an error is thrown.
24+
final bool Function(Object, StackTrace) _whenError;
25+
26+
/// The callback that determines how long to wait before retrying a request.
27+
final Duration Function(int) _delay;
28+
29+
/// The callback to call to indicate that a request is being retried.
30+
final void Function(BaseRequest, BaseResponse?, int)? _onRetry;
31+
32+
/// Creates a client wrapping [_inner] that retries HTTP requests.
33+
///
34+
/// This retries a failing request [retries] times (3 by default). Note that
35+
/// `n` retries means that the request will be sent at most `n + 1` times.
36+
///
37+
/// By default, this retries requests whose responses have status code 503
38+
/// Temporary Failure. If [when] is passed, it retries any request for whose
39+
/// response [when] returns `true`. If [whenError] is passed, it also retries
40+
/// any request that throws an error for which [whenError] returns `true`.
41+
///
42+
/// By default, this waits 500ms between the original request and the first
43+
/// retry, then increases the delay by 1.5x for each subsequent retry. If
44+
/// [delay] is passed, it's used to determine the time to wait before the
45+
/// given (zero-based) retry.
46+
///
47+
/// If [onRetry] is passed, it's called immediately before each retry so that
48+
/// the client has a chance to perform side effects like logging. The
49+
/// `response` parameter will be null if the request was retried due to an
50+
/// error for which [whenError] returned `true`.
51+
RetryClient(
52+
this._inner, {
53+
int retries = 3,
54+
bool Function(BaseResponse) when = _defaultWhen,
55+
bool Function(Object, StackTrace) whenError = _defaultWhenError,
56+
Duration Function(int retryCount) delay = _defaultDelay,
57+
void Function(BaseRequest, BaseResponse?, int retryCount)? onRetry,
58+
}) : _retries = retries,
59+
_when = when,
60+
_whenError = whenError,
61+
_delay = delay,
62+
_onRetry = onRetry {
63+
RangeError.checkNotNegative(_retries, 'retries');
64+
}
65+
66+
/// Like [new RetryClient], but with a pre-computed list of [delays]
67+
/// between each retry.
68+
///
69+
/// This will retry a request at most `delays.length` times, using each delay
70+
/// in order. It will wait for `delays[0]` after the initial request,
71+
/// `delays[1]` after the first retry, and so on.
72+
RetryClient.withDelays(
73+
Client inner,
74+
Iterable<Duration> delays, {
75+
bool Function(BaseResponse) when = _defaultWhen,
76+
bool Function(Object, StackTrace) whenError = _defaultWhenError,
77+
void Function(BaseRequest, BaseResponse?, int retryCount)? onRetry,
78+
}) : this._withDelays(
79+
inner,
80+
delays.toList(),
81+
when: when,
82+
whenError: whenError,
83+
onRetry: onRetry,
84+
);
85+
86+
RetryClient._withDelays(
87+
Client inner,
88+
List<Duration> delays, {
89+
required bool Function(BaseResponse) when,
90+
required bool Function(Object, StackTrace) whenError,
91+
required void Function(BaseRequest, BaseResponse?, int)? onRetry,
92+
}) : this(
93+
inner,
94+
retries: delays.length,
95+
delay: (retryCount) => delays[retryCount],
96+
when: when,
97+
whenError: whenError,
98+
onRetry: onRetry,
99+
);
100+
101+
@override
102+
Future<StreamedResponse> send(BaseRequest request) async {
103+
final splitter = StreamSplitter(request.finalize());
104+
105+
var i = 0;
106+
for (;;) {
107+
StreamedResponse? response;
108+
try {
109+
response = await _inner.send(_copyRequest(request, splitter.split()));
110+
} catch (error, stackTrace) {
111+
if (i == _retries || !_whenError(error, stackTrace)) rethrow;
112+
}
113+
114+
if (response != null) {
115+
if (i == _retries || !_when(response)) return response;
116+
117+
// Make sure the response stream is listened to so that we don't leave
118+
// dangling connections.
119+
_unawaited(response.stream.listen((_) {}).cancel().catchError((_) {}));
120+
}
121+
122+
await Future.delayed(_delay(i));
123+
_onRetry?.call(request, response, i);
124+
i++;
125+
}
126+
}
127+
128+
/// Returns a copy of [original] with the given [body].
129+
StreamedRequest _copyRequest(BaseRequest original, Stream<List<int>> body) {
130+
final request = StreamedRequest(original.method, original.url)
131+
..contentLength = original.contentLength
132+
..followRedirects = original.followRedirects
133+
..headers.addAll(original.headers)
134+
..maxRedirects = original.maxRedirects
135+
..persistentConnection = original.persistentConnection;
136+
137+
body.listen(request.sink.add,
138+
onError: request.sink.addError,
139+
onDone: request.sink.close,
140+
cancelOnError: true);
141+
142+
return request;
143+
}
144+
145+
@override
146+
void close() => _inner.close();
147+
}
148+
149+
bool _defaultWhen(BaseResponse response) => response.statusCode == 503;
150+
151+
bool _defaultWhenError(Object error, StackTrace stackTrace) => false;
152+
153+
Duration _defaultDelay(int retryCount) =>
154+
const Duration(milliseconds: 500) * math.pow(1.5, retryCount);
155+
156+
void _unawaited(Future<void>? f) {}

pubspec.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,13 @@ environment:
77
sdk: '>=2.12.0 <3.0.0'
88

99
dependencies:
10+
async: ^2.5.0
1011
http_parser: ^4.0.0
1112
meta: ^1.3.0
1213
path: ^1.8.0
1314
pedantic: ^1.10.0
1415

1516
dev_dependencies:
17+
fake_async: ^1.2.0
1618
shelf: ^1.1.0
1719
test: ^1.16.0

0 commit comments

Comments
 (0)