flutter - Dio - QueuedInterceptor 处理具有多个请求的刷新 token

标签 flutter dart dio

我正在像这样调用多个API

await Future.wait([profile, calendar, cities]);

我有一个拦截器类,它扩展了DioQueuedInterceptor。 在配置文件 api 上说,我的访问 token 过期并且 onError 被调用。 所以我点击了refresh_token api,重试当前请求,成功后我调用handler.resolve(response);。但其余的 api 仍然调用拦截器 onError 方法,并且我的refresh_token api 总共被命中了 3 次。 可能是什么问题?

    Future<void> onError(DioError err, ErrorInterceptorHandler handler) async {
    
    if (err.response?.statusCode == 401) {
      var tokenDio = Dio();

      try {
        Response refreshResponse = await tokenDio.post('/${endPoint.path}',
            data: endPoint.requestBody);

        if (refreshResponse.statusCode == 201) {
          var newAccessToken = refreshResponse.data['access_token'];

          var newHeaders = err.requestOptions.headers;
          newHeaders['Authorization'] = 'Bearer $newAccessToken';
          
           var response = await dio.request(
            err.requestOptions.path,
            data: err.requestOptions.data,
            queryParameters: err.requestOptions.queryParameters,
            options: Options(
              method: err.requestOptions.method,
              headers: newHeaders,
            ),
          );
          
          return handler.resolve(response);
        } 
        else {
          return handler.reject(err);
        }
      } catch (error) {
        return handler.reject(err);
      }
    } 
    else {
      return handler.next(err);
    }
  }

最佳答案

这是一个无需在拦截器中扩展 QueuedInterceptor 类的解决方案:

  1. 扩展 Interceptor 类而不是 QueuedInterceptor 类。
  2. 创建一个 bool 值 isRefreshing,在第一个请求失败时将其设置为 true,并发送 refreshToken 请求以刷新您的访问 token 。
  3. 创建失败请求列表,让所有请求都失败,并且只要 isRefreshing 为 true,就不断将失败请求添加到此列表中。
  4. 刷新 token (第 2 步)后,通过循环访问 failedRequests 列表来重试所有失败的请求。
if (!isRefreshing) {
    debugPrint("ACCESS TOKEN EXPIRED, GETTING NEW TOKEN PAIR");
    isRefreshing = true;
    await refreshToken(err, handler);
} else {
    debugPrint("ADDING ERRRORED REQUEST TO FAILED QUEUE");
    failedRequests.add({'err': err, 'handler': handler});
}

拦截器的完整代码:

class MyHttpInterceptor extends Interceptor {
  List<Map<dynamic, dynamic>> failedRequests = [];
  bool isRefreshing = false;

  MyHttpInterceptor();

  @override
  void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
    debugPrint('REQUEST[${options.method}] => PATH: ${options.path}');
    handler.next(options);
  }

  @override
  void onResponse(Response response, ResponseInterceptorHandler handler) {
    debugPrint(
        'RESPONSE[${response.statusCode}] => PATH: ${response.requestOptions.path}');
    handler.next(response);
  }

  @override
  void onError(DioError err, ErrorInterceptorHandler handler) async {
    debugPrint(
        'ERROR[${err.response?.statusCode}] => PATH: ${err.requestOptions.path}, IS REFRESHING: ${isRefreshing.toString()}');
    if (err.response?.statusCode == 401) {
      if (getIt<AuthProvider>().refreshToken.isEmpty) {
        debugPrint("LOGGING OUT: NO REFRESH TOKEN FOUND");
        // perform logout
        return handler.reject(err);
      }

      if (!isRefreshing) {
        debugPrint("ACCESS TOKEN EXPIRED, GETTING NEW TOKEN PAIR");
        isRefreshing = true;
        await refreshToken(err, handler);
      } else {
        debugPrint("ADDING ERRRORED REQUEST TO FAILED QUEUE");
        failedRequests.add({'err': err, 'handler': handler});
      }
    } else {
      return handler.next(err);
    }
  }

  FutureOr refreshToken(DioError err, ErrorInterceptorHandler handler) async {
    Dio retryDio = Dio(
      BaseOptions(
        baseUrl: baseURI,
        headers: <String, String>{
          'Content-Type': 'application/json',
          'Authorization': 'Bearer ${getIt<AuthProvider>().refreshToken}'
        },
      ),
    );
    // handle refresh token
    var response = await retryDio.get('/user/get-at-by-rt');
    var parsedResponse = response.data;
    if (response.statusCode == 401 || response.statusCode == 403) {
      // handle logout
      debugPrint("LOGGING OUT: EXPIRED REFRESH TOKEN");
      return handler.reject(err);
    }

    // handle setting tokens in your store for future requests

    isRefreshing = false;
    failedRequests.add({'err': err, 'handler': handler});
    debugPrint("RETRYING ${failedRequests.length} FAILED REQUEST(s)");
    retryRequests(parsedResponse['data']['access_token']);
  }

  Future retryRequests(token) async {
    Dio retryDio = Dio(
      BaseOptions(
        baseUrl: baseURI,
      ),
    );

    for (var i = 0; i < failedRequests.length; i++) {
      debugPrint(
          'RETRYING[$i] => PATH: ${failedRequests[i]['err'].requestOptions.path}');
     RequestOptions requestOptions =
          failedRequests[i]['err'].requestOptions as RequestOptions;
      requestOptions.headers = {
        'Authorization': 'Bearer $token',
        'Content-Type': 'application/json'
      };
      await retryDio.fetch(requestOptions).then(
            failedRequests[i]['handler'].resolve,
            onError: (error) =>
                failedRequests[i]['handler'].reject(error as DioError),
          );
    }
    isRefreshing = false;
    failedRequests = [];
  }
}

陷阱:

  1. 确保您跟踪每个失败请求的 DioErrorErrorInterceptorHandler,并使用它们执行重试请求。
  2. 请确保在重试所有失败的请求后将 isRefreshing 设置为 false。
  3. 完成后,请确保清空 failedRequests 列表。

希望这个解决方案适合您!

关于flutter - Dio - QueuedInterceptor 处理具有多个请求的刷新 token ,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/76228296/

相关文章:

flutter - 如何在按钮小部件中显示进度指示器(如图所示)

dart - 如何在 Flutter 中测试/调试摇树?

api - flutter DIO : upload image using binary body with Dio package

flutter http 0.13.0 : String can not assign to Uri

http - DioError [DioErrorType.RESPONSE] : Http status error [405] [Solved]

flutter - Dio 不会捕获错误并卡在抛出自定义异常

android - 圆形底部导航栏

flutter - Flutter已折叠ExpansionTile是否不保存表单数据?

dart - 运行后出错 "flutter upgrade"

flutter - 从 2.8.1 升级到 Flutter 3.0,得到 : Warning: Operand of null-aware operation '?.' has type 'PaintingBinding' which excludes null error