一、场景
- 某个迭代提测之前,后端需要进行冒烟测试,需要快速查看接口信息
- 提测之后,测试需要根据接口信息去验证一些测试用例
- 上线之后出现一些接口相关的BUG,后端需要模拟场景,快速查看该场景下的接口信息。
二、解决方案
-
对测试环境版本的App,提供代理接口设置页面,可以通过Fildder等代理软件进行抓包查看接口。
-
对生产环境版本的App,提供类似浏览器F12,Android手机开发者选项这样相对隐蔽的入口,自己实现抓包信息展示页面。
三、具体实现
3.1 方案1
流程:
- 进入代理配置页面
- 设置代理地址
- 保存代理地址到本地
- 给dio设置代理
- 打开抓包工具进行抓包获取。
1.提供设置代理接口页面
该页面提供:
- 一个开关:是否开启代理
- 一个输入框:输入代理地址
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方案二
流程:
- 继承实现Dio包内的Interceptor拦截器
- 通过拦截器一系列回调方法记录接口信息到LogManager中
- 给Dio设置拦截器
- UI层通过LogManager获取并展示接口信息。
1.记录信息内容
- 请求信息RequestInfo
- 响应信息ResponseInfo
- 错误信息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,