Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 36 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,29 +6,32 @@
[![codecov](https://codecov.io/gh/CodingAleCR/http_interceptor/branch/master/graph/badge.svg)](https://codecov.io/gh/CodingAleCR/http_interceptor)
[![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)

A middleware library that lets you modify requests and responses if desired. Based of on [http_middleware](https://github.com/TEDConsulting/http_middleware)
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.

## Getting Started
## Quick Reference

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.
- [Installation](#installation)
- [Usage](#usage)
- [Building your own interceptor](#building-your-own-interceptor)
- [Using your interceptor](#using-your-interceptor)
- [Retrying requests](#retrying-requests)
- [Having trouble? Fill an issue](#troubleshooting)

### Installing
## Installation

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

```dart
http_interceptor: any
```

### Importing
## Usage

```dart
import 'package:http_interceptor/http_interceptor.dart';
```

### Using `http_interceptor`

#### Building your own interceptor
### Building your own interceptor

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.

Expand Down Expand Up @@ -72,11 +75,11 @@ class WeatherApiInterceptor implements InterceptorContract {
}
```

#### Using your interceptor
### Using your interceptor

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. ;)

##### Using interceptors with Client
#### Using interceptors with Client

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

Expand Down Expand Up @@ -107,7 +110,7 @@ class WeatherRepository {
}
```

##### Using interceptors without Client
#### Using interceptors without Client

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

Expand Down Expand Up @@ -137,6 +140,27 @@ class WeatherRepository {
}
```

### Issue Reporting
### Retrying requests

**(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`.

```dart
class ExpiredTokenRetryPolicy extends RetryPolicy {
@override
bool shouldAttemptRetryOnResponse(Response response) {
if (response.statusCode == 401) {
// Perform your token refresh here.

return true;
}

return false;
}
}
```

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.

## Troubleshooting

Open an issue and tell me, I will be happy to help you out as soon as I can.
53 changes: 43 additions & 10 deletions lib/http_client_with_interceptor.dart
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,21 @@ import 'http_methods.dart';
class HttpClientWithInterceptor extends http.BaseClient {
List<InterceptorContract> interceptors;
Duration requestTimeout;
RetryPolicy retryPolicy;

final Client _client = Client();
int _retryCount = 0;

HttpClientWithInterceptor._internal({this.interceptors, this.requestTimeout});
HttpClientWithInterceptor._internal({
this.interceptors,
this.requestTimeout,
this.retryPolicy,
});

factory HttpClientWithInterceptor.build({
@required List<InterceptorContract> interceptors,
Duration requestTimeout,
RetryPolicy retryPolicy,
}) {
assert(interceptors != null);

Expand All @@ -53,6 +60,7 @@ class HttpClientWithInterceptor extends http.BaseClient {
return HttpClientWithInterceptor._internal(
interceptors: interceptors,
requestTimeout: requestTimeout,
retryPolicy: retryPolicy,
);
}

Expand Down Expand Up @@ -137,7 +145,8 @@ class HttpClientWithInterceptor extends http.BaseClient {
} else if (url is Uri) {
url = addParametersToUrl(url, params);
} else {
throw HttpInterceptorException("Malformed URL parameter");
throw HttpInterceptorException(
"Malformed URL parameter. Check that the url used is either a String or a Uri instance.");
}

Request request = new Request(methodToString(method), url);
Expand All @@ -155,14 +164,7 @@ class HttpClientWithInterceptor extends http.BaseClient {
}
}

// Intercept request
request = await _interceptRequest(request);

var stream = requestTimeout == null
? await send(request)
: await send(request).timeout(requestTimeout);

var response = await Response.fromStream(stream);
var response = await _attemptRequest(request);

// Intercept response
response = await _interceptResponse(response);
Expand All @@ -180,6 +182,37 @@ class HttpClientWithInterceptor extends http.BaseClient {
throw new ClientException("$message.", url);
}

Future<Response> _attemptRequest(Request request) async {
var response;
try {
// Intercept request
request = await _interceptRequest(request);

var stream = requestTimeout == null
? await send(request)
: await send(request).timeout(requestTimeout);

response = await Response.fromStream(stream);
if (retryPolicy != null
&& retryPolicy.maxRetryAttempts > _retryCount
&& retryPolicy.shouldAttemptRetryOnResponse(response)) {
_retryCount += 1;
return _attemptRequest(request);
}
} catch (error) {
if (retryPolicy != null
&& retryPolicy.maxRetryAttempts > _retryCount
&& retryPolicy.shouldAttemptRetryOnException(error)) {
_retryCount += 1;
return _attemptRequest(request);
} else {
throw HttpInterceptorException(error.toString());
}
}

return response;
}

/// This internal function intercepts the request.
Future<Request> _interceptRequest(Request request) async {
for (InterceptorContract interceptor in interceptors) {
Expand Down
5 changes: 5 additions & 0 deletions lib/http_with_interceptor.dart
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,18 @@ import 'package:http_interceptor/interceptor_contract.dart';
class HttpWithInterceptor {
List<InterceptorContract> interceptors;
Duration requestTimeout;
RetryPolicy retryPolicy;

HttpWithInterceptor._internal({
this.interceptors,
this.requestTimeout,
this.retryPolicy,
});

factory HttpWithInterceptor.build({
@required List<InterceptorContract> interceptors,
Duration requestTimeout,
RetryPolicy retryPolicy,
}) {
assert(interceptors != null);

Expand All @@ -45,6 +48,7 @@ class HttpWithInterceptor {
return new HttpWithInterceptor._internal(
interceptors: interceptors,
requestTimeout: requestTimeout,
retryPolicy: retryPolicy,
);
}

Expand Down Expand Up @@ -91,6 +95,7 @@ class HttpWithInterceptor {
var client = new HttpClientWithInterceptor.build(
interceptors: interceptors,
requestTimeout: requestTimeout,
retryPolicy: retryPolicy,
);
try {
return await fn(client);
Expand Down
3 changes: 2 additions & 1 deletion lib/models/models.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export 'request_data.dart';
export 'response_data.dart';
export 'http_interceptor_exception.dart';
export 'http_interceptor_exception.dart';
export 'retry_policy.dart';
7 changes: 7 additions & 0 deletions lib/models/retry_policy.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import 'package:http/http.dart';

abstract class RetryPolicy {
bool shouldAttemptRetryOnException(Exception reason) => false;
bool shouldAttemptRetryOnResponse(Response response) => false;
final int maxRetryAttempts = 1;
}