dio的拦截器Interceptor

拦截器是什么

Dio 的拦截器(Interceptor)是 在请求生命周期中的钩子函数。你可以在 请求发出前响应返回时错误发生时 对数据进行处理。

拦截器生命周期

Request 发起
   │
   ▼
onRequest 拦截器
   │   ├─ next() → 继续请求
   │   ├─ resolve() → 返回自定义响应
   │   └─ reject() → 进入 onError, ensureForward: true
   ▼
请求真正发出 (HTTP)
   │
   ▼
onResponse 拦截器
   │   ├─ next() → 返回给调用方
   │   ├─ resolve() → 返回自定义响应
   │   └─ reject() → 进入 onError , ensureForward: true
   ▼
返回给调用方
   │
   ▼
onError 拦截器 (请求失败 / reject)
   │   ├─ next() → 错误继续传递
   │   ├─ resolve() → 转换为正常响应
   │   └─ reject() → 抛出异常,直接终止链路

三大核心方法

onRequest

触发时机:请求发出前

常见用途:

  • 添加 公共 header(如 token)
  • 打印日志
  • mock 数据(用 resolve 返回)
@override
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
  print("请求拦截: ${options.uri}");
  options.headers["Authorization"] = "Bearer token";
  handler.next(options); // 放行
}

onResponse

触发时机:收到响应时

常见用途:

  • 统一处理响应格式(比如后端返回 {"code":0,"data":{...}}
  • 打印响应日志
@override
void onResponse(Response response, ResponseInterceptorHandler handler) {
  print("响应拦截: ${response.data}");
  handler.next(response); // 继续传递
}

onError

  • 触发时机:请求失败 / 主动 reject
  • 常见用途:
    • 统一错误处理
    • 自动刷新 token 后重试请求
    • 记录日志
@override
void onError(DioException err, ErrorInterceptorHandler handler) {
  print("错误拦截: ${err.message}");
  handler.next(err); // 把错误传递给调用方
}

三个控制方法

方法 作用 典型场景
next() 放行,继续执行下一个拦截器或 Dio 自身逻辑 日志记录、轻量修改
resolve() 返回一个自定义响应,拦截后续逻辑 Mock 数据、缓存
reject() 抛出一个错误,中断请求 参数校验、权限检查

handler.next

//onRequest
void next(RequestOptions requestOptions) {
// onResponse
void next(Response response) {
// onError
void next(DioException error) 

作用:把当前拦截的内容 交给下一个拦截器继续处理

使用场景:你只是对请求/响应做一些记录(比如打印日志),并且还希望后续拦截器或 Dio 自己继续处理。

handler.resolve

//onRequest
void resolve(
  Response response, [
  bool callFollowingResponseInterceptor = false,
]) {
// onResponse
void resolve(Response response) {
// onError
void resolve(Response response) {

作用直接返回一个响应,跳过后续的拦截器和真正的请求过程。

使用场景

  • 你想 提前返回一个模拟结果(mock),不让请求真的发出去。
  • 或者在某些情况下,直接构造一个“缓存响应”。

onRequest 里调用 resolve(),有个额外的布尔参数callFollowingResponseInterceptor,它的作用是:控制 当前返回的响应,是否还要经过后续的 response 拦截器(onResponse)

  • 默认值:false:表示你在 onRequest 中直接 resolve 一个 Response不会再走任何后续的 response 拦截器。响应会直接返回给调用者。
  • 如果设为 true:你在 onRequest 阶段就“伪造”了一个响应,但依然会让它走一遍 onResponse 拦截器链路。这样可以让 response 拦截器继续对它做加工或日志记录。
class MyInterceptor extends Interceptor {
  @override
  void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
    // 假设我们拦截所有 /mock 请求
    if (options.path.contains("/mock")) {
      final fakeResponse = Response(
        requestOptions: options,
        data: {"msg": "这是一个伪造的响应"},
        statusCode: 200,
      );

      // 如果传 true,那么后续的 onResponse 也会触发
      return handler.resolve(fakeResponse, true);
    }

    super.onRequest(options, handler);
  }

  @override
  void onResponse(Response response, ResponseInterceptorHandler handler) {
    print("onResponse 收到: ${response.data}");
    super.onResponse(response, handler);
  }
}

流程对比:

false: onRequest → resolve(fakeResponse) → 直接返回给调用者
true: onRequest → resolve(fakeResponse, true) → 进入 onResponse → 返回给调用者  

handler.reject

//onRequest
void reject(
  DioException error, [
  bool callFollowingErrorInterceptor = false,
]) {
// onResponse
void reject(
  DioException error, [
  bool callFollowingErrorInterceptor = false,
]) {
// onError
void reject(DioException error) {

作用直接抛出一个错误,跳过后续流程。

使用场景:你在请求前发现问题(比如没有 Token,或者参数错误),可以直接中断请求并抛出错误。

callFollowingErrorInterceptor 的作用是:控制当前你 手动抛出的错误,是否还要继续交给后续的 错误拦截器(onError) 处理。

  • 默认值:false:错误会立即抛出给调用方(await dio.get() 那里),不会触发其它 onError 拦截器。
  • 如果设为 true,错误会像“正常发生的错误”一样,继续进入拦截器链路,触发后续的 onError

onError里面没有这个配置,默认是直接抛异常出来,不用走onError

class MyInterceptor extends Interceptor {
  @override
  void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
    if (options.path.contains("error")) {
      return handler.reject(
        DioException(
          requestOptions: options,
          error: "拦截器主动拒绝请求",
          type: DioExceptionType.badResponse,
        ),
        true, // 👈 让错误进入 onError
      );
    }
    super.onRequest(options, handler);
  }

  @override
  void onError(DioException err, ErrorInterceptorHandler handler) {
    print("onError 拦截到了: ${err.error}");
    super.onError(err, handler);
  }
}

流程对比:

false: onRequest → reject(error) → 直接抛出异常给调用者
true: onRequest → reject(error, true) → 进入 onError → 再抛给调用者

两种拦截器添加方法

匿名拦截器(InterceptorsWrapper)

final dio = Dio();

dio.interceptors.add(
  InterceptorsWrapper(
    onRequest: (options, handler) {
      print("➡️ 请求拦截: ${options.uri}");
      // 继续请求
      handler.next(options);
    },
    onResponse: (response, handler) {
      print("✅ 响应拦截: ${response.data}");
      handler.next(response);
    },
    onError: (DioException e, handler) {
      print("❌ 错误拦截: ${e.message}");
      handler.next(e);
    },
  ),
);
  • 写法简洁,适合简单逻辑(如打印日志、统一 token 处理)。
  • 适合临时性、局部性逻辑。
  • 缺点是逻辑分散在一堆闭包里,不利于大型项目管理。

自定义拦截器类(继承 Interceptor

// 定义拦截器
class MyInterceptor extends Interceptor {
  @override
  void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
    print("➡️ 请求拦截: ${options.uri}");
    super.onRequest(options, handler);
  }

  @override
  void onResponse(Response response, ResponseInterceptorHandler handler) {
    print("✅ 响应拦截: ${response.data}");
    super.onResponse(response, handler);
  }

  @override
  void onError(DioException err, ErrorInterceptorHandler handler) {
    print("❌ 错误拦截: ${err.message}");
    super.onError(err, handler);
  }
}
// 注册时:
final dio = Dio();
dio.interceptors.add(MyInterceptor());
  • 逻辑清晰,结构化,适合大项目
  • 可以定义多个拦截器类,专门负责鉴权 / 缓存 / 日志 / 错误处理等。
  • 可复用,团队协作更好。

三个参数

RequestOptions

Dio 里,RequestOptions请求的完整配置信息,每次发请求都会生成一个 RequestOptions 对象。

RequestOptions 常见属性如下:

请求基本信息

method → 请求方法,如 "GET", "POST"

path → 请求路径(相对或绝对)

baseUrl → 基础地址,和 path 拼接成完整 URL

uribaseUrl + path 合并之后的最终 Uri 对象

请求参数与数据

queryParameters → GET 请求的参数(会拼到 URL 后面)

data → POST/PUT 的请求体数据,可以是 MapFormDataString

headers → 请求头(Map<String, dynamic>

其他配置

extra → 自定义字段(开发者可存放标记、缓存策略等信息)

contentTypeapplication/json / multipart/form-data

responseType → 响应的数据格式,比如 ResponseType.jsonResponseType.bytesResponseType.stream

followRedirects → 是否跟随重定向

connectTimeout → 连接超时时间

sendTimeout → 发送数据超时时间

receiveTimeout → 接收数据超时时间

内部控制

cancelToken → 请求取消用的 token

validateStatus → 自定义状态码校验函数

onReceiveProgress → 下载进度回调

onSendProgress → 上传进度回调

Response

Dio 里,Response 表示一次请求的结果,包含了 服务端返回的数据 + 请求的上下文信息

核心数据

data → 响应的数据(可能是 Map / List / String / Uint8List 等)例如:JSON API 会被自动解析成 Map<String, dynamic>

HTTP 相关

statusCode → HTTP 状态码(如 200, 404, 500

statusMessage → 状态消息(如 "OK", "Not Found"

请求上下文

headers → 响应头(Headers 对象,可用 response.headers.value("Content-Type") 获取单个字段)

requestOptions → 发起这次请求时的 RequestOptions(方便调试和回溯请求参数)

额外信息

extra → 存放自定义的扩展字段(一般用于拦截器加工数据)

isRedirect → 是否发生了重定向

redirects → 重定向链路(List<RedirectInfo>

DioException

在 Dio 中,所有请求错误都会被包装成 DioException,这样开发者就能统一处理,而不用区分底层是 SocketExceptionTimeoutException 还是 HttpException

DioException 的核心属性如下

class DioException implements Exception {
  final DioExceptionType type;     // 错误类型(超时、响应错误等)
  final String? message;           // 错误信息(可读的文字描述)
  final dynamic error;             // 原始错误对象(通常是系统异常)
  final RequestOptions requestOptions; // 请求的相关信息
  final Response? response;        // 服务端返回的响应(如果有)
  final StackTrace? stackTrace;    // 堆栈信息
}

DioExceptionType

**type (DioExceptionType)**错误类型枚举,常见的有:

  • connectionTimeout → 连接超时
  • sendTimeout → 发送数据超时
  • receiveTimeout → 接收数据超时
  • badResponse → 服务器返回了错误状态码(4xx / 5xx)
  • cancel → 请求被取消
  • unknown → 未知错误

message

**message (String?)**对错误的文字描述。比如 "Http status error [404]"

RequestOptions

**requestOptions (RequestOptions)**请求相关的详细信息。包括:

  • path
  • method
  • headers
  • data(请求体)
  • queryParameters 等。

Response

**response (Response?)**如果服务端有返回(即使是错误,比如 404),这个属性会包含 Response 对象。可以从 response?.statusCoderesponse?.data 中拿到服务端返回的内容。

stackTrace

stackTrace (StackTrace?):报错时的调用堆栈,便于调试。

案例:自定义拦截器

import 'package:flutter/material.dart';
import 'package:dio/dio.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Dio Interceptor Demo',
      theme: ThemeData(primarySwatch: Colors.blue),
      home: const HomePage(),
    );
  }
}

class HomePage extends StatefulWidget {
  const HomePage({super.key});

  @override
  State<HomePage> createState() => _HomePageState();
}

/// 自定义拦截器
class MyInterceptor extends Interceptor {
  @override
  void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
    debugPrint("-------------拦截到请求---------------");
    debugPrint("➡️ 请求: ${options.uri}");

    if (options.path.contains("mock")) {
      // resolve: 提前返回一个假数据
      return handler.resolve(
        Response(
          requestOptions: options,
          data: {"message": "这是拦截器返回的假数据"},
          statusCode: 200,
        ),
      );
    }

    if (options.path.contains("error")) {
      // reject: 主动抛出一个错误
      return handler.reject(
        DioException(
          requestOptions: options,
          error: "拦截器主动拒绝请求",
          type: DioExceptionType.badResponse,
        ),
        true, // ✅ 确保进入 onError
      );
    }

    // next: 正常放行
    super.onRequest(options, handler);
  }

  @override
  void onResponse(Response response, ResponseInterceptorHandler handler) {
    debugPrint("-------------拦截到响应---------------");
    debugPrint("✅ 响应: ${response.data}");
    // 放行,交给下一个或返回给调用方
    super.onResponse(response, handler);
  }

  @override
  void onError(DioException err, ErrorInterceptorHandler handler) {
    debugPrint("-------------拦截到错误---------------");
    debugPrint("❌ 错误: ${err.error}");

    handler.resolve(
      Response(
        requestOptions: err.requestOptions,
        data: {"message": "错误已被拦截器处理"},
        statusCode: 200,
      ),
      // 如果你、在 onError 中用 reject,
      //调用方就会直接在 try-catch 里捕获异常,
      //而不会再经过别的拦截器处理。
      // handler.reject(
      //   DioException(
      //     requestOptions: err.requestOptions,
      //     error: "拦截器主动拒绝请求",
      //     type: DioExceptionType.badResponse,
      //   ),
    );
  }
}

class _HomePageState extends State<HomePage> {
  final Dio _dio = Dio();

  @override
  void initState() {
    super.initState();
    _dio.interceptors.add(MyInterceptor());
  }

  Future<void> _testNormalRequest() async {
    debugPrint("--------------发起正常请求---------------");
    final res = await _dio.get("https://jsonplaceholder.typicode.com/todos/1");
    debugPrint("最终结果: ${res.data}");
  }

  Future<void> _testMockRequest() async {
    debugPrint("--------------发起模拟请求---------------");
    final res = await _dio.get("mock");
    debugPrint("最终结果: ${res.data}");
  }

  Future<void> _testErrorRequest() async {
    debugPrint("--------------发起错误请求---------------");
    final res = await _dio.get("error");
    debugPrint("最终结果: ${res.data}");
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text("Dio Interceptor Demo")),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            ElevatedButton(
              onPressed: _testNormalRequest,
              child: const Text("正常请求 (next)"),
            ),
            ElevatedButton(
              onPressed: _testMockRequest,
              child: const Text("模拟请求 (resolve)"),
            ),
            ElevatedButton(
              onPressed: _testErrorRequest,
              child: const Text("错误请求 (reject+resolve)"),
            ),
          ],
        ),
      ),
    );
  }
}

以下是日志:

I/flutter (20355): --------------发起正常请求---------------
I/flutter (20355): -------------拦截到请求---------------
I/flutter (20355): ➡️ 请求: https://jsonplaceholder.typicode.com/todos/1
I/flutter (20355): -------------拦截到错误---------------
I/flutter (20355): ❌ 错误: null
I/flutter (20355): 最终结果: {message: 错误已被拦截器处理}
I/flutter (20355): --------------发起模拟请求---------------
I/flutter (20355): -------------拦截到请求---------------
I/flutter (20355): ➡️ 请求: mock
I/flutter (20355): 最终结果: {message: 这是拦截器返回的假数据}
I/flutter (20355): --------------发起错误请求---------------
I/flutter (20355): -------------拦截到请求---------------
I/flutter (20355): ➡️ 请求: error
I/flutter (20355): -------------拦截到错误---------------
I/flutter (20355): ❌ 错误: 拦截器主动拒绝请求
I/flutter (20355): 最终结果: {message: 错误已被拦截器处理}

多个拦截器

Dio 拦截器执行规则

在 Dio 里,拦截器类似一个「洋葱模型」:

  • 请求阶段(onRequest):按添加顺序执行
  • 响应阶段(onResponse):按添加顺序的反向执行
  • 错误阶段(onError):同样是反向执行

也就是:

  • Request 顺序:Interceptor1 → Interceptor2 → …
  • Response / Error 顺序:… → Interceptor2 → Interceptor1

请求成功(返回 200)

sequenceDiagram
    participant App as 应用
    participant I1 as 拦截器1
    participant I2 as 拦截器2
    participant Server as 服务器

    App->>I1: onRequest
    I1->>I2: onRequest
    I2->>Server: 发起请求

    Server-->>I2: 返回响应
    I2-->>I1: onResponse
    I1-->>App: onResponse

请求失败(返回 404)

sequenceDiagram
    participant App as 应用
    participant I1 as 拦截器1
    participant I2 as 拦截器2
    participant Server as 服务器

    App->>I1: onRequest
    I1->>I2: onRequest
    I2->>Server: 发起请求

    Server-->>I2: 返回错误
    I2-->>I1: onError
    I1-->>App: onError

案例:两个拦截器

import 'package:dio/dio.dart';

void main() async {
  final dio = Dio();

  // 拦截器1
  dio.interceptors.add(InterceptorsWrapper(
    onRequest: (options, handler) {
      print("拦截器1 - 请求开始");
      handler.next(options); // 继续传递
    },
    onResponse: (response, handler) {
      print("拦截器1 - 收到响应");
      handler.next(response);
    },
    onError: (e, handler) {
      print("拦截器1 - 捕获错误");
      handler.next(e);
    },
  ));

  // 拦截器2
  dio.interceptors.add(InterceptorsWrapper(
    onRequest: (options, handler) {
      print("拦截器2 - 请求开始");
      handler.next(options);
    },
    onResponse: (response, handler) {
      print("拦截器2 - 收到响应");
      handler.next(response);
    },
    onError: (e, handler) {
      print("拦截器2 - 捕获错误");
      handler.next(e);
    },
  ));

  try {
    // 这里随便找个可能返回404的地址
    final response = await dio.get("https://httpbin.org/status/200");
    print("最终响应: ${response.statusCode}");
  } catch (e) {
    print("最终捕获异常: $e");
  }
}

以下是日志:

I/flutter (21319): 拦截器1 - 请求开始
I/flutter (21319): 拦截器2 - 请求开始
D/ProfileInstaller(21319): Installing profile for com.example.flutter_sophomore
I/flutter (21319): 拦截器1 - 收到响应
I/flutter (21319): 拦截器2 - 收到响应
I/flutter (21319): 最终响应: 200

拦截器常见应用场景

统一添加 Token

// 在请求前统一添加Token
onRequest(options, handler) {
  options.headers["Authorization"] = "Bearer xxx";
  handler.next(options);
}

统一处理响应格式

onResponse(response, handler) {
  if (response.data["code"] != 0) {
    return handler.reject(DioException(
      requestOptions: response.requestOptions,
      error: response.data["message"],
    ), true);
  }
  response.data = response.data["data"]; // 直接提取 data
  // 或者把data转成其他格式
  handler.next(response);
}

统一错误处理

onError(err, handler) {
  if (err.type == DioExceptionType.connectionTimeout) {
    print("网络超时");
  } else {
    print("其他错误: ${err.message}");
  }
  handler.next(err);
}

刷新 Token 重试请求

onError(err, handler) async {
  if (err.response?.statusCode == 401) {
    // 假设 refreshToken() 能刷新 token
    final newToken = await refreshToken();
    err.requestOptions.headers["Authorization"] = "Bearer $newToken";
    final cloneReq = await dio.fetch(err.requestOptions); // 重新发起请求
    return handler.resolve(cloneReq); // 用新的结果返回
  }
  handler.next(err);
}

×

喜欢就点赞,疼爱就打赏