文章系列
Flutter Dio源码分析(二)--HttpClient、Http、Dio对比
视频系列
Flutter Dio源码分析(二)--HttpClient、Http、Dio对比视频教程
源码仓库地址
介绍
在前面两篇文章中我们说了Dio
的介绍以及对HttpClient
、Http
、Dio
这三个网络请求的分析,这章节主要是对Dio
源码的分析。
从post请求来进行分析
var response = await Dio().post('http://localhost:8080/login', queryParameters: {
"username": "123456",
"password": "123456"
});
post方法
post
方法有七个参数,在该函数中调用了request
方法,并没有做任何处理,接下来我们看下request
方法。
- path: 请求的url链接
- data: 请求数据,例如上传用到的FromData
- queryParameters: 查询参数
- options: 请求选项
- cancelToken: 用来取消发送请求的token
- onSendProgress: 网络请求发送的进度
- onReceiveProgress: 网络请求接收的进度
@override
Future> post(
String path, {
data,
Map? queryParameters,
Options? options,
CancelToken? cancelToken,
ProgressCallback? onSendProgress,
ProgressCallback? onReceiveProgress,
}) {
return request(
path,
data: data,
options: checkOptions('POST', options),
queryParameters: queryParameters,
cancelToken: cancelToken,
onSendProgress: onSendProgress,
onReceiveProgress: onReceiveProgress,
);
}
request方法
request
接收了post
方法中传进来的参数。
第一步:合并选项
通过调用compose
方法来进行选项合并。
compose函数执行流程
- 首先判断
queryParameters
是否为空,不为空则添加到一个query
临时变量中 - 把
options
中的headers
全部拿出来存到临时变量_headers
中进行不区分大小写的映射,并删除headers
中的contentTypeHeader
- 如果
headers
不为空,则把headers
中的全部属性添加到临时变量_headers
中并把contentTypeHeader
赋值到一个临时变量_contentType
中。 - 把
options
中的自定义字段extra
赋值给一个临时变量 - 把
method
统一转换成大写字母 - 创建一个
RequestOptions
并传入上面处理过的参数并返回
compose源码
RequestOptions compose(
BaseOptions baseOpt,
String path, {
data,
Map? queryParameters,
CancelToken? cancelToken,
Options? options,
ProgressCallback? onSendProgress,
ProgressCallback? onReceiveProgress,
}) {
var query = {};
if (queryParameters != null) query.addAll(queryParameters);
query.addAll(baseOpt.queryParameters);
var _headers = caseInsensitiveKeyMap(baseOpt.headers);
_headers.remove(Headers.contentTypeHeader);
var _contentType;
if (headers != null) {
_headers.addAll(headers!);
_contentType = _headers[Headers.contentTypeHeader];
}
var _extra = Map.from(baseOpt.extra);
if (extra != null) {
_extra.addAll(extra!);
}
var _method = (method ?? baseOpt.method).toUpperCase();
var requestOptions = RequestOptions(
method: _method,
headers: _headers,
extra: _extra,
baseUrl: baseOpt.baseUrl,
path: path,
data: data,
connectTimeout: baseOpt.connectTimeout,
sendTimeout: sendTimeout ?? baseOpt.sendTimeout,
receiveTimeout: receiveTimeout ?? baseOpt.receiveTimeout,
responseType: responseType ?? baseOpt.responseType,
validateStatus: validateStatus ?? baseOpt.validateStatus,
receiveDataWhenStatusError:
receiveDataWhenStatusError ?? baseOpt.receiveDataWhenStatusError,
followRedirects: followRedirects ?? baseOpt.followRedirects,
maxRedirects: maxRedirects ?? baseOpt.maxRedirects,
queryParameters: query,
requestEncoder: requestEncoder ?? baseOpt.requestEncoder,
responseDecoder: responseDecoder ?? baseOpt.responseDecoder,
listFormat: listFormat ?? baseOpt.listFormat,
);
requestOptions.onReceiveProgress = onReceiveProgress;
requestOptions.onSendProgress = onSendProgress;
requestOptions.cancelToken = cancelToken;
requestOptions.contentType = _contentType ??
contentType ??
baseOpt.contentTypeWithRequestBody(_method);
return requestOptions;
}
第二步:调用fetch
判断用户是否关闭请求,关闭则退出,未关闭调用Fetch方法
request源码
@override
Future> request(
String path, {
data,
Map? queryParameters,
CancelToken? cancelToken,
Options? options,
ProgressCallback? onSendProgress,
ProgressCallback? onReceiveProgress,
}) async {
options ??= Options();
var requestOptions = options.compose(
this.options,
path,
data: data,
queryParameters: queryParameters,
onReceiveProgress: onReceiveProgress,
onSendProgress: onSendProgress,
cancelToken: cancelToken,
);
requestOptions.onReceiveProgress = onReceiveProgress;
requestOptions.onSendProgress = onSendProgress;
requestOptions.cancelToken = cancelToken;
if (_closed) {
throw DioError(
requestOptions: requestOptions,
error: "Dio can't establish new connection after closed.",
);
}
return fetch(requestOptions);
}
Fetch方法
第一步:请求参数赋值
判断如果传递进来的requestOptions.cancelToken
不为空的情况下,则把传递进来的requestOptions
进行赋值。
if (requestOptions.cancelToken != null) {
requestOptions.cancelToken!.requestOptions = requestOptions;
}
第二步:响应数据设定
如果请求回来的参数不是动态类型并且不是bytes
和stream
的方式,则进行判断该返回值类型是否是字符串,为真返回UTF-8的编码类型,否则返回字符串类型
if (T != dynamic &&
!(requestOptions.responseType == ResponseType.bytes ||
requestOptions.responseType == ResponseType.stream)) {
if (T == String) {
requestOptions.responseType = ResponseType.plain;
} else {
requestOptions.responseType = ResponseType.json;
}
}
第三步:构建请求流并添加拦截器
1、构建一个请求流,InterceptorState
是一个内部类,里面总共与两个属性T data
以及 InterceptorResultType type
,用于当前拦截器和下一个拦截器之间传递状态所定义。
2、按 FIFO 顺序执行,循环遍历向请求流中添加请求拦截器,拦截器中最主要的有RequestInterceptor
请求前拦截和 ResponseInterceptor
请求后拦截的两个实例。
var future = Future(() => InterceptorState(requestOptions));
interceptors.forEach((Interceptor interceptor) {
future = future.then(_requestInterceptorWrapper(interceptor.onRequest));
});
第四步:拦截器转换为函数回调
这里主要做的一步操作是把函数的回调作为方法的参数,这样就实现了把拦截器转换为函数回调,这里做了一层判断,如果state.type
等于 next
的话,那么会增加一个监听取消的异步任务,并把cancelToken
传递给了这个任务,接下来他会检查当前的这个拦截器请求是否入队,最后定义了一个请求拦截器的变量,该拦截器里面有三个主要的方法分别是next
() 、resole()
、 reject()
,最后把这个拦截器返回出去。
FutureOr Function(dynamic) _requestInterceptorWrapper(
void Function(
RequestOptions options,
RequestInterceptorHandler handler,
)
interceptor,
) {
return (dynamic _state) async {
var state = _state as InterceptorState;
if (state.type == InterceptorResultType.next) {
return listenCancelForAsyncTask(
requestOptions.cancelToken,
Future(() {
return checkIfNeedEnqueue(interceptors.requestLock, () {
var requestHandler = RequestInterceptorHandler();
interceptor(state.data, requestHandler);
return requestHandler.future;
});
}),
);
} else {
return state;
}
};
}
第五步:构建请求流调度回调
调度回调和添加拦截器转换为函数回调,不同的是调度回调里面进行了请求分发。
future = future.then(_requestInterceptorWrapper((
RequestOptions reqOpt,
RequestInterceptorHandler handler,
) {
requestOptions = reqOpt;
_dispatchRequest(reqOpt).then(
(value) => handler.resolve(value, true),
onError: (e) {
handler.reject(e, true);
},
);
}));
第六步:请求分发
1、请求分发函数里面会调用_transfromData
进行数据转换,最终转换出来的数据是一个 Stream
流。
2、调用网络请求适配器进行网络请求 fetch
方法,这里说明下该适配器定义有两个,分别如下:
2.1、BrowserHttpClientAdapter
是调用了html_dart2js
的库进行了网络请求,该库是将dart
代码编译成可部署的JavaScript
2.2、DefaultHttpClientAdapter
是采用系统请求库HttpClient
进行网络请求。
3、把响应头赋值给临时变量responseBody
并通过fromMap
转换成 Map
类型
4、初始化响应类,并对返回的数据进行赋值处理。
5、判断如果是正常返回就对ret.data
变量进行数据格式转换,失败则取消监听响应流
6、检查请求是否通过cancelToken
变量取消了,如果取消了则直接抛出异常
7、最后在进行请求是否正常,如果正常则检查是否入队并返回,否则直接抛出请求异常DioError
Future> _dispatchRequest(RequestOptions reqOpt) async {
var cancelToken = reqOpt.cancelToken;
ResponseBody responseBody;
try {
var stream = await _transformData(reqOpt);
responseBody = await httpClientAdapter.fetch(
reqOpt,
stream,
cancelToken?.whenCancel,
);
responseBody.headers = responseBody.headers;
var headers = Headers.fromMap(responseBody.headers);
var ret = Response(
headers: headers,
requestOptions: reqOpt,
redirects: responseBody.redirects ?? [],
isRedirect: responseBody.isRedirect,
statusCode: responseBody.statusCode,
statusMessage: responseBody.statusMessage,
extra: responseBody.extra,
);
var statusOk = reqOpt.validateStatus(responseBody.statusCode);
if (statusOk || reqOpt.receiveDataWhenStatusError == true) {
var forceConvert = !(T == dynamic || T == String) &&
!(reqOpt.responseType == ResponseType.bytes ||
reqOpt.responseType == ResponseType.stream);
String? contentType;
if (forceConvert) {
contentType = headers.value(Headers.contentTypeHeader);
headers.set(Headers.contentTypeHeader, Headers.jsonContentType);
}
ret.data = await transformer.transformResponse(reqOpt, responseBody);
if (forceConvert) {
headers.set(Headers.contentTypeHeader, contentType);
}
} else {
await responseBody.stream.listen(null).cancel();
}
checkCancelled(cancelToken);
if (statusOk) {
return checkIfNeedEnqueue(interceptors.responseLock, () => ret)
as Response;
} else {
throw DioError(
requestOptions: reqOpt,
response: ret,
error: 'Http status error [${responseBody.statusCode}]',
type: DioErrorType.response,
);
}
} catch (e) {
throw assureDioError(e, reqOpt);
}
}
download方法
download
方法的执行流程和post
一样,只是接收的数据类型以及逻辑处理上不一样,会把下载的文件保存到本地,具体实现流程在 src>entry>dio_fornative.dart 文件中,这里不在做过多的赘述。
总结
在我们进行 get()
post()
等调用时,都会进入到request
方法,request
方法主要负责对请求参数以及自定义请求头的统一处理,并调用了fetch
方法,而 fetch
中是对响应数据设定、构建请求流、添加拦截器、请求分发的操作。