升级 0.13:does not have that feature
主线位置:不在哪一条主线上,在编译期:
Cargo.toml的 feature 决定哪些方法存在、连接主线最后用哪个 TLS 后端。feature 逐个核对和默认后端的选择规则见 怎么读这一卷;TLS 后端和校验器见 TLS 配置。
flowchart TB
v["版本改成 0.13"] --> f{"rustls-tls 还在吗"}
f -->|在| e1["cargo 解析失败"]
e1 --> fix1["改名 rustls"]
f -->|不在| c{"用了 query form"}
fix1 --> c
c -->|用了| e2["E0599"]
e2 --> fix2["加 feature"]
c -->|没用| run["编过了"]
fix2 --> run
run --> beh["默认 TLS 和根证书变了"]
实验 crate:exp-reqwest/pita-up012(reqwest = "=0.12.28",features = ["json"])和 exp-reqwest/pita-up013(reqwest = "=0.13.5",三份 Cargo.toml 放在 manifests/),两者编译同一个 main.rs:
let client = reqwest::Client::builder()
.no_proxy()
.add_root_certificate(ca)
.timeout(Duration::from_secs(10))
.build()
.unwrap();
let r = client
.post(format!("{base}/echo"))
.query(&[("page", "2")])
.form(&[("q", "rust")])
.send()
.await
.unwrap();
服务端是 testsrv 二进制,单独一个进程(--tls -v,rustls,TLS 1.2 和 1.3,ALPN h2、http/1.1),脚本 pita-up013/run.sh。M2 Max、macOS 27、rustc 1.99.0。
现象
第一道:cargo 解析失败。 0.12 里常见的写法是关掉默认 feature、只开 rustls:default-features = false, features = ["json", "rustls-tls"]。版本号改成 0.13.5,cargo check:
error: failed to select a version for `reqwest`.
... required by package `pita-up013 v0.1.0 (.../pita-up013)`
versions that meet the requirements `=0.13.5` (locked to 0.13.5) are: 0.13.5
package `pita-up013` depends on `reqwest` with feature `rustls-tls` but `reqwest` does not have that feature.
help: available features: __native-tls, __native-tls-alpn, __rustls, __rustls-aws-lc-rs, __tls, blocking, brotli, charset, cookies, default, default-tls, deflate, form, gzip, hickory-dns, http2, http3, json, multipart, native-tls, native-tls-no-alpn, native-tls-vendored, native-tls-vendored-no-alpn, query, rustls, rustls-no-provider, socks, stream, system-proxy, zstd
这个错误发生在依赖解析阶段,一行代码都还没编译。依赖图里任何一个 crate 还写着 rustls-tls,整个工作区都过不去。
第二道:E0599。 改成 features = ["json"](默认 feature 打开),编译器报:
error[E0599]: no method named `query` found for struct `RequestBuilder` in the current scope
--> ../pita-up012/src/main.rs:17:6
|
15 | let r = client
| ___________-
16 | | .post(format!("{base}/echo"))
17 | | .query(&[("page", "2")])
| | -^^^^^ method not found in `RequestBuilder`
| |_____|
|
加上 query 再编,下一个才冒出来:
error[E0599]: no method named `form` found for struct `RequestBuilder` in the current scope
--> ../pita-up012/src/main.rs:18:6
|
15 | let r = client
| ___________-
16 | | .post(format!("{base}/echo"))
17 | | .query(&[("page", "2")])
18 | | .form(&[("q", "rust")])
| |_____-^^^^
|
help: there is a method `or` with a similar name
|
18 - .form(&[("q", "rust")])
18 + .or(&[("q", "rust")])
|
方法链上前一个方法找不到时,类型推不下去,后面的方法不会再报,所以要一个一个修。编译器建议的 .or(...) 只是拼写相近,不能照做。
第三道:编过了,行为变了。 features = ["json", "query", "form"] 编过以后,同一段代码对同一个服务器,两个版本的服务端日志:
== pita-up012 -> HTTP/1.1
POST /echo?page=2 HTTP/1.1
...
[server log]
[testsrv] conn #1 tls ok, TLSv1_2 alpn=None
...
== pita-up013 -> HTTP/2.0
POST https://localhost:18543/echo?page=2 HTTP/2.0
...
[server log]
[testsrv] conn #1 tls ok, TLSv1_3 alpn=Some("h2")
0.12.28 的默认是 native-tls(macOS 上是 Security.framework),只协商到 TLS 1.2,不带 ALPN,于是是 HTTP/1.1;0.13.5 的默认是 rustls,TLS 1.3,ALPN 选中 h2。依赖也换了一批:cargo tree 里 0.12.28 这边是 native-tls、hyper-tls、security-framework,0.13.5 这边是 rustls、hyper-rustls、rustls-platform-verifier、aws-lc-rs,以及要编译 C 代码的 aws-lc-sys(构建机上没有 C 编译器时会失败,见 rustls 卷的 没有 C 编译器)。
原因
都写在 0.13.0 的 CHANGELOG 里,「Breaking changes」一共七条:
- `rustls` is now the default TLS backend, instead of `native-tls`.
- `rustls` crypto provider defaults to aws-lc instead of _ring_. (`rustls-no-provider` exists if you want a different crypto provider)
- `rustls-tls` has been renamed to `rustls`.
- rustls roots features removed, `rustls-platform-verifier` is used by default.
- To use different roots, call `tls_certs_only(your_roots)`.
- `native-tls` now includes ALPN. To disable, use `native-tls-no-alpn`.
- `query` and `form` are now crate features, disabled by default.
- Long-deprecated methods and crate features have been removed (such as `trust-dns`, which was renamed `hickory-dns` a while ago).
对着两个 tag 的 Cargo.toml:
| 0.12.28 | 0.13.5 | 来源 |
|---|---|---|
default-tls = ["dep:hyper-tls", "dep:native-tls-crate", ...] | default-tls = ["rustls"] | #2752 |
rustls-tls = ["rustls-tls-webpki-roots"],provider 是 ring,根证书是编进二进制的 webpki-roots | rustls = ["__rustls-aws-lc-rs", "dep:rustls-platform-verifier", "__rustls"] | #2900、#2891 |
rustls-tls-webpki-roots / -native-roots / -manual-roots 及其 -no-provider | 没有了 | #2891 |
native-tls(无 ALPN)、native-tls-alpn | native-tls(带 ALPN)、native-tls-no-alpn | CHANGELOG |
query()、form() 无条件存在,serde_urlencoded 是必选依赖 | #[cfg(feature = "query")]、#[cfg(feature = "form")] | #2858 |
trust-dns、macos-system-configuration(都已标为 deprecated) | 没有了 | CHANGELOG |
根证书的来源变了,这一条编译器不会告诉你。 0.12 的 rustls-tls 自带一份 Mozilla 根证书(webpki-roots),在哪台机器上都一样。0.13 的 rustls 把校验交给 rustls-platform-verifier:macOS、Windows 上交给系统的证书校验,Linux 上它用 rustls-native-certs 读系统证书目录。读到零张根证书时,Verifier::new 返回「No CA certificates were loaded from the system」,reqwest 在 build() 里把它变成 builder error,而 Client::new 对 build() 的结果是 .expect("Client::new()")。所以一个没装 ca-certificates 的极简 Linux 镜像,0.12 + rustls-tls 能连,0.13 + rustls 在建 Client 时就失败。本机是 macOS,这一条没有实测,依据是 rustls-platform-verifier 0.7.1 的 src/verification/others.rs。
修法
按旧 feature 对照改:
| 0.12 里写的 | 0.13 里改成 |
|---|---|
rustls-tls(或 rustls-tls-webpki-roots) | rustls;想要和以前一样的固定根证书,自己依赖 webpki-roots,传给 tls_certs_only(...)(#2891 的说明原话:「instead of adding webpki-roots with a feature flag, you should use the webpki-roots crate yourself, and pass them into tls_certs_only(certs)」) |
rustls-tls-native-roots | rustls(平台校验器本来就用系统根) |
rustls-tls-manual-roots | rustls,加 tls_certs_only(你的根) |
rustls-tls-*-no-provider | rustls-no-provider,进程启动时自己 install_default() |
| 默认 feature(想继续用 native-tls) | 加 native-tls;只要它被打开,默认后端就是 native-tls |
native-tls-alpn | native-tls |
native-tls(想保持不带 ALPN) | native-tls-no-alpn |
用了 .query() / .form() | 加 query / form |
trust-dns | hickory-dns |
方法名也有一批改了(use_rustls_tls() 推荐写成 tls_backend_rustls()、danger_accept_invalid_certs 推荐写成 tls_danger_accept_invalid_certs 等),旧名都还在,只是文档上的软弃用,不影响编译。
本站从 0.12.28 升级的具体改动(两行 Cargo.toml)和运行时差异,见 本站的 reqwest。
怎么检查
- 先查谁在开 feature:
cargo tree -e features -i reqwest列出每个 reqwest feature 是被哪个 crate 打开的;工作区里任何一个成员、任何一个依赖写着rustls-tls都要改。 - 升级后看 TLS 依赖:
cargo tree -i native-tls、cargo tree -i aws-lc-sys,确认最后编进来的是你以为的那个后端。 - 看协议版本: 对自己的服务发一个请求打印
response.version()。HTTPS 从HTTP/1.1变成HTTP/2.0,说明换成了 rustls 加 ALPN;上游的负载均衡、限流如果按连接数算,要重新评估。 - 在部署镜像里跑一次: 根证书的变化只在目标环境里才看得到,在镜像里建一个
Client并请求一个真实的 HTTPS 地址。
小结
rustls-tls改名rustls,旧名在 cargo 解析阶段就报「does not have that feature」。query()、form()从 0.13 起要单独开 feature,否则 E0599,而且一次只报方法链上的第一个。- 编过以后,默认 TLS 从 native-tls 变成 rustls(aws-lc-rs),同一段代码在 macOS 上从 TLS 1.2 + HTTP/1.1 变成 TLS 1.3 + HTTP/2。
- 0.13 的 rustls 用系统根证书,不再自带 webpki-roots;没有系统根证书的环境要自己用
tls_certs_only提供。