flutter中Dio添加拦截器和日志记录

3,595 阅读2分钟

基于Github客户端示例 增加Dio拦截器和日志记录

import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:dio/adapter.dart';
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import '../index.dart';

class Git {
  // 在网络请求过程中可能会需要使用当前的context信息,比如在请求失败时
  // 打开一个新路由,而打开新路由需要context信息。
  Git([this.context]) {
    _options = Options(extra: {"context": context});
  }

  BuildContext context;
  Options _options;
  static Dio dio = new Dio(BaseOptions(
    connectTimeout: 50000,
    receiveTimeout: 50000,
    baseUrl: 'https://api.github.com/',
    headers: {
      HttpHeaders.acceptHeader: "application/vnd.github.squirrel-girl-preview,"
          "application/vnd.github.symmetra-preview+json",
    },
  ));

  static void init() {
    // 添加缓存插件
    dio.interceptors.add(Global.netCache);
    //添加拦截器
    dio.interceptors.add(InterceptorsWrapper(
      onRequest: (RequestOptions options) => requestInterceptor(options),
      onResponse: (Response response) => responseInterceptor(response),
      onError: (DioError dioError) => errorInterceptor(dioError)
    ));
    if (!Global.isRelease) {
      // 通过控制台打印所有的请求信息以及相应信息,以方便我们调试请求中的问题
      dio.interceptors.add(LoggingInterceptor());
    }
    // 设置用户token(可能为null,代表未登录)
    // dio.options.headers[HttpHeaders.authorizationHeader] = Global.profile.token;

    // 在调试模式下需要抓包调试,所以我们使用代理,并禁用HTTPS证书校验
    // 启动Charles客户端,配置抓包代理
    if (!Global.isRelease) {
      (dio.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate =
          (client) {
        client.findProxy = (uri) {
          return "PROXY 192.168.100.37:8888";
        };
        //代理工具会提供一个抓包的自签名证书,会通不过证书校验,所以我们禁用证书校验
        client.badCertificateCallback =
            (X509Certificate cert, String host, int port) => true;
      };
    }
  }

  static dynamic requestInterceptor(RequestOptions options) async {
    // 请求拦截器
    var token = Global.profile.token;
    options.headers[HttpHeaders.authorizationHeader] = Global.profile.token;
    options.headers.addAll({"X-Token": "$token"});
    return options;
  }

  static dynamic responseInterceptor(Response options) async {
    // 响应拦截器
    // if (options.headers.value("verifyToken") != null) {
    //   //if the header is present, then compare it with the Shared Prefs key
    //   var verifyToken = Global.profile.token;
    //   // if the value is the same as the header, continue with the request
    //   if (options.headers.value("verifyToken") != verifyToken) {
    //     return DioError(request: options.request, error: "User is no longer active");
    //   }
    // }

    // if (options.data['code'] == 20000){
    //   return options.data['data'];
    // }

    return options;
  }

  static dynamic errorInterceptor(DioError dioError) {
    // 错误拦截器
    if (dioError.message.contains("ERROR_001")) {
      // this will push a new route and remove all the routes that were present
      final GlobalKey<NavigatorState> navigatorKey = new GlobalKey<NavigatorState>();
      navigatorKey.currentState.pushNamedAndRemoveUntil(
          "/login", (Route<dynamic> route) => false);
    }
    
    return dioError;
  }

  // 登录接口,登录成功后返回用户信息
  Future<User> login(String login, String pwd) async {
    String basic = 'Basic ' + base64.encode(utf8.encode('$login:$pwd'));
    var r = await dio.get(
      "/users/$login",
      options: _options.merge(headers: {
        HttpHeaders.authorizationHeader: basic
      }, extra: {
        "noCache": true, //本接口禁用缓存
      }),
    );
    //登录成功后更新公共头(authorization),此后的所有请求都会带上用户身份信息
    dio.options.headers[HttpHeaders.authorizationHeader] = basic;
    //清空所有缓存
    Global.netCache.cache.clear();
    //更新profile中的token信息
    Global.profile.token = basic;
    return User.fromJson(r.data);
  }

  //获取用户项目列表
  Future<List<Repo>> getRepos(
      {Map<String, dynamic> queryParameters, //query参数,用于接收分页信息
      refresh = false}) async {
    if (refresh) {
      // 列表下拉刷新,需要删除缓存(拦截器中会读取这些信息)
      _options.extra.addAll({"refresh": true, "list": true});
    }
    var r = await dio.get<List>(
      "user/repos",
      queryParameters: queryParameters,
      options: _options,
    );
    return r.data.map((e) => Repo.fromJson(e)).toList();
  }
}

class LoggingInterceptor extends Interceptor{
  // 通过控制台打印所有的请求信息以及相应信息,以方便我们调试请求中的问题
  int _maxCharactersPerLine = 200;

  @override
  Future onRequest(RequestOptions options) {
    debugPrint("--> ${options.method} ${options.path}");
    debugPrint("Content type: ${options.contentType}");
    debugPrint("<-- END HTTP");
    return super.onRequest(options);
  }

  @override
  Future onResponse(Response response) {
    debugPrint(
        "<-- ${response.statusCode} ${response.request.method} ${response.request.path}");
    String responseAsString = response.data.toString();
    if (responseAsString.length > _maxCharactersPerLine) {
      int iterations =
      (responseAsString.length / _maxCharactersPerLine).floor();
      for (int i = 0; i <= iterations; i++) {
        int endingIndex = i * _maxCharactersPerLine + _maxCharactersPerLine;
        if (endingIndex > responseAsString.length) {
          endingIndex = responseAsString.length;
        }
        debugPrint(responseAsString.substring(
            i * _maxCharactersPerLine, endingIndex));
      }
    } else {
      debugPrint(response.data);
    }
    debugPrint("<-- END HTTP");

    return super.onResponse(response);
  }

  @override
  Future onError(DioError err) {
    debugPrint("<-- Error -->");
    debugPrint(err.error);
    debugPrint(err.message);
    return super.onError(err);
  }

}