HttpClientAdapter(抽象类)
abstract class HttpClientAdapter {
factory HttpClientAdapter() => adapter.createAdapter();
Future<ResponseBody> fetch(
RequestOptions options,
Stream<Uint8List>? requestStream,
Future<void>? cancelFuture,
);
void close({bool force = false});
}
HttpAdapter 类作为 Dio 和 HttpClient 之间的桥梁,用于实现 HTTP 请求的功能。
以下是关于 HttpAdapter 的一些关键点:
-
HttpAdapter充当了高级别的DioAPI 和底层的HttpClient实现之间的接口。 -
Dio提供了标准化且开发者友好的 API,而HttpClient是实际执行 HTTP 请求的对象。 -
通过提供自定义的
HttpClientAdapter实现,您可以使用任何HttpClient来发起 HTTP 请求,而不仅仅是默认的dart:io:HttpClient。 -
根据您所使用的平台,您可以使用不同的
HttpClientAdapter实现:- 在
dart:io平台上,您可以使用IOHttpClientAdapter。 - 在
dart:html平台上,您可以使用BrowserHttpClientAdapter。
- 在
-
要为
Dio设置自定义的HttpClientAdapter,可以将适配器的实例赋值给Dio实例的httpClientAdapter属性。
fetch
/// We should implement this method to make real http requests.
///
/// [options] are the request options.
///
/// [requestStream] The request stream, It will not be null
/// only when http method is one of "POST","PUT","PATCH"
/// and the request body is not empty.
///
/// We should give priority to using requestStream(not options.data) as request data.
/// because supporting stream ensures the `onSendProgress` works.
///
/// When cancelled the request, [cancelFuture] will be resolved!
/// you can listen cancel event by it, for example:
///
/// ```dart
/// cancelFuture?.then((_)=>print("request cancelled!"))
/// ```
/// [cancelFuture] will be null when the request is not set [CancelToken].
Future<ResponseBody> fetch(
RequestOptions options,
Stream<Uint8List>? requestStream,
Future<void>? cancelFuture,
);
为了进行真实的 HTTP 请求,我们需要实现 fetch 方法。该方法具有以下参数和功能:
options:请求的选项,包括 URL、请求方法、请求头等信息。requestStream:请求的数据流,仅当请求方法为 "POST"、"PUT"、"PATCH" 且请求体不为空时,该参数才不为 null。我们应优先使用requestStream而不是options.data作为请求数据,因为支持流式请求能够保证onSendProgress方法的正常工作。cancelFuture:当请求被取消时,该Future对象会被解析。您可以通过监听该cancelFuture事件来处理请求取消的情况
如果请求未设置 CancelToken,则 cancelFuture 将为 null。
该方法应返回一个 Future<ResponseBody> 对象,其中 ResponseBody 是表示响应体的类型。您可以根据实际需求来处理响应数据。
通过实现 fetch 方法,您可以使用底层的 HTTP 客户端执行真实的请求,并处理请求取消的情况。在实现时,您可以根据具体的需求选择适当的 HTTP 客户端,处理请求选项和数据流,以及监听取消事件。
close
void close({bool force = false});
close 方法用于关闭 HTTP 适配器,并释放相关资源。它具有一个可选参数 force,用于指定是否强制关闭。方法的具体行为如下:
- 当
force为false时,关闭操作会等待所有正在进行的请求完成后再关闭适配器。这意味着已发送但尚未完成的请求将继续执行,直到完成或取消。 - 当
force为true时,关闭操作将立即中断所有正在进行的请求,并关闭适配器。
使用 close 方法可以在不需要继续进行请求时主动关闭适配器,以释放资源并终止连接。需要注意的是,关闭适配器后将无法再发送新的请求。
IOHttpClientAdapter(实现类)
负责跟底层http请求打交道的类。
Dio封装的request、_dispatchRequest请求方法里调用。
/// The default [HttpClientAdapter] for native platforms.
class IOHttpClientAdapter implements HttpClientAdapter {
IOHttpClientAdapter({this.onHttpClientCreate, this.validateCertificate});
/// [Dio] will create [HttpClient] when it is needed. If [onHttpClientCreate]
/// has provided, [Dio] will call it when a [HttpClient] created.
OnHttpClientCreate? onHttpClientCreate;
/// Allows the user to decide if the response certificate is good.
/// If this function is missing, then the certificate is allowed.
/// This method is called only if both the [SecurityContext] and
/// [badCertificateCallback] accept the certificate chain. Those
/// methods evaluate the root or intermediate certificate, while
/// [validateCertificate] evaluates the leaf certificate.
ValidateCertificate? validateCertificate;
HttpClient? _defaultHttpClient;
}
IOHttpClientAdapter 类是实现了 HttpClientAdapter 接口的一个具体实现,用于在 dart:io 平台上进行 HTTP 请求。它具有以下可选参数:
onHttpClientCreate:一个回调函数,用于在创建HttpClient实例时进行自定义操作。您可以在此回调中对HttpClient进行配置或添加拦截器等操作。validateCertificate:一个用于验证服务器证书的回调函数。您可以在此回调中实现自定义的证书验证逻辑。
您可以通过创建 IOHttpClientAdapter 的实例并将其分配给 Dio 的 httpClientAdapter 属性来使用该适配器。
_defaultHttpClient
HttpClient? _defaultHttpClient;
bool _closed = false;
_defaultHttpClient 是一个私有成员变量,其类型为 HttpClient?,用于存储默认的 HttpClient 实例。
在 IOHttpClientAdapter 类中,_defaultHttpClient 用于存储创建的 HttpClient 实例,以便在进行 HTTP 请求时复用该实例。这样可以减少创建和销毁 HttpClient 的开销,并提高性能和效率。
通常情况下,您无需直接访问或操作 _defaultHttpClient 变量。它是作为 IOHttpClientAdapter 内部实现的一部分而存在的。
_configHttpClient
HttpClient _configHttpClient(
Future<void>? cancelFuture,
Duration? connectionTimeout,
) {
HttpClient client = onHttpClientCreate?.call(HttpClient()) ?? HttpClient();
if (cancelFuture != null) {
client.userAgent = null;
client.idleTimeout = Duration(seconds: 0);
cancelFuture.whenComplete(() => client.close(force: true));
return client..connectionTimeout = connectionTimeout;
}
if (_defaultHttpClient == null) {
client.idleTimeout = Duration(seconds: 3);
if (onHttpClientCreate?.call(client) != null) {
client = onHttpClientCreate!(client)!;
}
client.connectionTimeout = connectionTimeout;
_defaultHttpClient = client;
}
return _defaultHttpClient!..connectionTimeout = connectionTimeout;
}
该 _configHttpClient 方法用于配置 HttpClient,根据传入的参数设置相应的属性,并返回配置后的 HttpClient 实例。
具体的配置步骤如下:
- 首先,通过调用
onHttpClientCreate回调函数创建一个HttpClient实例,如果回调函数返回非空值,则使用回调函数返回的实例。否则,创建一个新的HttpClient实例。 - 如果传入的
cancelFuture参数不为空,表示请求设置了取消操作。在这种情况下,将client的userAgent设置为null,idleTimeout设置为Duration(seconds: 0),并且当cancelFuture完成时,强制关闭client。然后将connectionTimeout设置为传入的connectionTimeout参数,并返回client。 - 如果
_defaultHttpClient为空,表示之前没有创建过默认的HttpClient。在这种情况下,将client的idleTimeout设置为Duration(seconds: 3),并检查是否有onHttpClientCreate回调函数并调用它。如果回调函数返回非空值,则使用回调函数返回的实例替换client。然后将connectionTimeout设置为传入的connectionTimeout参数,并将client赋值给_defaultHttpClient。 - 最后,返回
_defaultHttpClient并将其connectionTimeout设置为传入的connectionTimeout参数。
fetch
Future<ResponseBody> fetch(
RequestOptions options,
Stream<Uint8List>? requestStream,
Future<void>? cancelFuture,
) async {
if (_closed) {
throw StateError(
"Can't establish connection after the adapter was closed!",
);
}
final httpClient = _configHttpClient(cancelFuture, options.connectTimeout);
final reqFuture = httpClient.openUrl(options.method, options.uri);
late HttpClientRequest request;
try {
final connectionTimeout = options.connectTimeout;
if (connectionTimeout != null) {
request = await reqFuture.timeout(
connectionTimeout,
onTimeout: () {
throw DioError.connectionTimeout(
requestOptions: options,
timeout: connectionTimeout,
);
},
);
} else {
request = await reqFuture;
}
// Set Headers
options.headers.forEach((k, v) {
if (v != null) request.headers.set(k, v);
});
} on SocketException catch (e) {
if (!e.message.contains('timed out')) {
rethrow;
}
throw DioError.connectionTimeout(
requestOptions: options,
timeout: options.connectTimeout ??
httpClient.connectionTimeout ??
Duration.zero,
error: e,
);
}
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) {
future = future.timeout(
sendTimeout,
onTimeout: () {
request.abort();
throw DioError.sendTimeout(
timeout: sendTimeout,
requestOptions: options,
);
},
);
}
await future;
}
final stopwatch = Stopwatch()..start();
Future<HttpClientResponse> future = request.close();
final receiveTimeout = options.receiveTimeout;
if (receiveTimeout != null) {
future = future.timeout(
receiveTimeout,
onTimeout: () {
throw DioError.receiveTimeout(
timeout: receiveTimeout,
requestOptions: options,
);
},
);
}
final responseStream = await future;
if (validateCertificate != null) {
final host = options.uri.host;
final port = options.uri.port;
final bool isCertApproved = validateCertificate!(
responseStream.certificate,
host,
port,
);
if (!isCertApproved) {
throw DioError(
requestOptions: options,
type: DioErrorType.badCertificate,
error: responseStream.certificate,
message: 'The certificate of the response is not approved.',
);
}
}
final stream = responseStream.transform<Uint8List>(
StreamTransformer.fromHandlers(
handleData: (data, sink) {
stopwatch.stop();
final duration = stopwatch.elapsed;
final receiveTimeout = options.receiveTimeout;
if (receiveTimeout != null && duration > receiveTimeout) {
sink.addError(
DioError.receiveTimeout(
timeout: receiveTimeout,
requestOptions: options,
),
);
responseStream.detachSocket().then((socket) => socket.destroy());
} else {
sink.add(Uint8List.fromList(data));
}
},
),
);
final headers = <String, List<String>>{};
responseStream.headers.forEach((key, values) {
headers[key] = values;
});
return ResponseBody(
stream,
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,
);
}
-
首先,检查适配器是否已关闭。如果适配器已关闭,表示无法在适配器关闭后建立连接,因此抛出
StateError异常。 -
使用获取到的
HttpClient对象调用openUrl方法来创建一个HttpClientRequest对象。openUrl方法接收请求方法(options.method)和请求URL(options.uri)作为参数,并返回一个Future,在未来某个时间点会提供一个HttpClientRequest对象。根据连接超时时间(
options.connectTimeout)配置处理连接超时的逻辑。如果连接超时时间不为null,则使用timeout方法对reqFuture进行超时处理。当连接超时时,抛出DioError.connectionTimeout异常。如果连接超时时间为
null,则直接等待reqFuture的完成,获取到HttpClientRequest对象。 -
在
try块中,通过遍历options.headers设置请求的头信息。对于每个键值对,如果值不为null,则将其设置到request.headers中。如果在创建请求的过程中捕获到
SocketException异常,并且异常消息不包含'timed out',则重新抛出该异常。否则,抛出DioError.connectionTimeout异常,指示连接超时。在这种情况下,DioError异常包含请求选项(options)、连接超时时间(options.connectTimeout或httpClient.connectionTimeout)和捕获的异常(e)。 -
设置请求的重定向配置和持久连接配置。根据
options.followRedirects设置是否跟随重定向,options.maxRedirects设置最大重定向次数,以及options.persistentConnection设置是否使用持久连接。 -
如果
requestStream不为null,则表示有请求体数据需要发送。使用addStream方法将请求数据流写入请求中。将返回的Future赋给future变量,以便进行超时处理。如果
options.sendTimeout不为null,则对future进行超时处理。当发送超时时,调用request.abort()中止请求,并抛出DioError.sendTimeout异常。 等待future的完成,即请求数据的发送过程。 -
等待
future的完成,即接收到响应。将响应流赋给responseStream变量。如果
validateCertificate回调函数不为null,则使用validateCertificate函数验证响应的证书是否 被批准。调用validateCertificate函数并传入响应的证书(responseStream.certificate)、请求 的主 机(options.uri.host)和端口(options.uri.port)。如果验证结果为false,表示响应的 证书未被 批准, -
在证书未被批准的情况下,抛出
DioError异常,指示证书无效。异常包含请求选项(options)、异常类型(DioErrorType.badCertificate)、错误信息(responseStream.certificate)和错误消息('The certificate of the response is not approved.')。 -
通过使用
transform方法将响应流responseStream转换为Stream<Uint8List>类型的流stream。使用StreamTransformer.fromHandlers创建一个处理器来处理每个数据块。处理器中首先停止计时器
stopwatch,然后计算接收响应所花费的时间duration。如果存在接收超时时间
options.receiveTimeout,并且实际接收时间超过了超时时间,则通过向sink添加DioError.receiveTimeout异常来触发接收超时错误。然后,将响应流的底层套接字分离并销毁。如果未超过接收超时时间,则将数据块
data转换为Uint8List类型,并通过sink添加到流中。 -
创建一个空的
headers字典,并通过遍历responseStream.headers将响应头信息添加到headers中。构造并返回一个
ResponseBody对象,其中包含转换后的响应流stream、响应的状态码responseStream.statusCode、响应头headers、是否为重定向responseStream.isRedirect || responseStream.redirects.isNotEmpty、重定向记录列表responseStream.redirects.map(...).toList()和状态消息responseStream.reasonPhrase。
close
void close({bool force = false}) {
_closed = true;
_defaultHttpClient?.close(force: force);
}
在方法中,首先将私有成员变量_closed设置为true,表示适配器已关闭。
然后,通过访问_defaultHttpClient属性获取默认的HttpClient实例,并调用其close方法来关闭客户端。可选参数force用于指定是否强制关闭。如果force为true,则会强制关闭客户端,即使有活动的请求也会被中断。如果force为false(默认值),则只有在所有请求完成后才会关闭客户端。
注意,通过调用close方法关闭适配器后,将无法再执行新的请求。