Skip to content

Commit ab64835

Browse files
authored
Merge pull request #23 from CodingAleCR/release/0.3.0
Release: 0.3.0
2 parents 6d70381 + 0b417a6 commit ab64835

13 files changed

+197
-39
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
# Changelog
22

3+
## 0.3.0
4+
5+
* Added: RetryPolicy. It allows to attempt retries on a request when an exception occurs or when a condition from the response is met.
6+
* Fixed: URI type urls not concatenating parameters.
7+
38
## 0.2.0
49

510
* Added: Unit testing for a few of the files.

README.md

Lines changed: 36 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,29 +6,32 @@
66
[![codecov](https://codecov.io/gh/CodingAleCR/http_interceptor/branch/master/graph/badge.svg)](https://codecov.io/gh/CodingAleCR/http_interceptor)
77
[![Star on GitHub](https://img.shields.io/github/stars/codingalecr/http_interceptor.svg?style=flat&logo=github&colorB=deeppink&label=stars)](https://github.com/codingalecr/http_interceptor)
88

9-
A middleware library that lets you modify requests and responses if desired. Based of on [http_middleware](https://github.com/TEDConsulting/http_middleware)
9+
This is a plugin that lets you intercept the different requests and responses from Dart's http package. You can use to add headers, modify query params, or print a log of the response.
1010

11-
## Getting Started
11+
## Quick Reference
1212

13-
This is a plugin that lets you intercept the different requests and responses from Dart's http package. You can use to add headers, modify query params, or print a log of the response.
13+
- [Installation](#installation)
14+
- [Usage](#usage)
15+
- [Building your own interceptor](#building-your-own-interceptor)
16+
- [Using your interceptor](#using-your-interceptor)
17+
- [Retrying requests](#retrying-requests)
18+
- [Having trouble? Fill an issue](#troubleshooting)
1419

15-
### Installing
20+
## Installation
1621

1722
Include the package with the latest version available in your `pubspec.yaml`.
1823

1924
```dart
2025
http_interceptor: any
2126
```
2227

23-
### Importing
28+
## Usage
2429

2530
```dart
2631
import 'package:http_interceptor/http_interceptor.dart';
2732
```
2833

29-
### Using `http_interceptor`
30-
31-
#### Building your own interceptor
34+
### Building your own interceptor
3235

3336
In order to implement `http_interceptor` you need to implement the `InterceptorContract` and create your own interceptor. This abstract class has two methods: `interceptRequest`, which triggers before the http request is called; and `interceptResponse`, which triggers after the request is called, it has a response attached to it which the corresponding to said request. You could use this to do logging, adding headers, error handling, or many other cool stuff. It is important to note that after you proccess the request/response objects you need to return them so that `http` can continue the execute.
3437

@@ -72,11 +75,11 @@ class WeatherApiInterceptor implements InterceptorContract {
7275
}
7376
```
7477

75-
#### Using your interceptor
78+
### Using your interceptor
7679

7780
Now that you actually have your interceptor implemented, now you need to use it. There are two general ways in which you can use them: by using the `HttpWithInterceptor` to do separate connections for different requests or using a `HttpClientWithInterceptor` for keeping a connection alive while making the different `http` calls. The ideal place to use them is in the service/provider class or the repository class (if you are not using services or providers); if you don't know about the repository pattern you can just google it and you'll know what I'm talking about. ;)
7881

79-
##### Using interceptors with Client
82+
#### Using interceptors with Client
8083

8184
Normally, this approach is taken because of its ability to be tested and mocked.
8285

@@ -107,7 +110,7 @@ class WeatherRepository {
107110
}
108111
```
109112

110-
##### Using interceptors without Client
113+
#### Using interceptors without Client
111114

112115
This is mostly the straight forward approach for a one-and-only call that you might need intercepted.
113116

@@ -137,6 +140,27 @@ class WeatherRepository {
137140
}
138141
```
139142

140-
### Issue Reporting
143+
### Retrying requests
144+
145+
**(NEW 🎉)** Sometimes you need to retry a request due to different circumstances, an expired token is a really good example. Here's how you could potentially implement an expired token retry policy with `http_interceptor`.
146+
147+
```dart
148+
class ExpiredTokenRetryPolicy extends RetryPolicy {
149+
@override
150+
bool shouldAttemptRetryOnResponse(Response response) {
151+
if (response.statusCode == 401) {
152+
// Perform your token refresh here.
153+
154+
return true;
155+
}
156+
157+
return false;
158+
}
159+
}
160+
```
161+
162+
You can also set the maximum amount of retry attempts with `maxRetryAttempts` property or override the `shouldAttemptRetryOnException` if you want to retry the request after it failed with an exception.
163+
164+
## Troubleshooting
141165

142166
Open an issue and tell me, I will be happy to help you out as soon as I can.

example/lib/main.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,7 @@ class WeatherApiInterceptor implements InterceptorContract {
284284
} catch (e) {
285285
print(e);
286286
}
287+
print(data.params);
287288
return data;
288289
}
289290

example/pubspec.lock

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ packages:
8787
path: ".."
8888
relative: true
8989
source: path
90-
version: "0.2.0"
90+
version: "0.3.0"
9191
http_parser:
9292
dependency: transitive
9393
description:

lib/http_client_with_interceptor.dart

Lines changed: 49 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -37,14 +37,21 @@ import 'http_methods.dart';
3737
class HttpClientWithInterceptor extends http.BaseClient {
3838
List<InterceptorContract> interceptors;
3939
Duration requestTimeout;
40+
RetryPolicy retryPolicy;
4041

4142
final Client _client = Client();
43+
int _retryCount = 0;
4244

43-
HttpClientWithInterceptor._internal({this.interceptors, this.requestTimeout});
45+
HttpClientWithInterceptor._internal({
46+
this.interceptors,
47+
this.requestTimeout,
48+
this.retryPolicy,
49+
});
4450

4551
factory HttpClientWithInterceptor.build({
4652
@required List<InterceptorContract> interceptors,
4753
Duration requestTimeout,
54+
RetryPolicy retryPolicy,
4855
}) {
4956
assert(interceptors != null);
5057

@@ -53,6 +60,7 @@ class HttpClientWithInterceptor extends http.BaseClient {
5360
return HttpClientWithInterceptor._internal(
5461
interceptors: interceptors,
5562
requestTimeout: requestTimeout,
63+
retryPolicy: retryPolicy,
5664
);
5765
}
5866

@@ -132,7 +140,14 @@ class HttpClientWithInterceptor extends http.BaseClient {
132140
dynamic body,
133141
Encoding encoding,
134142
}) async {
135-
if (url is String) url = Uri.parse(addParametersToUrl(url, params));
143+
if (url is String) {
144+
url = Uri.parse(addParametersToStringUrl(url, params));
145+
} else if (url is Uri) {
146+
url = addParametersToUrl(url, params);
147+
} else {
148+
throw HttpInterceptorException(
149+
"Malformed URL parameter. Check that the url used is either a String or a Uri instance.");
150+
}
136151

137152
Request request = new Request(methodToString(method), url);
138153
if (headers != null) request.headers.addAll(headers);
@@ -149,14 +164,7 @@ class HttpClientWithInterceptor extends http.BaseClient {
149164
}
150165
}
151166

152-
// Intercept request
153-
await _interceptRequest(request);
154-
155-
var stream = requestTimeout == null
156-
? await send(request)
157-
: await send(request).timeout(requestTimeout);
158-
159-
var response = await Response.fromStream(stream);
167+
var response = await _attemptRequest(request);
160168

161169
// Intercept response
162170
response = await _interceptResponse(response);
@@ -174,6 +182,37 @@ class HttpClientWithInterceptor extends http.BaseClient {
174182
throw new ClientException("$message.", url);
175183
}
176184

185+
Future<Response> _attemptRequest(Request request) async {
186+
var response;
187+
try {
188+
// Intercept request
189+
request = await _interceptRequest(request);
190+
191+
var stream = requestTimeout == null
192+
? await send(request)
193+
: await send(request).timeout(requestTimeout);
194+
195+
response = await Response.fromStream(stream);
196+
if (retryPolicy != null
197+
&& retryPolicy.maxRetryAttempts > _retryCount
198+
&& retryPolicy.shouldAttemptRetryOnResponse(response)) {
199+
_retryCount += 1;
200+
return _attemptRequest(request);
201+
}
202+
} catch (error) {
203+
if (retryPolicy != null
204+
&& retryPolicy.maxRetryAttempts > _retryCount
205+
&& retryPolicy.shouldAttemptRetryOnException(error)) {
206+
_retryCount += 1;
207+
return _attemptRequest(request);
208+
} else {
209+
throw HttpInterceptorException(error.toString());
210+
}
211+
}
212+
213+
return response;
214+
}
215+
177216
/// This internal function intercepts the request.
178217
Future<Request> _interceptRequest(Request request) async {
179218
for (InterceptorContract interceptor in interceptors) {

lib/http_with_interceptor.dart

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,15 +28,18 @@ import 'package:http_interceptor/interceptor_contract.dart';
2828
class HttpWithInterceptor {
2929
List<InterceptorContract> interceptors;
3030
Duration requestTimeout;
31+
RetryPolicy retryPolicy;
3132

3233
HttpWithInterceptor._internal({
3334
this.interceptors,
3435
this.requestTimeout,
36+
this.retryPolicy,
3537
});
3638

3739
factory HttpWithInterceptor.build({
3840
@required List<InterceptorContract> interceptors,
3941
Duration requestTimeout,
42+
RetryPolicy retryPolicy,
4043
}) {
4144
assert(interceptors != null);
4245

@@ -45,6 +48,7 @@ class HttpWithInterceptor {
4548
return new HttpWithInterceptor._internal(
4649
interceptors: interceptors,
4750
requestTimeout: requestTimeout,
51+
retryPolicy: retryPolicy,
4852
);
4953
}
5054

@@ -91,6 +95,7 @@ class HttpWithInterceptor {
9195
var client = new HttpClientWithInterceptor.build(
9296
interceptors: interceptors,
9397
requestTimeout: requestTimeout,
98+
retryPolicy: retryPolicy,
9499
);
95100
try {
96101
return await fn(client);
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
2+
3+
class HttpInterceptorException implements Exception {
4+
final message;
5+
6+
HttpInterceptorException([this.message]);
7+
8+
String toString() {
9+
if (message == null) return "Exception";
10+
return "Exception: $message";
11+
}
12+
}

lib/models/models.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
11
export 'request_data.dart';
22
export 'response_data.dart';
3+
export 'http_interceptor_exception.dart';
4+
export 'retry_policy.dart';

lib/models/request_data.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ class RequestData {
2323
}) : assert(method != null),
2424
assert(baseUrl != null);
2525

26-
String get url => addParametersToUrl(baseUrl, params);
26+
String get url => addParametersToStringUrl(baseUrl, params);
2727

2828
factory RequestData.fromHttpRequest(Request request) {
2929
var params = Map<String, String>();
@@ -42,7 +42,7 @@ class RequestData {
4242
}
4343

4444
Request toHttpRequest() {
45-
var reqUrl = Uri.parse(addParametersToUrl(baseUrl, params));
45+
var reqUrl = Uri.parse(addParametersToStringUrl(baseUrl, params));
4646

4747
Request request = new Request(methodToString(method), reqUrl);
4848

lib/models/retry_policy.dart

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import 'package:http/http.dart';
2+
3+
abstract class RetryPolicy {
4+
bool shouldAttemptRetryOnException(Exception reason) => false;
5+
bool shouldAttemptRetryOnResponse(Response response) => false;
6+
final int maxRetryAttempts = 1;
7+
}

0 commit comments

Comments
 (0)