官方描述
用于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(证书指纹)。
通过_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数据的处理。