GithubHelp home page GithubHelp logo

codingalecr / http_interceptor Goto Github PK

View Code? Open in Web Editor NEW
132.0 5.0 65.0 336 KB

A lightweight, simple plugin that allows you to intercept request and response objects and modify them if desired.

License: MIT License

Objective-C 0.02% Dart 97.82% Kotlin 0.08% Swift 0.26% HTML 0.96% Ruby 0.86%
flutter plugin dart-library dart http-interceptor retrying-requests open-source http http-requests interceptors

http_interceptor's Introduction

http_interceptor

Pub style: lints/recommended License: MIT codecov Star on GitHub

All Contributors

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.

Quick Reference

Already using http_interceptor? Check out the 1.0.0 migration guide for quick reference on the changes made and how to migrate your code.

Installation

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

http_interceptor: <latest>

Features

  • 🚦 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.
  • 🙌🏼 Array parameters on requests.
  • 🖋 Supports self-signed certificates (except on Flutter Web).
  • 🍦 Compatible with vanilla Dart projects or Flutter projects.
  • 🎉 Null-safety.
  • ⏲ Timeout configuration with duration and timeout functions.
  • ⏳ Configure the delay for each retry attempt.

Usage

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

  • Logging with interceptor:
class LoggerInterceptor extends InterceptorContract {
  @override
  Future<BaseRequest> interceptRequest({
    required BaseRequest request,
  }) async {
    print('----- Request -----');
    print(request.toString());
    print(request.headers.toString());
    return request;
  }

  @override
  Future<BaseResponse> interceptResponse({
    required BaseResponse response,
  }) async {
    log('----- Response -----');
    log('Code: ${response.statusCode}');
    if (response is Response) {
      log((response).body);
    }
    return response;
  }
}
  • Changing headers with interceptor:
class WeatherApiInterceptor implements InterceptorContract {
  @override
  Future<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
  Future<BaseResponse> interceptResponse({required BaseResponse response}) async => response;
}
  • You can also react to and modify specific types of requests and responses, such as StreamedRequest,StreamedResponse, or MultipartRequest :
class MultipartRequestInterceptor implements InterceptorContract {
  @override
  Future<BaseRequest> interceptRequest({required BaseRequest request}) async {
    if(request is MultipartRequest){
      request.fields['app_version'] = await PackageInfo.fromPlatform().version;
    }
    return request;
  }

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

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 InterceptedHttp to do separate connections for different requests or using a InterceptedClient 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

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

Here is an example with a repository using the InterceptedClient class.

class WeatherRepository {
  Client 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);
    }
    return parsedWeather;
  }

}

Using interceptors without Client

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

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 😢');
    }

    return parsedWeather;
  }

}

Retrying requests

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.

class ExpiredTokenRetryPolicy extends RetryPolicy {
  @override
  Future<bool> shouldAttemptRetryOnResponse(BaseResponse response) async {
    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.

Sometimes it is helpful to have a cool-down phase between multiple requests. This delay could for example also differ between the first and the second retry attempt as shown in the following example.

class ExpiredTokenRetryPolicy extends RetryPolicy {
  @override
  Duration delayRetryAttemptOnResponse({required int retryAttempt}) {
    return const Duration(milliseconds: 250) * math.pow(2.0, retryAttempt);
  }
}

Using self signed certificates

You can achieve support for self-signed certificates by providing InterceptedHttp or InterceptedClient with the client parameter when using the build method on either of those, it should look something like this:

InterceptedClient

Client client = InterceptedClient.build(
  interceptors: [
    WeatherApiInterceptor(),
  ],
  client: IOClient(
    HttpClient()
      ..badCertificateCallback = badCertificateCallback
      ..findProxy = findProxy,
  );
);

InterceptedHttp

final http = InterceptedHttp.build(
  interceptors: [
    WeatherApiInterceptor(),
  ],
  client: IOClient(
    HttpClient()
      ..badCertificateCallback = badCertificateCallback
      ..findProxy = findProxy,
  );
);

Note: It is important to know that since both HttpClient and IOClient are part of dart:io package, this will not be a feature that you can perform on Flutter Web (due to BrowserClient and browser limitations).

Roadmap

Check out our roadmap here.

We migrated our roadmap to better suit the needs for development since we use ClickUp as our task management tool.

Troubleshooting

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

Contributions

Contributions are always welcomed and encouraged, we will always give you credit for your work on this section. If you are interested in maintaining the project on a regular basis drop me a line at [email protected].

Contributors

Thanks to all the wonderful people contributing to improve this package. Check the Emoji Key for reference on what means what!


Alejandro Ulate Fallas

💻 📖 ⚠️ 🤔 🚧

Konstantin Serov

🤔

Virus1908

🤔 💻 ⚠️

Wes Ehrlichman

🤔 💻 ⚠️

Jan Lübeck

🤔 💻 ⚠️

Lucas Alves

🤔 💻 ⚠️

István Juhos

🤔 💻 ⚠️

Scott Hyndman

🤔

Islam Akhrarov

🤔 ⚠️ 💻

Meysam

📖

Martijn

⚠️ 💻

MaciejZuk

🐛

Lukas Kurz

⚠️ 🤔 💻

Glenn Ruysschaert

💻 ⚠️

Erick

💻 ⚠️

javiermrz

💻

nihar

🤔

http_interceptor's People

Contributors

allcontributors[bot] avatar asynchronysuperwes avatar codingalecr avatar javiermrz avatar jlubeck avatar jonasschaude avatar lucalves avatar lukaskurz avatar mauryagaurav947 avatar mawi137 avatar meysammahfouzi avatar stewemetal avatar vixez avatar wilinz avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

http_interceptor's Issues

Http Retry Policy not working

shouldAttemptRetryOnResponse never triggered

class ExpiredTokenRetryPolicy extends RetryPolicy {
  @override
  bool shouldAttemptRetryOnResponse(http.Response response) {
    log('TESTTSTSTS' + response.statusCode.toString());
    if (response.statusCode == 401) {
      return true;
    }
    return false;
  }
}

static http.Client client = HttpClientWithInterceptor
    .build(
      interceptors: [ApiInterceptor()],
      retryPolicy: ExpiredTokenRetryPolicy()
    );

static Future<List<Event>> getEvents() async {
    var url = baseUrl + "/event";
    return client.get(url, headers: API.headers)
      .then((response) {
        Iterable list = json.decode(response.body)['data']['events'];
        return list.map((model) => Event.fromJson(model)).toList();
      })
      .catchError((err) {
        log('error: $err');
      });
  }

I'm using dart for apps

Retry policy may got NoSuchMethodError issue

I was trying to get 401 from server(i got no token when i requested). But the application did not went i expected. It passed all exception handlers i used so i checked the log and it says "type NoSuchMethodError is not type of Exception". After debugging, i noticed that retry policy is causing this issue. So now i temporarily removed retry policy with that api request. I was using firebase authentication social for login. So i request firebase auth in retry.

Migration guide link links to 404

Hi, I'm trying to migrate our app to 1.0.0. Clicking to Migration guide link inks to page not found error 404.

I appreciate if you can direct me to your migration guide. Thanks in advance.

How to use updated token in the repeating request?

I have been using http and http_interceptor for networking in the flutter application. In the request, I am adding token header from the SecureStorage. Now once the token expires(401 error), I refresh the token, update the token in SecureStorage. But when the request gets repeated old token is used instead of the new one

Feature: Add array support in query params

Is your feature request related to a problem? Please describe.
Sending array as part of parameters.

Describe the solution you'd like
Add array support to parameters

Describe alternatives you've considered
json encoding/decoding params beforehand, but it does not always work as expected

Additional context
See #56 for implementation on previous version of the plugin

Bug: HTTP Methods have misaligned parameters

Hi, I have noticed that for Http methods that doesn't need query parameters (POST, PUT, PATCH, etc.), the body parameter is being assigned to params in _sendUnstreamed function instead. Refer to the example below:

Future<Response> post(url,
          {Map<String, String> headers, body, Encoding encoding}) =>
      _sendUnstreamed("POST", url, headers, body, encoding);

For the post function above, the body parameter is being passed to params, causing type mismatch exceptions when being used.

This is the _sendUnstreamed function for reference:

Future<Response> _sendUnstreamed(String method, url,
      Map<String, String> headers, Map<String, String> params,
      [body, Encoding encoding]) async {
    if (url is String) {
      var paramUrl = url;
      if (params != null && params.length > 0) {
        paramUrl += "?";
        params.forEach((key, value) {
          paramUrl += "$key=$value&";
        });
        paramUrl = paramUrl.substring(0, paramUrl.length); // to remove the last '&' character
      }
      url = Uri.parse(paramUrl);
    }

    var request = new Request(method, url);

    if (headers != null) request.headers.addAll(headers);
    if (encoding != null) request.encoding = encoding;
    if (body != null) {
      if (body is String) {
        request.body = body;
      } else if (body is List) {
        request.bodyBytes = body.cast<int>();
      } else if (body is Map) {
        request.bodyFields = body.cast<String, String>();
      } else {
        throw new ArgumentError('Invalid request body "$body".');
      }
    }

    //Perform request interception
    for (InterceptorContract interceptor in interceptors) {
      RequestData interceptedData = await interceptor.interceptRequest(
        data: RequestData.fromHttpRequest(request),
      );
      request = interceptedData.toHttpRequest();
    }

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

    var response = await Response.fromStream(stream);

    var responseData = ResponseData.fromHttpResponse(response);
    for (InterceptorContract interceptor in interceptors) {
      responseData = await interceptor.interceptResponse(data: responseData);
    }

    return responseData.toHttpResponse();
  }

Can I use http_interceptor in flutter web?

Hello, I've succesfully used this library for a mobile app.
I'm trying to use it in a web app but it gives me error when I make a request with interceptor.

I would like to know if is not provided for the web version or if is another type of error.

Thank you in advance.

this is the stack:

onError Expected a value of type 'Exception', but got one of type 'UnsupportedError'
Error: Unhandled error Expected a value of type 'Exception', but got one of type 'UnsupportedError' occurred in Instance of 'AuthenticationBloc'.
C:/b/s/w/ir/cache/builder/src/out/host_debug/dart-sdk/lib/internal/js_dev_runtime/private/ddc_runtime/errors.dart 212:49 throw
C:/b/s/w/ir/cache/builder/src/out/host_debug/dart-sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/errors.dart 60:3 castError
C:/b/s/w/ir/cache/builder/src/out/host_debug/dart-sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/operations.dart 442:10 cast
C:/b/s/w/ir/cache/builder/src/out/host_debug/dart-sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/types.dart 411:9 as
packages/http_interceptor/http_client_with_interceptor.dart 222:23 _attemptRequest$
C:/b/s/w/ir/cache/builder/src/out/host_debug/dart-sdk/lib/_internal/js_dev_runtime/patch/async_patch.dart 45:50
C:/b/s/w/ir/cache/builder/src/out/host_debug/dart-sdk/lib/async/zone.dart 1450:54 runUnary
C:/b/s/w/ir/cache/builder/src/out/host_debug/dart-sdk/lib/async/future_impl.dart 143:18 handleValue
C:/b/s/w/ir/cache/builder/src/out/host_debug/dart-sdk/lib/async/future_impl.dart 696:44 handleValueCallbackC:/b/s/w/ir/cache/builder/src/out/host_debug/dart-sdk/lib/async/future_impl.dart 725:32
_propagateToListeners
C:/b/s/w/ir/cache/builder/src/out/host_debug/dart-sdk/lib/async/future_impl.dart 393:9 callback
C:/b/s/w/ir/cache/builder/src/out/host_debug/dart-sdk/lib/async/schedule_microtask.dart 41:11 _microtaskLoop
C:/b/s/w/ir/cache/builder/src/out/host_debug/dart-sdk/lib/async/schedule_microtask.dart 50:5 _startMicrotaskLoopC:/b/s/w/ir/cache/builder/src/out/host_debug/dart-sdk/lib/_internal/js_dev_runtime/patch/async_patch.dart 166:15

at Object.throw_ [as throw] (http://localhost:56368/dart_sdk.js:4334:11)
at http://localhost:56368/packages/bloc/src/cubit.dart.lib.js:105:21
at authentication_bloc.AuthenticationBloc.new.onError (http://localhost:56368/packages/bloc/src/cubit.dart.lib.js:106:26)
at authentication_bloc.AuthenticationBloc.new.onError (http://localhost:56368/packages/bloc/src/cubit.dart.lib.js:213:15)
at _RootZone.runBinaryGuarded (http://localhost:56368/dart_sdk.js:37400:11)
at sendError (http://localhost:56368/dart_sdk.js:31008:26)
at _BroadcastSubscription.new.[_sendError] (http://localhost:56368/dart_sdk.js:31025:11)
at _BroadcastSubscription.new.[_addError] (http://localhost:56368/dart_sdk.js:30948:27)
at http://localhost:56368/dart_sdk.js:31678:34
at _SyncBroadcastStreamController.new.[_forEachListener] (http://localhost:56368/dart_sdk.js:31524:13)
at _SyncBroadcastStreamController.new.[_sendError] (http://localhost:56368/dart_sdk.js:31676:31)
at _SyncBroadcastStreamController.new.[_addError] (http://localhost:56368/dart_sdk.js:31501:25)
at _RootZone.runBinaryGuarded (http://localhost:56368/dart_sdk.js:37400:11)
at sendError (http://localhost:56368/dart_sdk.js:31008:26)
at _ForwardingStreamSubscription.new.[_sendError] (http://localhost:56368/dart_sdk.js:31025:11)
at _ForwardingStreamSubscription.new.[_addError] (http://localhost:56368/dart_sdk.js:30948:27)
at _ForwardingStreamSubscription.new.[_addError] (http://localhost:56368/dart_sdk.js:35112:25)
at _MapStream.new.[_handleError] (http://localhost:56368/dart_sdk.js:35066:24)
at _ForwardingStreamSubscription.new.[_handleError] (http://localhost:56368/dart_sdk.js:35138:38)
at _RootZone.runBinaryGuarded (http://localhost:56368/dart_sdk.js:37400:11)
at sendError (http://localhost:56368/dart_sdk.js:31008:26)
at _ControllerSubscription.new.[_sendError] (http://localhost:56368/dart_sdk.js:31025:11)
at async._DelayedError.new.perform (http://localhost:56368/dart_sdk.js:34393:27)
at _StreamImplEvents.new.handleNext (http://localhost:56368/dart_sdk.js:34475:15)
at async._AsyncCallbackEntry.new.callback (http://localhost:56368/dart_sdk.js:34227:16)
at Object._microtaskLoop (http://localhost:56368/dart_sdk.js:37718:13)
at _startMicrotaskLoop (http://localhost:56368/dart_sdk.js:37724:13)
at http://localhost:56368/dart_sdk.js:33243:9

Can't intercept requests on 'send()' method

I am using an API that has an odd DELETE request endpoint where I have to pass a body to it, the default delete method doesn't have an option to pass a body so I have to create a custom request then pass it like this client.send(request), the problem is the interceptor doesn't seem to intercept these requests on the client, and so my default headers are left blank.

Is there a way to intercept this type of request without having to manually add the headers to the custom request?

[WEB] Unsupported operation: Platform._version

HttpClientWithInterceptor _httpClient;
...
await _httpClient.get();

Result:
Unsupported operation: Platform._version

Flutter 1.26.0-17.3.pre • channel beta • https://github.com/flutter/flutter.git
Framework • revision 4b50ca7f7f (8 days ago) • 2021-02-04 19:44:27 -0800       
Engine • revision 2c527d6c7e
Tools • Dart 2.12.0 (build 2.12.0-259.8.beta)

http_interceptor: ^0.3.3

Related to flutter/flutter#39998.

<getObject: NoSuchMethodError: The getter 'uri' was called on null.>
http_client_with_interceptor.dart

Parameters are not added to Uri.

When using an Uri object as url and also a separate parameters map, the parameters are not being added to the Uri in the same way that they are added if a string url is used instead.

interceptResponse, missing response headers

Hi,

I have some trouble, I'd like to retrieve custom header into the interceptresponse but the ResponseData object reveal only one header. Look at the screenshot. I'm searching for the Token-AuthorizationUser-Expired header and it's missing. The data contains only one header instead of 9 in the chrome network tab.

Thanks for your help.

  • Flutter version: 2.5.3
  • IDE : VS Code
  • http_interceptor Version 1.0.2

image

Is there any way to mock http response?

Hello,
I want to mock http response.

Like MockClient. (in http package)

import 'package:http/http.dart';
import 'package:http/testing.dart';

final MockClient mockClient = MockClient((request) async {
  if (request.url.path == '/foo') {
    return Response('foo', 200);
  }
  return Response('bar', 200);
}

But _client instance in HttpClientWithInterceptor is final, private and initialized in field, so it's not injectable.

Then, I tried to mock with InterceptorContract.

But interceptRequest can't modify response.
And interceptResponse can't stop request, so (real) request thrown.
I want to mock without connecting to the internet.

Is there any way to mock http response?

(If I can't, I request feature to inject client instance.)

Sorry for my poor English, Thanks.

HttpClientWithInterceptor.send

Hello guys.
It looks like httpClientWithInterceptor.send(BaseRequest request) doesn't use interceptors, retry policies and request timeouts.
Is this a desired behavior?
Best regards

Missing parameters for DELETE method

Hello,

I have found one problem with DELETE method.

Intercepted_client.dart - line 144

@OverRide
Future delete(
Uri url, {
Map<String, String>? headers,
Object? body,
Encoding? encoding,
}) =>
_sendUnstreamed(
method: Method.DELETE,
url: url,
headers: headers,
);

You have missed pass two parameters (body and encoding) for method _sendUnstreamed inside delete method.

Could you please correct it?

Being able to pause requests

Is your feature request related to a problem? Please describe.
The app I'm building can fire multiple requests at once.
When one of the requests gets a 401 for an expired token, it updates the token in a RetryPolicy.
During this update, all other requests should be paused as a new token is being retrieved.

Describe the solution you'd like
Be able to pause requests in the queue, and resume them. For example pausing the queue when a new token is being retrieved.
After the token is updated, all requests in the queue should have their interceptRequest method called, so the request can be updated with the new token (if an old token is in the request).

An alternative would be that everything runs in a sequence, no simultaneous requests.

  • Request 1: Added to queue
  • Request 1: interceptRequest
  • Request 2: Added to queue
  • Request 1: 401 => RetryPolicy + pause all requests in the queue
  • Request 1: Asynchronously finish shouldAttemptRetryOnResponse (token in storage updated)
  • Request 1: Retry so > interceptRequest
  • Request 1: finish successfully
  • Request 2: interceptRequest
  • Request 2: finish successfully
  • ...

Can it be used with MultipartRequest?

How did you use multipart request using interceptor, i cant using interceptor with MultipartRequest() ?

final http = InterceptedHttp.build(interceptors: [Interceptor(),]);
var request = http.MultipartRequest("POST", uri);

The method 'MultipartRequest' isn't defined for the type 'InterceptedHttp'. (Documentation)

Modified params are not reflecting in api call.

Hello,
I am modifying my request url params in my interceptor but the modified params are not reflecting in my api call.

data.params["xyz"] = "mytoken";
this is how I am modifying the request params.
Please, help me with this.
Thanks.

Adding onError() method to the interceptor

Thanks for this awesome package. My use case involves re-authorization using refreshToken and this can be done if an error-code is intercepted and a new token request. This feature would be greatly appreciated. Thanks

How do i catch socket exceptions

I am trying to catch Socket exceptions in my service but it keeps slipping through and throwing the Exception block instead.

This is my implementation. Kindly advise

    try {
      http.Response response =
          await client.post('$baseUrl/auth/', body: login.toJson());
      Map<String, dynamic> data = jsonDecode(response.body);
      if (response.statusCode == 401) {
        throw Failure('Wrong username and password combination');
      }
      return data;
    } on HttpException catch (e) {
      throw Failure(e.toString());
    } on SocketException {
      throw Failure('No Internet connection 😑');
    } on FormatException {
      throw Failure('Bad response format 👎');
    } on Exception {
      throw Failure('Unexpected error 😢');
    }
class HttpInterceptor implements InterceptorContract {
  @override
  Future<RequestData> interceptRequest({RequestData data}) async {
    if (data.headers.containsKey('requireAuth')) {
      String accessToken = await _storage.read(key: 'access');
      data.headers.remove('requireAuth');
      data.headers['Authorization'] = 'Bearer $accessToken';
      data.headers["Content-Type"] = "application/json";
    }
    print(data.body);
    return data;
  }

  @override
  Future<ResponseData> interceptResponse({ResponseData data}) async {
    print(data.body);
    return data;
  }
}

Client _client = HttpClientWithInterceptor.build(
  interceptors: [
    HttpInterceptor(),
  ],
  retryPolicy: ExpiredTokenRetryPolicy(),
);

Client get client => _client;

Also how do I set this up for the exceptions to be handled globally

Make it work with Client.send()

Currently it doesn't seem to work whenever using the http client's 'send()' method, which makes the interceptor useless in those cases.

Feature Request: Query Parameters

Right now you have to do some funky stuff in order to use query parameters when doing a 'get' request. An example would be:

class WeatherRepository {
  Client client = HttpClientWithInterceptor.build(interceptors: [
      WeatherApiInterceptor(),
  ]);

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

class WeatherApiInterceptor implements InterceptorContract {
  @override
  Future<RequestData> interceptRequest({RequestData data}) async {
    try {
      data.url = "${data.url}&appid=$OPEN_WEATHER_API_KEY";
      data.url = "${data.url}&units=metric";
      data.headers["Content-Type"] = "application/json";
    } catch (e) {
      print(e);
    }
    return data;
  }

  @override
  Future<ResponseData> interceptResponse({ResponseData data}) async => data;
}

This is a big problem mainly because if you do not add the '?' to your URL in the repository then most the interception could fail to add the proper parameters resulting in an URL like so:

'$baseUrl/weather&appid=$appid&units=metric'

Instead of the desired:

'$baseUrl/weather?appid=$appid&units=metric'

Exclude specific calls from being intercepted/retried -- HeaderSanitizerContract proposal

Is your feature request related to a problem? Please describe.
Looking for a way to exclude interceptors and/or the retry policy I ended up using some custom headers (mainly necessary for the retry policy)
Es: DO_NOT_REPLY or UNAUTHENTICATED
But using them on a Flutter Web project ended up giving some CORS errors due to these custom headers.

Describe the solution you'd like
Added a HeaderSanitizerContract to remove custom utility headers before making the request

Describe alternatives you've considered
Another approach could be to add specific arguments to every method to exclude the specific call from being retried/intercepted

@CodingAleCR are you interested in a pull request?

https://github.com/fnicastri/http_interceptor/tree/main

Thanks,
Francesco

HandshakeException: Connection terminated during handshake

Obtengo la siguiente excepción cuando hago peticiones http, ¿Qué podría estar sucediendo? En sí la excepción se produce al realizar peticiones get.
Nota: esto no ocurre todo el tiempo, es rara vez que se presenta (VERSION: ^0.2.0)
image

También suele pasar esto:
image

El interceptior corresponde a este código:

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

class Interceptor implements InterceptorContract {

  @override
  Future<RequestData> interceptRequest({RequestData data}) async {
    if (_session != null) {
      data.headers['authorization'] = 'TOKEN';
      data.headers['content-type'] = 'application/json';
    }
    return data;
  }

  @override
  Future<ResponseData> interceptResponse({ResponseData data}) async {
    return data;
  }
}

Y en sí la petición http corresponde a este código

import 'package:http/http.dart' as http;
import 'package:http_interceptor/http_client_with_interceptor.dart';
import 'package:larvia/src/interceptor/interceptor.dart';

class ApiHelper {
  HttpClientWithInterceptor client = HttpClientWithInterceptor.build(interceptors: [
    Interceptor(),
  ]);

  Future<http.Response> get(String url, {Map<String, dynamic> params}) async {
    final Uri uri = Uri(scheme: 'https', host: 'domain', path: url, queryParameters: params);
    http.Response response = await client.get(uri);
    return response;
  }
}

Feature: Support for Multipart Requests

Getting error when trying to send multipart request like this. No header is added.

    var request = MultipartRequest(
      "POST",
      Uri.parse("url"),
    );
    var file = await MultipartFile.fromPath("name", "path");
    request.files.add(file);
    var response = await client.send(request);

How can I send multipart request with all headers added in interceptor?

InterceptedHttp get not working with int parameters

Describe the bug
I was trying to get an endpoint with int parameter:

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

I was wondering why id: 1 is not received on the server. After some investigation I realized that the parameter values are included in the requet only if they are string:

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

Expected behavior
I expect the parameters to be included even when they are not string:

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

Please complete the following information

  • Flutter version: Flutter 2.4.0-1.0.pre.60
  • IDE: Android Studio
  • http_interceptor Version 1.0.1

Is there a way to catch all socket exceptions in place for this package?

I use this package for intercepting all my requests and I need way to handle all internet exceptions in one place not in all repositories as this example shows

class WeatherRepository {

    Future<Map<String, dynamic>> fetchCityWeather(int id) async {
    var parsedWeather;
    try {
      WeatherApiInterceptor http = HttpWithInterceptor.build(interceptors: [
          Logger(),
      ]);
      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 😢');
    }

    return parsedWeather;
  }

}

can any one tell me how to at one place?

How to use as a reusable service ?

Hello,
I am looking to build an HttpInterceptor Service in dart, that would act the same way as a service I have built in Angular 2 :

@Injectable({
  providedIn: 'root'
})
export class InterceptorService implements HttpInterceptor {
  constructor(private auth: AuthService) { }
   intercept(
     req: HttpRequest<any>,
     next: HttpHandler
   ): Observable<HttpEvent<any>> [...]
}

In Angular 2 Ts, I am injecting this service in a CoreModule, and can therefore intercept all requests from all services in that module :

@NgModule({
  declarations: [
  ],
  imports: [
  ],
  providers: [
    {
       provide: HTTP_INTERCEPTORS,
       useClass: InterceptorService,
       multi: true
    },
 ]

By the examples, it seems the service has to be provided with each Http request.
Is there a way to achieve the pattern I am describing above ?

Many thanks.

Put request is bugged

This interceptor doesn't works good with type put requests. I had to debug a several hours to understand why my request doesn't send. I think there is a problem with the post requests too but i'm not sure of one hundred percents, it has to be tested.

Consider(?) removing flutter/foundation.dart dependency

Hi!

Currently, code that uses the library needs flutter run instead of dart run.
On dart run I get:

...
Error: Not found: 'dart:ui'
export 'dart:ui' show VoidCallback;

If run with flutter run all is good. Seems that dart:ui is not available in normal dart run...

Went through the code and it seems that if I remove flutter/foundation.dart imports all is well. Also, no @required is then available but that seems small price to pay, IMO at least.

What do you say, would you consider this change in your code?

Is this working with multipartrequests?

I need to work with a soap API and all their endpoints run over a multipartrequest. Is there a way to intercept those as well? I could not figure that out yet? Do you have a little example?

KR

Unit testing interceptors/retry policies

I'm building an http client with:

    final client = HttpClientWithInterceptor.build(
      interceptors: [
        MyApiInterceptor()
      ],
      retryPolicy: RefreshTokenRetryPolicy(),
    );

I'd like to mock a 401 via:

    test('test retry policy called twice', () async {
      when(apiClient.post('/path', body: {})).thenAnswer(
          (realInvocation) async => Response('Not Authorized', 401));

      final response = await apiClient.post('/path', body: {});

      expect(response.statusCode, 401);
      verify(tokenService.refresh()).called(2);
    });

But I'm getting an error: type 'Future<Response>' is not a subtype of type 'Future<String>' . Any examples or tips would be awesome!

interceptRequest never called

I am trying to put auth token inside of a call, but my request interceptor is never called:

final http.Client client = HttpClientWithInterceptor.build(interceptors: [
    ApiInterceptor(),
  ]);

  Future<http.Response> request(RequestMethod method,
      {String path, Map<String, String> headers, Object body}) async {
    final uri = _constructUri(path);

    var request = http.Request(method.value, uri);

    if (headers != null) request.headers.addAll(_generateHeaders(headers));
    if (body != null) {
      if (body is String) {
        request.body = body;
      } else if (body is List || body is Map<String, dynamic>) {
        request.body = jsonEncode(body);
      } else {
        throw ArgumentError('Invalid request body "$body".');
      }
    }
    final stream = await client.send(request);
    return http.Response.fromStream(stream);
  }

This is how interceptor looks like:

class ApiInterceptor implements InterceptorContract {
  @override
  Future<RequestData> interceptRequest({RequestData data}) async {
    print("Intercept call");
    final token = await getToken();
    try {
      data.params[HttpHeaders.authorizationHeader] = 'Bearer ${token}';
    } catch (e) {
      print(e);
    }
    return data;
  }

  @override
  Future<ResponseData> interceptResponse({ResponseData data}) async => data;
}

Any idea why? I need to be attached to client so every call passes through it.
Thanks

Adding the possibility to add a generic request timeout handler

First of all, thanks for this great library, really great work!

Is your feature request related to a problem? Please describe.
I find it weird that there is a request timeout option, which will throw an error, but there is not an option to handle that error (to be able to, for example, show a toast with a generic error text if the timeout throws).

Describe the solution you'd like
The solution is actually really easy and it only requires a couple lines. In intercepted_client.dart, in line 242 you have this:

await send(interceptedRequest).timeout(requestTimeout!);

Dart lets you override a Future timeout behaviour by adding a parameter:

await send(interceptedRequest).timeout(requestTimeout!, onTimeout: _onTimeout);

and then you would only have to accept this new parameter in the build method:

InterceptedClient.build(
  ...
  requestTimeout: Duration(seconds:20),
  onRequestTimeout: Function()
)

Issue with Retrying requests with protobuf as response

The _attemptRequest method checks if the retry policy is configured, and if yes it creates a response clone using ResponseData.fromHttpResponse(response) which tries to read response body as string (response.body). This will not work if the response body is a protobuf byte array.

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 &&
          await retryPolicy.shouldAttemptRetryOnResponse(
              ResponseData.fromHttpResponse(response))) {
        _retryCount += 1;
        return _attemptRequest(request);
      }
    } catch (error) {
      if (retryPolicy != null &&
          retryPolicy.maxRetryAttempts > _retryCount &&
          retryPolicy.shouldAttemptRetryOnException(error)) {
        _retryCount += 1;
        return _attemptRequest(request);
      } else {
        rethrow;
      }
    }

    _retryCount = 0;
    return response;
  }

BaseRequest field is missing in ResponseData

In mapper method Response toHttpResponse() and factory factory ResponseData.fromHttpResponse(Response response) , mapping BaseRequest is missing.
P.s. in Response toHttpResponse() method maps to Request only method and Uri. But headers and bodies are missing

Question: Retry policy

Thanks for this great addition to http lib.

I have a question: do we have to call synchronous function to get new token after 401 in RetryPolicy?
I saw dio enables us to call refresh in async so it's convenient for Futures.

Thanks!

Any chance for unbinding it from Flutter and making it a pure Dart package?

This interceptor is a really cool thing and I'm using it in several projects. Today I needed to use it on my Jaguar backend and I found out it depends on Flutter (for some reason). Of course I didn't want to add Flutter to my backend so taking a quick look into sources I found out there is absolutely nothing related to flutter inside the package and thus I could quickly make it work with a pure dart server.
Could you please, also remove Flutter dependency from it so it could be used in any software written in dart?

Adding bodyBytes to RequestData

Is your feature request related to a problem? Please describe.

Currently, InterceptorContract's interceptRequest and interceptResponse methods receive RequestData.

RequestData has body field but not bodyBytes.

We want access to bodyBytes to be able to decode it using the right encoding. In our case, we use UTF8, instead of latin1.

Describe the solution you'd like

I suggest bodyBytes gets added to RequestData.

Describe alternatives you've considered

I could encode in latin1 and decode it using UTF8.

Additional context

Is the interceptor design to be used without repository design pattern? Is so, any demo to how to do it?

Hi, first thanks for the great complementary package to the existing http package to address one of the most wanted feature.

However I'm having trouble to figure it out how to integrate the package into our project.

Currently, we do all the http networking in a most direct and simple way. As you can see from the two GET and POST method I pasted below:

// other codes...

Future get(String endpoint, {Map<String, String> arg}) {
  arg?.removeWhere((key, value) => key == null || value == null);

  // construct request url with Uri factory function
  final requestUrl = Uri.https(authority, endpoint, arg);
  return http.get(requestUrl);
}

Future getWithToken(String endpoint, {Map<String, String> arg}) {
  final userModel = UserModel();

  arg?.removeWhere((key, value) => key == null || value == null);

  // construct request url with Uri factory function
  final requestUrl = Uri.https(authority, endpoint, arg);

  print('requestUrl === $requestUrl');

  return http.get(
    requestUrl,
    headers: {
      HttpHeaders.authorizationHeader: 'Bearer ${userModel.token}',
    },
  );
}

Future post(String endpoint, dynamic arg) {
  final body = json.encode(arg);
  return http.post(
    '${endpoint}',
    headers: {
      HttpHeaders.contentTypeHeader: 'application/json',
    },
    body: body,
  );
}

Future postWithToken(String endpoint, dynamic arg) {
  final userModel = UserModel();
  final body = json.encode(arg);

  return http.post(
    '${endpoint}',
    headers: {
      HttpHeaders.contentTypeHeader: 'application/json',
      HttpHeaders.authorizationHeader: 'Bearer ${userModel.token}',
    },
    body: body,
  );
}

// other codes...

And this is an example of how we do networking using the function above:

import 'package:our_project/services/request.dart' as request; // this file contains the code above
import 'package:our_project/services/api/api.dart' as api; // this file contains all the api strings

// other codes...

      Response res = await request.get(api.some_api, arg: {
        'something': 123,
        'somethingElse': 'something else',
      });

     // do something with res...

// other codes...

I just basically wrap the existing http package's method http.get or http.post with my method to perform certain task such as adding header...

Now I want to use the interceptor to do the invalid-token-then-retry thing using this package. (And yes, I know that I can also use the package to do the adding the header thing, but the retry thing is top priority at the moment, so I want to achieve that first😅)

But I don't know how to start...

It seems to me that I have to rewrite the way how we do the networking... But I afraid in order to do that I have to rewrite a lot of the code since we were doing the networking in a most basic way.....

So, back to the title/question, is there a way to use the this package without using any kind of pattern... or just the most direct way to use the package is.......?

Thanks in advance!

Bad state: Can't finalize a finalized Request.

I just got a chance to try this out, and the first time that I got my request to try, I got this exception:

Bad state: Can't finalize a finalized Request.

This is how I'm creating my initial client:

    var httpClient = HttpClientWithInterceptor.build(
        retryPolicy: ApiRetryPolicy(), interceptors: []);

And this is my RetryPolicy

class ApiRetryPolicy extends RetryPolicy {
  @override
  int maxRetryAttempts = 1;

  @override
  Future<bool> shouldAttemptRetryOnResponse(ResponseData response) async {
    if (response.statusCode == 302) {
      // Auth expired. Re-login and try again
      if (CurrentUser().email != null && CurrentUser().password != null) {
        var loginResults = await AuthenticationService.login(
            email: CurrentUser().email, password: CurrentUser().password);
        if (loginResults.isAuthorized) {
          CurrentUser().oAuth = loginResults.oAuth;
          // Re-try request
          return true;
        }
      }
    }
    return false;
  }
}

Request for Dependencies update

Can the dependencies for the project be updated?

Flutter 2.0 for Stable channel has been released and many packages have been updated to null safety versions. This package has dependency on http and updating http package requires updating http_interceptor, so version solving fails.
But, firebase_core depends on http latest version so that can't be updated either.

This package is mandatory for my project's implementation, so cannot remove it but now it's preventing me from updating any other packages, mainly firebase packages.

So any chance for the dependencies to be updated?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.