Dio分析-HttpClientAdapter

官方描述

用于Dio与底层HttpClient间的桥接。
Dio向业务层开发者提供标准和友好的API接口
HttpClient, Dart底层真实的网络请求处理对象。
通过HttpClientAdapter,开发者可以使用各种自定义HttpClient,而非dart:io:HttpClient,只需要通过HttpClientAdapter实现桥接。
如果开发者想要定制HttpClientAdapter, 可以使用来自dart:io的IOHttpClientAdapter或者来自dart:html BrowserHttpClientAdapter,两种Adapter提供不同底层网络数据请求的处理逻辑.

[HttpAdapter] is a bridge between [Dio] and [HttpClient].

[Dio] implements standard and friendly API for developer. [HttpClient] is the real object that makes Http requests.

We can use any [HttpClient]s not just “dart:io:HttpClient” to make the HTTP request. All we need is to provide a [HttpClientAdapter].

If you want to customize the [HttpClientAdapter] you should instead use either [IOHttpClientAdapter] on dart:io platforms or [BrowserHttpClientAdapter] on dart:html platforms.

设计概念

HttpClientAdapter提供了HttpClient与Dio交互的行为准则,所以使用implements进行关联。例如dart:io_adapter

class IOHttpClientAdapter implements HttpClientAdapter

细节分析

import 'adapters/io_adapter.dart'
    if (dart.library.html) 'adapters/browser_adapter.dart' as adapter;

abstract class HttpClientAdapter {
  /// Create a [HttpClientAdapter] based on the current platform (IO/Web).
  factory HttpClientAdapter() => adapter.createAdapter();

  /// Implement this method to make real HTTP requests.
  ///
  /// [options] are the request options.
  ///
  /// [requestStream] is the request stream. It will not be null only when
  /// the request body is not empty.
  /// Use [requestStream] if your code rely on [RequestOptions.onSendProgress].
  ///
  /// [cancelFuture] corresponds to [CancelToken] handling.
  /// When the request is canceled, [cancelFuture] will be resolved.
  /// To await if a request has been canceled:
  /// ```dart
  /// cancelFuture?.then((_) => print('request cancelled!'));
  /// ```
  Future<ResponseBody> fetch(
    RequestOptions options,
    Stream<Uint8List>? requestStream,
    Future<void>? cancelFuture,
  );

  /// Close the current adapter and its inner clients or requests.
  void close({bool force = false});
}

HttpClientAdapter的协议很简单,主要是实际网络交互对象adapter需要提供一个全局的createAdapter方法。
提供fetch和close两个方法,用于数据交互。
fetch方法用于实现发起真实的Http请求,Options(Request Options)对象。Request stream是数据流请求。只有当请求body为数据流非空时设置。cancelFuture提供请求取消的canceltoken的处理(具体参见IOHttpClientAdapter),以future形式提供请求取消的异步处理流程。
close方法关闭当前adapter和相应httpclient及请求。

/// Implement this method to make real HTTP requests.
  ///
  /// [options] are the request options.
  ///
  /// [requestStream] is the request stream. It will not be null only when
  /// the request body is not empty.
  /// Use [requestStream] if your code rely on [RequestOptions.onSendProgress].
  ///
  /// [cancelFuture] corresponds to [CancelToken] handling.
  /// When the request is canceled, [cancelFuture] will be resolved.
  /// To await if a request has been canceled:
  /// ```dart
  /// cancelFuture?.then((_) => print('request cancelled!'));
  /// ```
  Future<ResponseBody> fetch(
    RequestOptions options,
    Stream<Uint8List>? requestStream,
    Future<void>? cancelFuture,
  );

  /// Close the current adapter and its inner clients or requests.
  void close({bool force = false});

HttpClientAdapter也定义ResponseBody作为交互数据类型,fromString,fromByte工厂方法提供response的底层数据返回构造,statusCode,contentLenght这些标准的httpResponse的信息.

class ResponseBody {
  ResponseBody(
    this.stream,
    this.statusCode, {
    this.statusMessage,
    this.isRedirect = false,
    this.redirects,
    void Function()? onClose,
    Map<String, List<String>>? headers,
  })  : headers = headers ?? {},
        _onClose = onClose;

  ResponseBody.fromString(
    String text,
    this.statusCode, {
    this.statusMessage,
    this.isRedirect = false,
    void Function()? onClose,
    Map<String, List<String>>? headers,
  })  : stream = Stream.value(Uint8List.fromList(utf8.encode(text))),
        headers = headers ?? {},
        _onClose = onClose;

  ResponseBody.fromBytes(
    List<int> bytes,
    this.statusCode, {
    this.statusMessage,
    this.isRedirect = false,
    void Function()? onClose,
    Map<String, List<String>>? headers,
  })  : stream = Stream.value(
          bytes is Uint8List ? bytes : Uint8List.fromList(bytes),
        ),
        headers = headers ?? {},
        _onClose = onClose;

  /// Whether this response is a redirect.
  final bool isRedirect;

  /// The response stream.
  Stream<Uint8List> stream;

  /// HTTP status code.
  int statusCode;

  /// Content length of the response or -1 if not specified
  int get contentLength =>
      int.parse(headers[Headers.contentLengthHeader]?.first ?? '-1');

  /// Returns the reason phrase corresponds to the status code.
  /// The message can be [HttpRequest.statusText]
  /// or [HttpClientResponse.reasonPhrase].
  String? statusMessage;

  /// Stores redirections during the request.
  List<RedirectRecord>? redirects;

  /// The response headers.
  Map<String, List<String>> headers;

  /// The extra field which will pass-through to the [Response.extra].
  Map<String, dynamic> extra = {};

  final void Function()? _onClose;

  /// Closes the request & frees the underlying resources.
  
  void close() => _onClose?.call();
}

IOHttpClientAdapter

引入dart:io,看一下标准的IOHttpClientAdapter的构造逻辑。
首先,实现HttpClientAdapter

class IOHttpClientAdapter implements HttpClientAdapter

提供全局的createAdapter方法

HttpClientAdapter createAdapter() => IOHttpClientAdapter();

可以初始化三个回调, OnHttpClientCreate回调httpClient创建成功;CreateHttpClient,定制httpClient创建;ValidateCertificate,定制校验证书。

/// The signature of [IOHttpClientAdapter.onHttpClientCreate].
('Use CreateHttpClient instead. This will be removed in 6.0.0')
typedef OnHttpClientCreate = HttpClient? Function(HttpClient client);

/// The signature of [IOHttpClientAdapter.createHttpClient].
/// Can be used to provide a custom [HttpClient] for Dio.
typedef CreateHttpClient = HttpClient Function();

/// The signature of [IOHttpClientAdapter.validateCertificate].
typedef ValidateCertificate = bool Function(
  X509Certificate? certificate,
  String host,
  int port,
);

  IOHttpClientAdapter({
    ('Use createHttpClient instead. This will be removed in 6.0.0')
    this.onHttpClientCreate,
    this.createHttpClient,
    this.validateCertificate,
  });

以常用的validateCertificate为例子

final fingerprint =
      // 'update-with-latest-sha256-hex-ee5ce1dfa7a53657c545c62b65802e4272';
      // should look like this:
      'ee5ce1dfa7a53657c545c62b65802e4272878dabd65c0aadcf85783ebb0b4d5c';

  // Don't trust any certificate just because their root cert is trusted
  dio.httpClientAdapter = IOHttpClientAdapter(
    createHttpClient: () {
      final client = HttpClient(
        context: SecurityContext(withTrustedRoots: false),
      );
      // You can test the intermediate / root cert here. We just ignore it.
      client.badCertificateCallback = (cert, host, port) => true;
      return client;
    },
    validateCertificate: (cert, host, port) {
      // Check that the cert fingerprint matches the one we expect
      // We definitely require _some_ certificate
      if (cert == null) return false;
      // Validate it any way you want. Here we only check that
      // the fingerprint matches the OpenSSL SHA256.
      final f = sha256.convert(cert.der).toString();
      print(f);
      return fingerprint == f;
    },
  );

创建一个带有securityContext的httpClient,validateCertificate校验fingerprint(证书指纹)。
证书中的fingerprint
通过_cachedHttpClient来实现httpClient的缓存。

fetch

fetch的核心就是_fetch方法

  Future<ResponseBody> _fetch(
    RequestOptions options,
    Stream<Uint8List>? requestStream,
    Future<void>? cancelFuture,
  ) async {

options提供链接相关的配置, requestStream提供请求写入流对象,cancelFuture用于提供取消机制。

整个流程通过await Future将连接过程拆分成connect-> send request -> response receive -> validate cert -> response stream

connect过程:通过HttpClient.openUrl实现。

Future<HttpClientRequest> openUrl(String method, Uri url);
    late HttpClientRequest request;
    try {
      final connectionTimeout = options.connectTimeout;
      if (connectionTimeout != null && connectionTimeout > Duration.zero) {
        request = await reqFuture.timeout(
          connectionTimeout,
          onTimeout: () {
            throw DioException.connectionTimeout(
              requestOptions: options,
              timeout: connectionTimeout,
            );
          },
        );
      } else {
        request = await reqFuture;
      }

      cancelFuture?.whenComplete(() => request.abort());

      // Set Headers
      options.headers.forEach((key, value) {
        if (value != null) {
          request.headers.set(
            key,
            value,
            preserveHeaderCase: options.preserveHeaderCase,
          );
        }
      });
    } on SocketException catch (e) {
      if (e.message.contains('timed out')) {
        final Duration effectiveTimeout;
        if (options.connectTimeout != null &&
            options.connectTimeout! > Duration.zero) {
          effectiveTimeout = options.connectTimeout!;
        } else if (httpClient.connectionTimeout != null &&
            httpClient.connectionTimeout! > Duration.zero) {
          effectiveTimeout = httpClient.connectionTimeout!;
        } else {
          effectiveTimeout = Duration.zero;
        }
        throw DioException.connectionTimeout(
          requestOptions: options,
          timeout: effectiveTimeout,
          error: e,
        );
      }
      throw DioException.connectionError(
        requestOptions: options,
        reason: e.message,
        error: e,
      );
    }```

> send Request阶段:第一阶段以HttpClientRequest作为完结作为第二阶段的操作对象,如果request需要body写入数据流,request.addStream实现body流添加,HttpClinetRequest作为完结。 HttpClientRequest继承IOSink实现流body的添加

```dart
abstract interface class HttpClientRequest implements IOSink
    request.followRedirects = options.followRedirects;
    request.maxRedirects = options.maxRedirects;
    request.persistentConnection = options.persistentConnection;

    if (requestStream != null) {
      // Transform the request data.
      Future<dynamic> future = request.addStream(requestStream);
      final sendTimeout = options.sendTimeout;
      if (sendTimeout != null && sendTimeout > Duration.zero) {
        future = future.timeout(
          sendTimeout,
          onTimeout: () {
            request.abort();
            throw DioException.sendTimeout(
              timeout: sendTimeout,
              requestOptions: options,
            );
          },
        );
      }
      await future;
    }

response receive阶段,以第一第二阶段完结的HttpClientRequest作为处理对象,request.close()实现请求发送和响应接收,以HttpClientResponse作为完结

    Future<HttpClientResponse> future = request.close();
    final receiveTimeout = options.receiveTimeout ?? Duration.zero;
    if (receiveTimeout > Duration.zero) {
      future = future.timeout(
        receiveTimeout,
        onTimeout: () {
          request.abort();
          throw DioException.receiveTimeout(
            timeout: receiveTimeout,
            requestOptions: options,
          );
        },
      );
    }
    final responseStream = await future;

第四阶段validate certification,校验证书,如果配置的校验方法成立,对证书进行校验

    if (validateCertificate != null) {
      final host = options.uri.host;
      final port = options.uri.port;
      final bool isCertApproved = validateCertificate!(
        responseStream.certificate,
        host,
        port,
      );
      if (!isCertApproved) {
        throw DioException(
          requestOptions: options,
          type: DioExceptionType.badCertificate,
          error: responseStream.certificate,
          message: 'The certificate of the response is not approved.',
        );
      }
    }

第五阶段,以Adapter定义的ResponseBody对象作为完结的future返回,并提供response stream的读取流,在response stream完结的时候,完成stream socket释放.

abstract interface class HttpClientResponse implements Stream<List<int>>
    final headers = <String, List<String>>{};
    responseStream.headers.forEach((key, values) {
      headers[key] = values;
    });
    return ResponseBody(
      responseStream.cast(),
      responseStream.statusCode,
      headers: headers,
      isRedirect:
          responseStream.isRedirect || responseStream.redirects.isNotEmpty,
      redirects: responseStream.redirects
          .map((e) => RedirectRecord(e.statusCode, e.method, e.location))
          .toList(),
      statusMessage: responseStream.reasonPhrase,
      onClose: () {
        responseStream.detachSocket().then((socket) => socket.destroy());
      },
    );

结论

如官方所述,HttpClientAdapter用于桥接底层HttpClient和DioMixin,可以定制HttpClient实现特定的连接。DioMixin中通过_dispatchRequest实现请求交互和response数据的处理。

  • 7
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值