PAYATHON 2026

Flutter/Dart 请求 HTTPS 时为何报 WRONG_VERSION_NUMBER?

数据小许

结论

WRONG_VERSION_NUMBER 表示 TLS 握手阶段收到的数据不符合预期的 TLS 记录格式。常见原因是 Dart 实际连接的地址或中间代理返回了明文 HTTP、错误端口上的服务,或其他非预期响应。单凭错误文本通常无法判断这是证书链问题,也无法说明 API 路由有误。

题目中的 URL 是 https://my-backend/api/v1/users/123,错误发生在 HTTP 请求收到响应之前。Postman 和 curl 能成功,说明它们各自的请求路径可用,但无法确认 Dart 使用了相同的代理设置、DNS 结果和目标 IP。

建议按连接路径排查

1. 查看 curl 是否使用了代理

先用 verbose 模式查看 curl 实际连接的主机、端口和代理信息,再绕过环境代理重试:

curl -v 'https://my-backend/api/v1/users/123'
curl --noproxy '*' -v 'https://my-backend/api/v1/users/123'

如果绕过代理前后结果不同,说明两次请求走了不同的路径。检查运行 Dart 测试的进程环境中是否设置了代理变量,并确认 Postman 的代理配置是否一致。findProxy = (_) => 'DIRECT' 可以排除 Dart HttpClient 的代理发现路径,但仍需确认进程所在环境和实际连接地址。

2. 检查 DNS 解析和实际目标 IP

在运行 Dart 的同一台机器上查看 my-backend 解析到的地址,再与 curl 的 verbose 输出和 Postman 的连接目标比较。如果解析结果不同,可能与 DNS、hosts 配置、VPN 或网络分流有关。也可以在 Dart 中记录解析结果:

final addresses = await InternetAddress.lookup('my-backend');
for (final address in addresses) {
  print(address.address);
}

如果解析出多个 IP,逐个确认对应服务是否都在预期端口提供 TLS。

3. 直接检查 TLS 入口

如果系统装有 OpenSSL,可以查看连接到主机 443 端口时收到的握手信息:

openssl s_client -connect my-backend:443 -servername my-backend -showcerts

-servername 用于发送 SNI。如果输出显示对端返回的不是 TLS 握手数据,或连接到的服务、证书与预期不符,就检查反向代理、负载均衡器、TLS 终止配置和端口映射。具体输出还需结合服务端配置判断。

也可以对比 HTTP 与 HTTPS 端口配置,确认 https:// 请求最终到达 TLS 监听端口。如果服务前面有网关或负载均衡器,还要检查不同后端节点的配置是否一致。

4. 再比较客户端的 TLS 协商

确认代理、DNS 和目标 TLS 入口一致后,再比较 curl 与 Dart 的 TLS 版本、SNI 和 ALPN 协商。可以用 OpenSSL 分别尝试服务端支持的 TLS 版本,但不要在缺少服务端配置依据时断定某个版本一定可用。题目没有提供服务端 TLS 配置或协商日志,因此无法据此确定 ALPN 或某个特定 TLS 版本就是原因。

badCertificateCallback 为什么未必有效

badCertificateCallback 用于处理证书校验问题。如果握手过程中收到的内容不是有效的 TLS 记录,连接会在证书校验之前失败,因此放宽证书校验通常无法修复 WRONG_VERSION_NUMBER。也不建议通过忽略证书校验来解决。

根据结果缩小范围

  • 如果 Dart 与 curl 连接的 IP 不同,先检查 DNS、hosts、VPN 或网络分流。
  • 如果两者通过不同代理连接,统一代理路径后再复现。
  • 如果目标 IP 和代理相同,但 OpenSSL 握手失败,检查该入口的 TLS 配置、端口及上游响应。
  • 如果 OpenSSL 握手成功而 Dart 失败,再收集 Dart 运行环境、解析地址和 TLS 握手日志,并与 curl 使用的 TLS 协商条件逐项比较。

目前的信息不足以确定具体故障点。下一步可以收集 curl 的 -v 输出、Dart 实际解析到的 IP,以及对同一主机和端口执行 openssl s_client 的结果。注意移除输出中的凭据或敏感信息。

备注:内容仅供参考。