文章系列

Flutter Dio源码剖析(一)--Dio介绍

Flutter Dio源码剖析(二)--HttpClient、Http、Dio比照

Flutter Dio源码剖析(三)--深度分析

Flutter Dio源码剖析(四)--封装

视频系列

Flutter Dio源码剖析(一)--Dio介绍视频教程

Flutter Dio源码剖析(二)--HttpClient、Http、Dio比照视频教程

Flutter Dio源码剖析(三)--深度分析视频教程

Flutter Dio源码剖析(四)--封装视频教程

源码仓库地址

github仓库地址

介绍

在后面两篇文章中咱们说了Dio的介绍以及对HttpClientHttpDio这三个网络申请的剖析,这章节次要是对Dio 源码的剖析。

从post申请来进行剖析

var response = await Dio().post('http://localhost:8080/login', queryParameters: {  "username": "123456",  "password": "123456"});

post办法

post 办法有七个参数,在该函数中调用了request办法,并没有做任何解决,接下来咱们看下request 办法。

  1. path: 申请的url链接
  2. data: 申请数据,例如上传用到的FromData
  3. queryParameters: 查问参数
  4. options: 申请选项
  5. cancelToken: 用来勾销发送申请的token
  6. onSendProgress: 网络申请发送的进度
  7. onReceiveProgress: 网络申请接管的进度
@overrideFuture<Response<T>> post<T>(  String path, {  data,  Map<String, dynamic>? queryParameters,  Options? options,  CancelToken? cancelToken,  ProgressCallback? onSendProgress,  ProgressCallback? onReceiveProgress,}) {  return request<T>(    path,    data: data,    options: checkOptions('POST', options),    queryParameters: queryParameters,    cancelToken: cancelToken,    onSendProgress: onSendProgress,    onReceiveProgress: onReceiveProgress,  );}

request办法

request 接管了post 办法中传进来的参数。

第一步:合并选项

通过调用compose 办法来进行选项合并。

compose函数执行流程
  1. 首先判断queryParameters 是否为空,不为空则增加到一个query 长期变量中
  2. options 中的headers 全副拿进去存到长期变量_headers中进行不辨别大小写的映射,并删除headers 中的 contentTypeHeader
  3. 如果headers不为空,则把headers 中的全副属性增加到长期变量_headers 中并把contentTypeHeader赋值到一个长期变量_contentType中。
  4. options中的自定义字段extra 赋值给一个长期变量
  5. method对立转换成大写字母
  6. 创立一个RequestOptions并传入下面解决过的参数并返回
compose源码
RequestOptions compose(  BaseOptions baseOpt,  String path, {    data,    Map<String, dynamic>? queryParameters,    CancelToken? cancelToken,    Options? options,    ProgressCallback? onSendProgress,    ProgressCallback? onReceiveProgress,  }) {  var query = <String, dynamic>{};  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<String, dynamic>.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<Response<T>> request<T>(    String path, {    data,    Map<String, dynamic>? 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<T>(requestOptions);  }

Fetch办法

第一步:申请参数赋值

判断如果传递进来的requestOptions.cancelToken 不为空的状况下,则把传递进来的requestOptions 进行赋值。

if (requestOptions.cancelToken != null) {  requestOptions.cancelToken!.requestOptions = requestOptions;}
第二步:响应数据设定

如果申请回来的参数不是动静类型并且不是bytesstream的形式,则进行判断该返回值类型是否是字符串,为真返回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<dynamic>(() => 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<String, List<String>> 类型

4、初始化响应类,并对返回的数据进行赋值解决。

5、判断如果是失常返回就对ret.data 变量进行数据格式转换,失败则勾销监听响应流

6、查看申请是否通过cancelToken 变量勾销了,如果勾销了则间接抛出异样

7、最初在进行申请是否失常,如果失常则查看是否入队并返回,否则间接抛出申请异样DioError

Future<Response<T>> _dispatchRequest<T>(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<T>;    } 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 中是对响应数据设定、构建申请流、增加拦截器、申请散发的操作。