Flutter基于Dio实现抓包功能

387 阅读3分钟

一、场景

  1. 某个迭代提测之前,后端需要进行冒烟测试,需要快速查看接口信息
  2. 提测之后,测试需要根据接口信息去验证一些测试用例
  3. 上线之后出现一些接口相关的BUG,后端需要模拟场景,快速查看该场景下的接口信息。

二、解决方案

  1. 对测试环境版本的App,提供代理接口设置页面,可以通过Fildder等代理软件进行抓包查看接口。

  2. 对生产环境版本的App,提供类似浏览器F12,Android手机开发者选项这样相对隐蔽的入口,自己实现抓包信息展示页面。

三、具体实现

3.1 方案1

流程:

  1. 进入代理配置页面
  2. 设置代理地址
  3. 保存代理地址到本地
  4. 给dio设置代理
  5. 打开抓包工具进行抓包获取。

1.提供设置代理接口页面

该页面提供:

  1. 一个开关:是否开启代理
  2. 一个输入框:输入代理地址

image.png

2.保存代理信息到缓存

将代理信息保存到缓存,形式不限。

3.给Dio设置代理

在网络请求层初始化时,获取代理缓存信息,给Dio设置代理。

//开启代理
bool isOpenProxy =Configuration.isOpenProxy();
if(!isOpenProxy){
  return;
}
String proxyUrl = Configuration.getProxyUrl();
(_dio.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate =
    (client) {
  client.findProxy = (uri) {
    return "PROXY $proxyUrl";
  };
  client.badCertificateCallback = (cert, host, port) {
    return true;
  };
};

4.设置代理软件

推荐使用Fildder,具体使用方法可参考goole.

3.2方案二

参考地址

流程:

  1. 继承实现Dio包内的Interceptor拦截器
  2. 通过拦截器一系列回调方法记录接口信息到LogManager中
  3. 给Dio设置拦截器
  4. UI层通过LogManager获取并展示接口信息。

image.png

1.记录信息内容

  1. 请求信息RequestInfo
  2. 响应信息ResponseInfo
  3. 错误信息ErrorInfo
///log信息
class LogInfo {
  RequestInfo? request;
  ResponseInfo? response;
  ErrorInfo? error;
  LogInfo({
    this.request,
    this.response,
    this.error,
  });
}
///请求信息
class RequestInfo {
  ///记录id
  int? id;
  ///请求地址
  String? url;
  ///请求方法
  String? method;
  ///contentType
  String? contentType;
  ///请求头
  Map<String, dynamic>? headers;
  ///请求时间
  DateTime? requestTime;
  ///GET请求参数
  Map<String, dynamic>? parameters;
  ///请求参数
  dynamic data;
}
///返回信息
class ResponseInfo {
  ///记录id
  int? id;
  ///状态码
  int? statusCode;
  ///返回时间
  DateTime? responseTime;
  ///ms延迟
  int? ms;
  ///header
  Map<String, List<String>>? headers;
  ///返回数据
  dynamic data;
}
///错误信息
class ErrorInfo {
  int? id;
  ///报错信息
  String? message;
}

2.实现拦截器Interceptor

///拦截器
class MyDioInterceptor implements Interceptor {
  MyDioLogManager? logManage;

  MyDioInterceptor() {
    logManage = MyDioLogManager.shared();
  }

  ///拦截请求失败事件,记录到Manager中
  @override
  Future onError(DioError error, ErrorInterceptorHandler handler) async {
    ErrorInfo errorInfo = ErrorInfo();
    errorInfo.id = error.requestOptions.hashCode;
    errorInfo.message = error.toString();
    logManage?.insertError(errorInfo);
    if (error.response != null) {
      insertResponse(error.response!);
    }
    return handler.next(error);
  }

  ///拦截发起请求事件,记录到Manager中
  @override
  void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
    var requestInfo = RequestInfo();
    requestInfo.id = options.hashCode;
    requestInfo.url = options.uri.toString();
    requestInfo.method = options.method;
    requestInfo.contentType = options.contentType.toString();
    requestInfo.headers = options.headers;
    requestInfo.requestTime = DateTime.now();
    requestInfo.parameters = options.queryParameters;
    requestInfo.data = options.data;
    logManage?.insertRequest(requestInfo);
    return handler.next(options);
  }

  ///拦截响应事件,记录到Manager中
  @override
  Future onResponse(
      Response response, ResponseInterceptorHandler handler) async {
    insertResponse(response);
    return handler.next(response);
  }

  void insertResponse(Response response) {
    var responseInfo = ResponseInfo();
    responseInfo.id = response.requestOptions.hashCode;
    responseInfo.responseTime = DateTime.now();
    responseInfo.statusCode = response.statusCode ?? 0;
    responseInfo.data = response.data;
    responseInfo.headers = response.headers.map;
    logManage?.insertRepsonse(responseInfo);
  }
}

3.实现log管理类Manager

///log管理类
class MyDioLogManager {
  ///请求日志Map
  Map<String, LogInfo> logMap = {};

  static MyDioLogManager? _shared;

  MyDioLogManager._internal() {}

  static MyDioLogManager shared() {
    if (_shared == null) {
      _shared = MyDioLogManager._internal();
    }
    return _shared!;
  }

  ///记录Error
  void insertError(ErrorInfo error) {
    var key = error.id.toString();
    if (!logMap.containsKey(key)) {
      return;
    }
    logMap.update(key, (value) {
      value.error = error;
      return value;
    });
  }

  ///记录请求信息
  void insertRequest(RequestInfo options) {
    var key = options.id.toString();
    ///这儿肯定是先记录request,再记录reponse/error,因此无需判断key
    logMap.putIfAbsent(key, () => LogInfo(request: options));
  }

  ///记录返回信息
  void insertRepsonse(ResponseInfo response) {
    var key = response.id.toString();
    if (!logMap.containsKey(key)) {
      return;
    }
    logMap.update(key, (value) {
      response.ms = response.responseTime!.millisecondsSinceEpoch -
          value.request!.requestTime!.millisecondsSinceEpoch;
      value.response = response;
      return value;
    });
  }
}

3.设置拦截器

_dio.interceptors.add(MyDioInterceptor());

4.UI层展示 通过MyDioLogManager.shared.logMap,即可获取所有日志; UI展示部分不再赘述,涉及到json数据的展示,可考虑使用flutter_json_view,