dio_http2_adapter 演进全景:Dio HTTP/2 适配器的连接复用、安全加固与版本能力图谱
【免费下载链接】dioA powerful HTTP client for Dart and Flutter, which supports global settings, Interceptors, FormData, aborting and canceling a request, files uploading and downloading, requests timeout, custom adapters, etc.项目地址: https://gitcode.com/gh_mirrors/di/dio
导读:本文以
dio_http2_adapter插件(Dio 生态中基于package:http2的 HTTP/2 适配器)的 CHANGELOG 为主线,结合源码逐项还原其从 0.0.2 到 2.8.0 的能力演进:连接复用与超时体系、重定向与敏感头保护、代理隧道、证书校验、回退机制与流式传输修复。读完本文,你将掌握该适配器各核心配置参数的语义与默认值、底层调用链,以及升级到新版时需要注意的破坏性变更,可直接用于实际接入与排障。
一、插件定位与版本脉络总览
dio_http2_adapter是 Dio 官方插件体系中把HTTP/2 协议能力接入 Dio 的标准方案。它实现了HttpClientAdapter接口,通过package:http2提供连接复用(connection reuse)、头部压缩(header compression)等 HTTP/2 特性,覆盖原生平台(IO)场景。
安装方式是在项目pubspec.yaml的 dependencies 中加入依赖(版本以 pubspec.yaml 中version: 2.8.0为准):
dependencies: dio: ^5.2.0 dio_http2_adapter: ^2.8.0包要求 Dart SDK>=3.0.0 <4.0.0(该要求自 2.5.0 起因http2包升级而引入)。CHANGELOG 记录了从 2019 年 9 月首个版本到当前 2.8.0 的完整演进,核心里程碑如下:
| 版本 | 发布时间/阶段 | 关键变化 |
|---|---|---|
| 0.0.2 | 2019.9.17 | 首个支持 HTTP/2.0 的 Dio HttpAdapter |
| 1.0.0 | 2019.9.18 | 支持重定向 |
| 2.0.0-beta2 | null-safety 时代 | 支持空安全、适配 dio 4.x |
| 2.0.0 | dio 4.0 时代 | 正式支持 dio 4.0.0 |
| 2.1.0 | dio 5.0 时代 | 面向 dio 5.0 发布;新增validateCertificate |
| 2.2.0 | 适配器支持代理 | |
| 2.3.0 | DioError全面替换为DioException | |
| 2.3.2 | 实现sendTimeout/receiveTimeout,忽略Duration.zero | |
| 2.4.0 | 支持非 TLS 连接请求;改进receiveTimeout | |
| 2.5.0 | 修复流式响应/下载的取消与进度;Dart SDK 升至 3.0.0;支持 HTTP/1.0 代理;新增回退适配器 | |
| 2.6.0 | 缓存连接尊重重定向与 scheme(含破坏性变更) | |
| 2.7.0 | 修复缓存键不一致导致的内存泄漏;新增handshakeTimeout;Response.extra提供httpVersion | |
| 2.7.1 | 修复:authority头生成未包含非默认端口的问题 | |
| 2.8.0 | Unreleased | 跨源重定向剥离敏感头;修复上传时连接关闭抛StateError |
二、核心架构:Http2Adapter 与 ConnectionManager 的双层设计
从源码结构看,插件入口 lib/dio_http2_adapter.dart 仅导出src/http2_adapter.dart,而后者通过part指令把 client_setting.dart、connection_manager.dart、connection_manager_imp.dart 组合为一个库,形成清晰的职责分层:
- Http2Adapter(src/http2_adapter.dart):实现
HttpClientAdapter.fetch(),负责把 Dio 的RequestOptions翻译为 HTTP/2 帧流,并处理响应、重定向、超时; - ConnectionManager(抽象接口,src/connection_manager.dart):管理可复用连接,定义
idleTimeout、handshakeTimeout、onClientCreate、proxyConnectedPredicate等配置; - _ConnectionManager(默认实现,src/connection_manager_imp.dart):维护
_transportsMap与_connectFutures两张缓存表,实现连接创建、复用、空闲回收; - ClientSetting(src/client_setting.dart):透传
SecurityContext、onBadCertificate、validateCertificate、proxy到 socket 层。
最简接入示例(与 example/example.dart 一致):
import 'package:dio/dio.dart'; import 'package:dio_http2_adapter/dio_http2_adapter.dart'; void main() async { final dio = Dio() ..options.baseUrl = 'https://pub.dev' ..interceptors.add(LogInterceptor()) ..httpClientAdapter = Http2Adapter( ConnectionManager(idleTimeout: Duration(seconds: 10)), ); Response<String> response; response = await dio.get('/?xx=6'); for (final e in response.redirects) { print('redirect: ${e.statusCode} ${e.location}'); } print(response.data); }三、连接复用:缓存键、空闲回收与握手超时
3.1 缓存键与内存泄漏修复(2.7.0)
_ConnectionManager用_getCacheKey(uri)生成连接缓存键,格式为scheme://host:port(例如https://example.com:443)。在 2.7.0 之前,getConnection()与_connect()两处生成缓存键的方式不一致,导致空闲超时清理时_transportsMap.remove()静默失败,进而引发连接泄漏。2.7.0 统一了键格式,保证存储与移除路径一致(src/connection_manager_imp.dart)。
3.2 idleTimeout:非活动持久连接的空闲回收
idleTimeout控制非活动持久连接的存活时长,默认 15 秒。注意实现中的下限约束:_ClientTransportConnectionState.delayClose会把小于 100ms 的超时强制提升为 100ms(src/connection_manager_imp.dart)。定时器逻辑会在连接活跃时重置计时,仅在连续空闲达到阈值后才关闭连接并移除缓存。
3.3 handshakeTimeout:握手超时兜底(2.7.0)
2.7.0 为ConnectionManager新增handshakeTimeout(默认 15 秒),作用于SecureSocket.connect与代理场景下SecureSocket.secure返回的握手 Future,避免对端握手异常时无限期等待(src/connection_manager_imp.dart)。注意工厂构造参数实际拼写为handshakeTimout(历史拼写保留),语义即握手超时:
ConnectionManager( idleTimeout: const Duration(seconds: 15), handshakeTimout: const Duration(seconds: 15), onClientCreate: (uri, config) { /* 自定义 socket 配置 */ }, )3.4 缓存连接尊重重定向与 scheme(2.6.0)
2.6.0 让缓存连接感知重定向结果与协议 scheme:getConnection()新增List<RedirectRecord> redirects参数,在查找/新建连接前先用重定向目标解析出真实 URI(src/connection_manager_imp.dart),从而保证从非 TLS 重定向到 TLS 目标时能正确建立新连接。
这是 CHANGELOG 标注的破坏性变更:若你在子类中自定义了ConnectionManager,需要同步把getConnection签名改为携带redirects参数(详见 doc/migration_guide.md):
Future<ClientTransportConnection> getConnection( RequestOptions options, + List<RedirectRecord> redirects, );四、重定向:从支持到跨源敏感头保护
- 1.0.0起支持重定向。
Http2Adapter内核对 301/302/303/307/308 状态码、在followRedirects && maxRedirects > 0时自动跟进,并在无location头时抛出connectionError(src/http2_adapter.dart)。重定向次数通过maxRedirects - 1递归递减控制。 - 2.7.1修复
:authority头生成未包含非默认端口的问题,保证代理或非标准端口场景下 Host 协商正确。 - 2.8.0新增跨源敏感头保护:适配器维护
_sensitiveRedirectHeaders集合(authorization、www-authenticate、cookie、cookie2、proxy-authorization、proxy-authenticate)。当重定向目标与当前请求不同源(scheme、host、port 任一不同)时,会从重定向请求中移除这些敏感头,防止凭据泄露到跨源服务器(src/http2_adapter.dart 与 src/http2_adapter.dart)。同源判定由_isSameOrigin实现。
五、超时体系:connect、send、receive 三态实现
- 2.3.2为适配器实现了
sendTimeout与receiveTimeout,并忽略Duration.zero超时(零值视为未设置);同时修复requestStream == null时重定向不生效的问题。 - 2.4.0改进
receiveTimeout实现,使其更精确地作用于响应头到达阶段。 - 2.7.0通过
handshakeTimeout补全了握手阶段的兜底。
从源码看(src/http2_adapter.dart):发送超时对“请求体流式上传完成”这一 Future 施加timeout,超时后关闭出站消息流并抛出DioException.sendTimeout;接收超时则对“响应头到达”的 Future 施加timeout,超时后取消订阅、关闭响应流并抛出DioException.receiveTimeout(src/http2_adapter.dart)。连接阶段超时则由connectTimeout透传至Socket.connect/SecureSocket.connect的timeout参数,并区分“超时”与“普通连接错误”抛错。
六、证书校验:ClientSetting 的三个层级
2.1.0 为ClientSetting新增validateCertificate,与 Dio 5.0 一起发布。ClientSetting提供三套证书处理入口(src/client_setting.dart):
context:SecurityContext,决定使用哪些受信任根证书做链校验;onBadCertificate:不可验证证书的可选处理回调,返回true则放行该SecureSocket连接;validateCertificate:对叶子证书做最终裁决(仅在链校验通过后才被调用),返回false时抛出DioExceptionType.badCertificate。
忽略证书的典型用法(来自 README.md):
final dio = Dio() ..httpClientAdapter = Http2Adapter( ConnectionManager( idleTimeout: Duration(seconds: 10), onClientCreate: (_, config) => config.onBadCertificate = (_) => true, ), );七、代理支持:CONNECT 隧道与 HTTP/1.x 代理
- 2.2.0起支持代理。代理通过
ClientSetting.proxy设置(用Uri完整表达 scheme、地址、端口及可选凭据)。 - 2.5.0允许连接代理时使用 HTTP/1.0,放宽了对代理协议版本的限定。
底层实现(src/connection_manager_imp.dart):先与代理地址建立 TCP 连接,发送CONNECT host:port HTTP/1.1请求(带Host头与Proxy-Authorization: Basic ...),随后用SecureSocket.secure在该隧道上做 TLS 握手并协商h2。代理握手结果通过proxyConnectedPredicate判定,默认实现defaultProxyConnectedPredicate接受状态行匹配HTTP/1.x 200的响应(src/connection_manager.dart)。
final dio = Dio() ..httpClientAdapter = Http2Adapter( ConnectionManager( idleTimeout: Duration(seconds: 10), onClientCreate: (_, config) => config.proxy = Uri.parse('http://login:password@192.168.0.1:8888'), ), );八、回退机制与异常体系:HTTP/2 不可用时的兜底路径
- 2.5.0引入回退能力:当目标服务器未协商出
h2协议时,_createSocket会通过_throwIfH2NotSelected抛出DioH2NotSupportedException(src/connection_manager_imp.dart);Http2Adapter.fetch捕获后,若配置了onNotSupported回调则交由回调处理,否则交给fallbackAdapter(默认IOHttpClientAdapter)发起普通 HTTP/1.x 请求(src/http2_adapter.dart)。 - 2.5.1把握手/建连阶段的
SocketException统一包装为DioExceptionType.connectionError(消息含timed out的包装为connectionTimeout),而不是笼统的unknown,便于调用方精确分类(src/http2_adapter.dart)。 - 2.3.0起全面用
DioException替代DioError,与 dio 5.x 的异常体系对齐。
九、流式上传下载、取消与进度修复
该插件对流式场景的修复贯穿多个版本,是排障时最容易踩坑的部分:
- 2.5.0修复流式响应与下载的取消和进度问题(取消时同步关闭出站消息流、终止响应订阅)。
- 2.5.2移除
Http2Adapter中不合理的客户端流终止逻辑(此前在请求体结束后会提前关闭出站流,导致部分场景异常)。 - 2.5.3改进
CancelToken场景下的内存分配(_connectFutures中失败后及时移除缓存项,避免悬挂 Future 长期占用)。 - 2.8.0修复上传时连接提前关闭导致的
StateError:向出站消息队列添加DataStreamMessage时用 try/catch 捕获StateError并优雅终止请求流(src/http2_adapter.dart)。2.8.0 同时把 HTTP 集成测试与证书固定测试切换到配置的测试服务器上运行。
此外,2.7.0 起在ResponseBody.extra中提供httpVersion(值如'2.0'),调用方可通过response.extra[HttpClientAdapter.extraKeyHttpVersion]感知实际协商到的协议版本(src/http2_adapter.dart)。
十、与 Dio 主版本协同及升级检查清单
该插件版本与 Dio 主版本强绑定,升级前请对照:
| 插件版本 | 对应 Dio | 关键前提 |
|---|---|---|
| 2.0.0-beta2 / 2.0.0 | dio 4.x | 空安全 |
| 2.1.0 及以上 | dio 5.x | DioException取代DioError |
| 2.5.0 及以上 | dio 5.x | Dart SDK ≥ 3.0.0 |
升级到 2.6.0+ 时,若自定义过ConnectionManager子类,必须为getConnection增加redirects参数(见 doc/migration_guide.md);升级到 2.8.0 后,跨源重定向将默认剥离Authorization、Cookie等敏感头,若你的业务确实依赖跨源携带这些头,需要在重定向前自行处理(例如调整重定向选项或预解析 location)。其余版本均以缺陷修复与内部实现优化为主,可在保持 dio 5.x 的前提下平滑升级。
结语
纵观 CHANGELOG 与源码,dio_http2_adapter的演进脉络清晰可循:早期版本补齐 HTTP/2 基础能力(连接复用、重定向、空安全),中期围绕 dio 5.x 完善异常体系与超时/代理/证书能力,近期则聚焦可靠性与安全性(缓存键修复防泄漏、握手超时兜底、流式上传异常修复、跨源敏感头剥离)。理解这些版本背后的实现细节,不仅能帮助你在接入时选对配置(idleTimeout、handshakeTimeout、onClientCreate、onNotSupported等),也能在遇到“连接泄漏”“上传抛 StateError”“重定向丢失 Cookie”等问题时,快速定位到对应的修复版本与底层调用链。
【免费下载链接】dioA powerful HTTP client for Dart and Flutter, which supports global settings, Interceptors, FormData, aborting and canceling a request, files uploading and downloading, requests timeout, custom adapters, etc.项目地址: https://gitcode.com/gh_mirrors/di/dio
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考