Skip to content
Open
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
8 changes: 5 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,12 @@

build/


.atom/
.idea
.vscode
packages/**/ios/
packages/**/android/
doc/
doc/api/
pubspec.lock
.flutter-plugins
*.iml
Expand All @@ -21,4 +20,7 @@ coverage/
*.log
flutter_export_environment.sh
!packages/**/example/ios/
!packages/**/example/android/
!packages/**/example/android/

# FVM Version Cache
.fvm/
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
# Changelog

## 3.0.0-beta.1

- **Breaking**: Rebuild from scratch. Not backwards compatible with 2.x.
- **Added**: `HttpInterceptor` interface (replaces `InterceptorContract`). Same four methods with `FutureOr` support.
- **Added**: `InterceptedClient.build(...)` and `InterceptedHttp.build(...)` with `interceptors`, `client`, `retryPolicy`, `requestTimeout`, `onRequestTimeout`.
- **Added**: Optional `params` and `paramsAll` on `get`, `post`, `put`, `patch`, `delete`, `head`; merged into URL query.
- **Added**: `String.toUri()` extension. `Uri.addQueryParams(params: ..., paramsAll: ...)` extension.
- **Added**: `Response` JSON decoding extension (`response.jsonMap`, `response.jsonList`, `response.jsonBody`, etc.).
- **Added**: Conditional export `http_interceptor_io.dart` for `IOClient` (VM/mobile/desktop; do not use on web).
- **Removed**: `RequestData`/`ResponseData`. Use `BaseRequest`/`BaseResponse` only; no `copyWith` in core.
- **Removed**: Dependencies `qs_dart` and `validators` (not used in v3).

## 2.0.0

* feat: Simplify configuration of delay between retries by @jonasschaude in <https://github.com/CodingAleCR/http_interceptor/pull/122>
Expand Down
238 changes: 116 additions & 122 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ This is a plugin that lets you intercept the different requests and responses fr

## Quick Reference

**Already using `http_interceptor`? Check out the [1.0.0 migration guide](./guides/migration_guide_1.0.0.md) for quick reference on the changes made and how to migrate your code.**
**Upgrading from 2.x? See the [3.0.0 migration guide](./guides/migration_guide_3.0.0.md).**

- [http\_interceptor](#http_interceptor)
- [Quick Reference](#quick-reference)
Expand All @@ -29,6 +29,8 @@ This is a plugin that lets you intercept the different requests and responses fr
- [Using your interceptor](#using-your-interceptor)
- [Using interceptors with Client](#using-interceptors-with-client)
- [Using interceptors without Client](#using-interceptors-without-client)
- [Working with JSON responses](#working-with-json-responses)
- [Decoding responses into models](#decoding-responses-into-models)
- [Retrying requests](#retrying-requests)
- [Using self signed certificates](#using-self-signed-certificates)
- [InterceptedClient](#interceptedclient)
Expand All @@ -51,7 +53,8 @@ http_interceptor: <latest>
- 🚦 Intercept & change unstreamed requests and responses.
- ✨ Retrying requests when an error occurs or when the response does not match the desired (useful for handling custom error responses).
- 👓 `GET` requests with separated parameters.
- ⚡️ Standard `bodyBytes` on `ResponseData` to encode or decode in the desired format.
- ⚡️ Standard `Response.bodyBytes` for encoding or decoding as needed.
- 📦 Convenience helpers to decode JSON responses and map them into your own models.
- 🙌🏼 Array parameters on requests.
- 🖋 Supports self-signed certificates (except on Flutter Web).
- 🍦 Compatible with vanilla Dart projects or Flutter projects.
Expand All @@ -67,114 +70,57 @@ import 'package:http_interceptor/http_interceptor.dart';

### 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 four methods:
Implement `HttpInterceptor` to add logging, headers, error handling, and more. The interface has four methods:

- `interceptRequest`, which triggers before the http request is called
- `interceptResponse`, which triggers after the request is called, it has a response attached to it which the corresponding to said request;

- `shouldInterceptRequest` and `shouldInterceptResponse`, which are used to determine if the request or response should be intercepted or not. These two methods are optional as they return `true` by default, but they can be useful if you want to conditionally intercept requests or responses based on certain criteria.
- **interceptRequest** – runs before the request is sent. Return the (possibly modified) request.
- **interceptResponse** – runs after the response is received. Return the (possibly modified) response.
- **shouldInterceptRequest** / **shouldInterceptResponse** – return `false` to skip interception for that request/response (default `true`).

You could use this package 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.
All methods support `FutureOr` so you can use sync or async. Modify the request/response in place and return it, or return a new instance.

All four methods use `FutureOr` syntax, which makes it easier to support both synchronous and asynchronous behaviors.

- Logging with interceptor:
- Logging interceptor:

```dart
class LoggerInterceptor extends InterceptorContract {
class LoggerInterceptor implements HttpInterceptor {
@override
BaseRequest interceptRequest({
required BaseRequest request,
}) {
BaseRequest interceptRequest({required BaseRequest request}) {
print('----- Request -----');
print(request.toString());
print(request.headers.toString());
return request;
}

@override
BaseResponse interceptResponse({
required BaseResponse response,
}) {
log('----- Response -----');
log('Code: ${response.statusCode}');
BaseResponse interceptResponse({required BaseResponse response}) {
print('----- Response -----');
print('Code: ${response.statusCode}');
if (response is Response) {
log((response).body);
print(response.body);
}
return response;
}
}
```

- Changing headers with interceptor:

```dart
class WeatherApiInterceptor implements InterceptorContract {
@override
FutureOr<BaseRequest> interceptRequest({required BaseRequest request}) async {
try {
request.url.queryParameters['appid'] = OPEN_WEATHER_API_KEY;
request.url.queryParameters['units'] = 'metric';
request.headers[HttpHeaders.contentTypeHeader] = "application/json";
} catch (e) {
print(e);
}
return request;
}

@override
BaseResponse interceptResponse({
required BaseResponse response,
}) =>
response;

@override
FutureOr<bool> shouldInterceptRequest({required BaseRequest request}) async {
// You can conditionally intercept requests here
return true; // Intercept all requests
}

@override
FutureOr<bool> shouldInterceptResponse({required BaseResponse response}) async {
// You can conditionally intercept responses here
return true; // Intercept all responses
}
}
```

- You can also react to and modify specific types of requests and responses, such as `StreamedRequest`,`StreamedResponse`, or `MultipartRequest` :
- Adding headers / query params (in-place mutation):

```dart
class MultipartRequestInterceptor implements InterceptorContract {
@override
FutureOr<BaseRequest> interceptRequest({required BaseRequest request}) async {
if(request is MultipartRequest){
request.fields['app_version'] = await PackageInfo.fromPlatform().version;
}
return request;
}

@override
FutureOr<BaseResponse> interceptResponse({required BaseResponse response}) async {
if(response is StreamedResponse){
response.stream.asBroadcastStream().listen((data){
print(data);
});
}
return response;
}

class WeatherApiInterceptor implements HttpInterceptor {
@override
FutureOr<bool> shouldInterceptRequest({required BaseRequest request}) async {
// You can conditionally intercept requests here
return true; // Intercept all requests
BaseRequest interceptRequest({required BaseRequest request}) {
final url = request.url.replace(
queryParameters: {
...request.url.queryParameters,
'appid': apiKey,
'units': 'metric',
},
);
return Request(request.method, url)
..headers.addAll(request.headers)
..headers[HttpHeaders.contentTypeHeader] = 'application/json';
}

@override
FutureOr<bool> shouldInterceptResponse({required BaseResponse response}) async {
// You can conditionally intercept responses here
return true; // Intercept all responses
}
BaseResponse interceptResponse({required BaseResponse response}) => response;
}
```

Expand All @@ -190,24 +136,20 @@ Here is an example with a repository using the `InterceptedClient` class.

```dart
class WeatherRepository {
Client client = InterceptedClient.build(interceptors: [
WeatherApiInterceptor(),
]);
final client = InterceptedClient.build(
interceptors: [WeatherApiInterceptor()],
);

Future<Map<String, dynamic>> fetchCityWeather(int id) async {
var parsedWeather;
try {
final response =
await client.get("$baseUrl/weather".toUri(), params: {'id': "$id"});
if (response.statusCode == 200) {
parsedWeather = json.decode(response.body);
} else {
throw Exception("Error while fetching. \n ${response.body}");
}
} catch (e) {
print(e);
final response = await client.get(
'$baseUrl/weather'.toUri(),
params: {'id': '$id'},
);
if (response.statusCode == 200) {
// Built-in Response JSON helpers:
return response.jsonMap;
}
return parsedWeather;
throw Exception('Error while fetching.\\n${response.body}');
}

}
Expand All @@ -223,32 +165,84 @@ Here is an example with a repository using the `InterceptedHttp` class.
class WeatherRepository {

Future<Map<String, dynamic>> fetchCityWeather(int id) async {
var parsedWeather;
try {
final http = InterceptedHttp.build(interceptors: [
WeatherApiInterceptor(),
]);
final response =
await http.get("$baseUrl/weather".toUri(), params: {'id': "$id"});
if (response.statusCode == 200) {
parsedWeather = json.decode(response.body);
} else {
return Future.error(
"Error while fetching.",
StackTrace.fromString("${response.body}"),
);
}
} on SocketException {
return Future.error('No Internet connection 😑');
} on FormatException {
return Future.error('Bad response format 👎');
} on Exception {
return Future.error('Unexpected error 😢');
final http = InterceptedHttp.build(interceptors: [WeatherApiInterceptor()]);
final response = await http.get(
'$baseUrl/weather'.toUri(),
params: {'id': '$id'},
);
if (response.statusCode == 200) {
// Built-in Response JSON helpers:
return response.jsonMap;
}
return Future.error(
'Error while fetching.',
StackTrace.fromString(response.body),
);
}

}
```

### Working with JSON responses

The `ResponseBodyDecoding` extension adds a few lightweight helpers on `Response` for common JSON use cases:

```dart
final response = await client.get(
'$baseUrl/weather'.toUri(),
params: {'id': '$id'},
);

// Dynamically-typed JSON value (Map/List/primitive or null on empty body).
final Object? json = response.jsonBody;

// JSON object as a map (throws if body is empty or not a JSON object).
final Map<String, dynamic> data = response.jsonMap;

// JSON array as a list (throws if body is empty or not a JSON array).
final List<dynamic> items = response.jsonList;
```

### Decoding responses into models

The `ResponseBodyDecoding` extension provides helpers to turn JSON responses into strongly-typed models with minimal boilerplate.

```dart
class Weather {
final String description;
final double temperature;

const Weather({
required this.description,
required this.temperature,
});

factory Weather.fromJson(Map<String, dynamic> json) {
return Weather(
description: (json['weather'] as List).first['description'] as String,
temperature: (json['main']['temp'] as num).toDouble(),
);
}
}

Future<Weather> fetchCityWeather(int id) async {
final client = InterceptedClient.build(
interceptors: [WeatherApiInterceptor()],
);

final response = await client.get(
'$baseUrl/weather'.toUri(),
params: {'id': '$id'},
);

return parsedWeather;
if (response.statusCode == 200) {
// Use the built-in JSON mapper:
return response.decodeJson(
(json) => Weather.fromJson(json as Map<String, dynamic>),
);
}

throw Exception('Error while fetching.\n${response.body}');
}
```

Expand Down
18 changes: 18 additions & 0 deletions doc/decisions/000-template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Title

Date: YYYY-MM-DD

Status: proposed | rejected | accepted | deprecated | … | superseded by
[0005](0005-example.md)

## Context

<!-- Explain the overall context of the decision, the problem it attempts to solve, examples of why it might need solving. You can even add possible solutions -->

## Decision

<!-- Explain the decision that was taken. Why it was taken -->

## Consequences

<!-- Describe the consequences of this decision, deprecations, new features, removed code, etc. -->
Loading
Loading