搜尋套件、儲存庫與 APP 分類

套件詳情v3.0.0

http_interceptor

一個輕量級、簡單的外掛程式,可讓您攔截請求和回應物件,並依需求進行修改。

AndroidiOSWebmacOSWindowsLinux
近 30 天下載
7.4萬
喜歡數
213
Pub 分數
150

安裝套件

flutter pub add http_interceptor

套件介紹

根據套件公開資料產生的選型評估,僅供參考。

http_interceptor 是一個輕量級的 Flutter 插件,允許開發者攔截 HTTP 請求和回應,進而實現新增標頭、記錄日誌或全域錯誤處理等修改。它簡化了常見任務,例如驗證權杖注入、請求/回應記錄以及應用範圍內的錯誤處理。該套件可與內建的 http 客戶端無縫整合,並支援 Android、iOS、Web、macOS、Windows 及 Linux 等主要平台。設計簡潔且彈性,提供清晰的中間件式攔截 API,無需複雜設定。

適用情境

  • 全域請求頭修改
  • 身份驗證令牌注入
  • 請求/響應日誌
  • 應用程式中的錯誤處理
  • API請求監控
  • 除錯 HTTP 流量

主要特色

  • 簡單的中間件式攔截
  • 支援請求和回應
  • 跨平台相容性
  • 與 HTTP 客戶端的輕鬆整合
  • 最小化樣板
  • 可擴展的攔截器鏈

適合對象

  • 需要集中化HTTP邏輯的應用程式
  • 開發人員實現驗證流程
  • 需要調試友好的 HTTP 跟蹤的團隊
  • 頻繁進行 API 互動的專案
  • 尋找輕量級HTTP工具的人

使用建議

  • 需要理解 HTTP 生命周期
  • 如果過度使用,可能會影響效能
  • 攔截器預設同步執行
  • 有限的內建錯誤恢復
  • 不適用於複雜路由或快取

原始 README

以下為關聯原始碼儲存庫的 README 原文快照,最新內容請造訪原始碼儲存庫。

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

Upgrading from 2.x? See the 3.0.0 migration guide.

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

Implement HttpInterceptor to add logging, headers, error handling, and more. The interface has four methods:

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

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.

  • Logging interceptor:
class LoggerInterceptor implements HttpInterceptor {
  @override
  BaseRequest interceptRequest({required BaseRequest request}) {
    print('----- Request -----');
    print(request.toString());
    return request;
  }

  @override
  BaseResponse interceptResponse({required BaseResponse response}) {
    print('----- Response -----');
    print('Code: ${response.statusCode}');
    if (response is Response) {
      print(response.body);
    }
    return response;
  }
}
  • Adding headers / query params (in-place mutation):
class WeatherApiInterceptor implements HttpInterceptor {
  @override
  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
  BaseResponse interceptResponse({required BaseResponse response}) => 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 {
  final client = InterceptedClient.build(
    interceptors: [WeatherApiInterceptor()],
  );

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

}
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 {
    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:

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.

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

  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}');
}
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
  int get maxRetryAttempts => 2;

  @override
  bool shouldAttemptRetryOnException(Exception reason, BaseRequest request) {
    // Log the exception for debugging
    print('Request failed: ${reason.toString()}');
    print('Request URL: ${request.url}');
    
    // Retry on network exceptions, but not on client errors
    return reason is SocketException || reason is TimeoutException;
  }

  @override
  Future<bool> shouldAttemptRetryOnResponse(BaseResponse response) async {
    if (response.statusCode == 401) {
      // Perform your token refresh here.
      print('Token expired, refreshing...');
      
      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.

RetryPolicy Interface

The RetryPolicy abstract class provides the following methods that you can override:

  • shouldAttemptRetryOnException(Exception reason, BaseRequest request): Called when an exception occurs during the request. Return true to retry, false to fail immediately.
  • shouldAttemptRetryOnResponse(BaseResponse response): Called after receiving a response. Return true to retry, false to accept the response.
  • maxRetryAttempts: The maximum number of retry attempts (default: 1).
  • delayRetryAttemptOnException({required int retryAttempt}): Delay before retrying after an exception (default: no delay).
  • delayRetryAttemptOnResponse({required int retryAttempt}): Delay before retrying after a response (default: no delay).
Using Retry Policies

To use a retry policy, pass it to the InterceptedClient or InterceptedHttp:

final client = InterceptedClient.build(
  interceptors: [WeatherApiInterceptor()],
  retryPolicy: ExpiredTokenRetryPolicy(),
);

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
  int get maxRetryAttempts => 3;

  @override
  bool shouldAttemptRetryOnException(Exception reason, BaseRequest request) {
    // Only retry on network-related exceptions
    return reason is SocketException || reason is TimeoutException;
  }

  @override
  Future<bool> shouldAttemptRetryOnResponse(BaseResponse response) async {
    // Retry on server errors (5xx) and authentication errors (401)
    return response.statusCode >= 500 || response.statusCode == 401;
  }

  @override
  Duration delayRetryAttemptOnException({required int retryAttempt}) {
    // Exponential backoff for exceptions
    return Duration(milliseconds: (250 * math.pow(2.0, retryAttempt - 1)).round());
  }

  @override
  Duration delayRetryAttemptOnResponse({required int retryAttempt}) {
    // Exponential backoff for response-based retries
    return Duration(milliseconds: (250 * math.pow(2.0, retryAttempt - 1)).round());
  }
}
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 [me@codingale.dev](mailto:me@codingale.dev).

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
Alejandro Ulate Fallas

💻 📖 ⚠️ 🤔 🚧
Konstantin Serov
Konstantin Serov

🤔
Virus1908
Virus1908

🤔 💻 ⚠️
Wes Ehrlichman
Wes Ehrlichman

🤔 💻 ⚠️
Jan Lübeck
Jan Lübeck

🤔 💻 ⚠️
Lucas Alves
Lucas Alves

🤔 💻 ⚠️
István Juhos
István Juhos

🤔 💻 ⚠️
Scott Hyndman
Scott Hyndman

🤔
Islam Akhrarov
Islam Akhrarov

🤔 ⚠️ 💻
Meysam
Meysam

📖
Martijn
Martijn

⚠️ 💻
MaciejZuk
MaciejZuk

🐛
Lukas Kurz
Lukas Kurz

⚠️ 🤔 💻
Glenn Ruysschaert
Glenn Ruysschaert

💻 ⚠️
Erick
Erick

💻 ⚠️
javiermrz
javiermrz

💻
nihar
nihar

🤔
Ayush Yadav
Ayush Yadav

🤔
Alex
Alex

💻
Iñigo R.
Iñigo R.

💻
Thinh TRUONG
Thinh TRUONG

💻
KacperKluka
KacperKluka

💻
Klemen Tusar
Klemen Tusar

💻 📖

所屬分類

依賴

依賴清單 1 項

原始碼儲存庫

使用此套件的專案