首页 Flutter Dio源码分析(三)--深度剖析
文章
取消

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

文章系列

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请求来进行分析

1
2
3
4
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: 网络请求接收的进度
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@override
Future<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源码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
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源码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
 @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 进行赋值。

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

如果请求回来的参数不是动态类型并且不是bytesstream的方式,则进行判断该返回值类型是否是字符串,为真返回UTF-8的编码类型,否则返回字符串类型

1
2
3
4
5
6
7
8
9
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 请求后拦截的两个实例。

1
2
3
4
5
var future = Future<dynamic>(() => InterceptorState(requestOptions));

interceptors.forEach((Interceptor interceptor) {
  future = future.then(_requestInterceptorWrapper(interceptor.onRequest));
});
第四步:拦截器转换为函数回调

这里主要做的一步操作是把函数的回调作为方法的参数,这样就实现了把拦截器转换为函数回调,这里做了一层判断,如果state.type 等于 next 的话,那么会增加一个监听取消的异步任务,并把cancelToken传递给了这个任务,接下来他会检查当前的这个拦截器请求是否入队,最后定义了一个请求拦截器的变量,该拦截器里面有三个主要的方法分别是next() 、resole()reject() ,最后把这个拦截器返回出去。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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;
    }
  };
}
第五步:构建请求流调度回调

调度回调和添加拦截器转换为函数回调,不同的是调度回调里面进行了请求分发。

1
2
3
4
5
6
7
8
9
10
11
12
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
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 中是对响应数据设定、构建请求流、添加拦截器、请求分发的操作。

本文由作者按照 CC BY 4.0 进行授权