本站的 reqwest
主线位置:请求主线的起点和连接主线的 TLS 一步。请求主线从
Client::builder()和RequestBuilder开始,本站的代码只活在这一段:建客户端、设超时、加头、序列化 body、读响应。连接主线上本站只做了一个决定,而且不是在代码里做的:Cargo.toml里打开的 feature 决定了TlsBackend::default()选谁。
flowchart TB oauth["OAuth 回调"] --> c15["5 秒连接 15 秒总"] pay["支付 send"] --> c15 llm["LLM 两个协议"] --> c30["30 秒总"] ing["papers-ingest"] --> c60["60 秒总 带 UA"] ing --> c600["LLM 600 秒总"] c15 --> rq["reqwest 0.12.28"] c30 --> rq c60 --> rq c600 --> rq rq --> nt["native-tls"] nt --> h1["HTTP/1.1"]
本页说的「本站」是框架仓库 main 分支的提交 363dd8f(2026-10-29),论文采集在还没合并的 feat/papers 分支上,按提交 e727bf0(2026-10-30)。两处的锁文件里 reqwest 都只有一个版本 0.12.28。
实验都在 exp-reqwest/ 下,平台是 M2 Max、macOS 27、rustc 1.99.0:
site-mirror:用git archive导出feat/papers的Cargo.toml、Cargo.lock、crates/和assets/i18n,只在这份拷贝上跑cargo tree --offline --locked和cargo check;site-mirror013是它的副本,用来试升级。site-up012/site-up013:同一份main.rs,按本站的写法各建一遍客户端(.form()、basic_auth、bearer_auth、.json()、timeout、user_agent),分别依赖reqwest = "=0.12.28"(json加rustls-tls,默认 feature 不关,和本站一样)与reqwest = "=0.13.5"。服务端是共享的testsrv,单独起两个进程(明文127.0.0.1:18081,HTTPSlocalhost:18443),这样它自己依赖的 reqwest 0.13.5 和那一堆 feature 不会并进镜像 crate 的依赖图。- 每个客户端都
.no_proxy():这台 Mac 开着系统代理127.0.0.1:7890,原因见下文。
六处 Client
| 调用方 | 代码 | Client 活多久 | 超时 | 请求 | 读响应 |
|---|---|---|---|---|---|
| OAuth 换 token | handle_callback | 每次回调一个 | 连接 5 秒,总 15 秒 | .form(),Basic 方式另加 basic_auth | .json(),不看状态码 |
| OAuth 取用户信息 | fetch_profile | 和上一行共用 | 同上 | 手写 Authorization: Bearer,User-Agent: app | error_for_status() 再 .json() |
| OpenAI 兼容协议 | OpenAiChat::new | 构造一次,进程内共用 | 总 30 秒,无连接超时 | bearer_auth,.json() | 先 .text(),自己看状态码,再 serde_json |
| Anthropic 协议 | AnthropicChat::new | 同上 | 同上 | x-api-key 头,.json() | 同上 |
| 支付网关 | host.rs 的 send | 每个请求一个 | 连接 5 秒,总 15 秒 | 网关给的头逐个 .header(),.form() 或原始 body | .text(),状态码交给各网关的解析函数 |
| 论文采集:HF 列表、arXiv 全文 | papers_ingest.rs | 一次运行一个,两个来源共用 | 总 60 秒,无连接超时 | user_agent(...) 设在 Client 上 | 自己看状态码,arXiv 的 404 当「没有 HTML 版」 |
| 论文采集:生成解读 | Generator::from_env | 一次运行一个,经 with_client 交给 OpenAiChat | 总 600 秒(LLM_TIMEOUT) | 同 OpenAI 那一行 | 同 OpenAI 那一行 |
前五行里的超时数字、每次新建 Client 的代价,hyper 卷的本站的出站客户端已经逐条实测过,这里只补论文采集。它是一个单独的二进制,由 Dockerfile 里的 cargo build --release -p module-papers --features ingest --bin papers-ingest 构建,服务器上用 cron 跑,不链接进 app。它建了两个 Client:抓 HF 列表和 arXiv 全文的那个设了 60 秒总超时和 User-Agent(注释是「arXiv 与 HF 都建议带上能联系到人的 User-Agent」);调模型的那个单独设 600 秒,注释是「长论文抽取实测接近 30 秒,留足余量」,再经 OpenAiChat::with_client 换掉 OpenAiChat::new 自己建的 30 秒客户端。两个都没有 connect_timeout,连接阶段的时间算在总超时里,见超时。arXiv 要求每 3 秒最多一个请求,采集循环在两次 arXiv 请求之间睡 3 秒,远小于 90 秒的池空闲超时,又因为每个响应都用 .text() 读完,只要 arXiv 不主动关连接,同一次运行里的请求就复用同一条连接。
OpenAiChat::new 和 AnthropicChat::new 的构造写法相同:
let client = Client::builder()
.timeout(Duration::from_secs(DEFAULT_TIMEOUT_SECS))
.build()
.unwrap_or_else(|_| Client::new());
Client::new 就是 ClientBuilder::new().build().expect("Client::new()")(0.12.28 相同)。build 失败的原因(TLS 后端初始化、读系统配置)和有没有 .timeout 无关,所以这个回退分支要么以同样的原因 panic,要么得到一个没有超时的客户端。本页没有构造出让 build 失败的环境,这一点只来自读代码。
线上的请求头
镜像 crate 把 OAuth、OpenAI、支付三处的请求原样发给 testsrv 的 /echo,它把收到的请求行和每个头打印回来。reqwest 0.12.28 这边(site-up012):
--- oauth token (Basic) (HTTP/1.1)
POST /echo HTTP/1.1
accept: application/json
content-type: application/x-www-form-urlencoded
authorization: Basic Y2xpZW50LWlkOmNsaWVudC1zZWNyZXQ=
host: 127.0.0.1:18081
content-length: 43
body: 43 bytes
--- llm openai (HTTP/1.1)
POST /echo HTTP/1.1
authorization: Bearer sk-test
content-type: application/json
accept: */*
host: 127.0.0.1:18081
content-length: 57
body: 57 bytes
--- payment form (HTTP/1.1)
POST /echo HTTP/1.1
content-type: application/x-www-form-urlencoded
accept: */*
host: 127.0.0.1:18081
content-length: 15
body: 15 bytes
- 没有
User-Agent。 reqwest 不加默认的 UA(执行与 Pending 里execute_request只补accept: */*),所以换 token、调模型、调支付网关的请求都不带 UA。只有fetch_profile手写了User-Agent: app,论文采集在Client上设了user_agent。 accept: */*只在调用方没设时补上。 OAuth 那一组自己写了Accept: application/json,就只有这一个。Content-Type只有一个。 OAuth 的Basic分支先.header("Content-Type", "application/x-www-form-urlencoded")再.form(...)。form用的是entry(CONTENT_TYPE).or_insert(...),已有就不动;json也一样,所以 OpenAI 那一处手写的Content-Type: application/json和.json()也不会重复。- 没有
accept-encoding:本站没开gzip等解压 feature,解压层什么也不加,见响应体。
读响应的写法有三种。fetch_profile 先 error_for_status(),401 直接变成错误(kind: Status(401, None))。换 token 那一处不看状态码就 .json():provider 的错误响应也是 JSON,代码从里面取 error 字段报出来;但如果回来的不是 JSON(比如网关的 502 页面),得到的是解码错误,0.12.28 的这个错误里没有 URL:
--- profile 401: Err(reqwest::Error { kind: Status(401, None), url: "http://127.0.0.1:18081/user?status=401" })
--- profile 200 text body: Err(reqwest::Error { kind: Decode, source: Error("expected value", line: 1, column: 1) })
其余几处都先 .text():LLM 客户端在状态码不对或 JSON 解不开时,把截断后的响应体(truncate(&text, 500)、truncate(&text, 200))放进错误,这是直接 .json() 做不到的;支付把 body 整个交给各网关的解析函数;论文采集只报状态码。reqwest 错误里有什么、没什么,见错误。
重定向时的 API key
六处都没改重定向策略,用的是默认的最多 10 次。跨源(主机字符串或端口不同,0.13.4 起还有 scheme)时 reqwest 去掉的只有固定的五个头(Authorization、Cookie、Cookie2、Proxy-Authorization、WWW-Authenticate),见重定向。OpenAI 客户端的 bearer_auth 落在 Authorization 里,Anthropic 客户端的 key 放在自定义的 x-api-key 头里,不在这个列表上。镜像 crate 把两种头放进同一个 POST,让 testsrv 回一个 307 指向 localhost(原请求是 127.0.0.1,主机字符串不同,算跨源):
--- llm headers after cross-origin 307 to http://localhost:18081/echo: ["x-api-key: sk-ant", "anthropic-version: 2023-06-01"]
0.12.28 和 0.13.5 的输出相同:authorization 被去掉,x-api-key 跟着到了新的源。正常情况下 LLM 服务商的接口不会把 POST 重定向到别的源,base_url 也是管理员配置的;要让 key 只发给配置的那个源,可以在这两个 Client 上设 redirect(reqwest::redirect::Policy::none())。
测试里的 .no_proxy()
本站的测试一律 reqwest::Client::builder().no_proxy().build():OAuth 的 fetch_profile_rejects_error_status,crates/llm 两个客户端的 test_client,经 with_client 注入。默认的 build 会追加系统代理的匹配器;macOS 上 hyper-util 只读系统代理的开关、主机和端口,不读例外列表,发往 127.0.0.1 的 mockito 请求于是可能被交给本机的代理;代理不转发它时,错误是 connection closed before message completed。机制见代理的「系统代理」一节,用黑洞代理复现的实验见 hyper 卷的本站的出站客户端。这两处都是 reqwest 0.13.5 和 0.12.28 共用的 hyper-util 代码,升级不改变这个约定。
生产代码不调 .no_proxy()。容器是 Linux,系统代理只来自环境变量,compose 没给 app 设代理变量,请求直连。
编进来的 feature
工作区写的是 reqwest = { version = "0.12", features = ["json"] },没关默认 feature。但 cargo 的 feature 是整个依赖图的并集,最后编进 reqwest 的远不止这些。在 site-mirror 上按生产平台查发布镜像那次服务端构建:
cargo tree --offline --locked -p app --no-default-features --features server,accounts \
-e features -i reqwest --target x86_64-unknown-linux-gnu --depth 1
reqwest v0.12.28
`-- oauth2 v5.0.0
|-- reqwest feature "__rustls"
|-- reqwest feature "__rustls-ring"
|-- reqwest feature "__tls"
|-- reqwest feature "charset"
|-- reqwest feature "cookies"
|-- reqwest feature "default"
|-- reqwest feature "default-tls"
|-- reqwest feature "h2"
|-- reqwest feature "http2"
|-- reqwest feature "json"
|-- reqwest feature "multipart"
|-- reqwest feature "rustls-tls"
|-- reqwest feature "rustls-tls-webpki-roots"
|-- reqwest feature "rustls-tls-webpki-roots-no-provider"
|-- reqwest feature "stream"
`-- reqwest feature "system-proxy"
把 -p app ... 换成 -p module-papers --no-default-features --features ingest(papers-ingest 的构建),16 项一模一样。来源分三处:
default(default-tls、charset、http2、system-proxy)和json:工作区那一行。default-tls在 0.12.28 里就是 native-tls(Cargo.toml:default-tls = ["dep:hyper-tls", "dep:native-tls-crate", "__tls", "dep:tokio-native-tls"]),Linux 上是动态链接的系统 OpenSSL。rustls-tls及其展开:crates/llm显式写的features = ["json", "rustls-tls"],加上 dioxus-fullstack 0.7.9 和 oauth2 5.0.0 自己的声明。cookies、multipart、stream:dioxus-fullstack 带进来的,本站代码没用到。cookies编进来不等于开了 cookie 存储,cookie_store(true)没人调,见 cookie。
图里第一行的 oauth2 v5.0.0 是 -i 反查时排在最前的一个依赖方。工作区声明了 oauth2 = { version = "5.0.0", features = ["reqwest"] },crates/core 的 server feature 也打开它,但 git grep 'oauth2::' 在 crates/ 下没有结果:OAuth 流程是 auth/mod.rs 手写的,oauth2 crate 在依赖图里唯一的作用是打开 reqwest 的 rustls-tls。
两种 TLS 都在,走 native-tls
两个后端都编进来时用哪个,由 0.12.28 的 TlsBackend 的 Default 决定:default-tls 在(且没开 http3)就是 native-tls。本站六处都没调 use_rustls_tls(),所以全部走 native-tls。这件事和两条路的 ClientHello,rustls 卷的本站的 rustls已经实测过;0.13.5 里同样的规则(编进 native-tls 就选它)见 TLS 配置。
native-tls 这条路还决定了 HTTP 版本。0.12.28 的 TlsBackend::Default 分支只在 native-tls-alpn feature 打开时才调 request_alpns,本站没开,ClientHello 里没有 ALPN,服务端无从选 h2。镜像 crate 用本站同样的 builder(只多 .no_proxy() 和测试 CA)连两次 testsrv 的 HTTPS 端口,服务端同时接受 h2 和 http/1.1:
--- https default builder: HTTP/1.1 hello / (conn #1 req #1 total #1)
--- https default builder: HTTP/1.1 hello / (conn #1 req #2 total #2)
[https server log]
[testsrv] conn #1 from 127.0.0.1:64422
[testsrv] conn #1 tls ok, TLSv1_2 alpn=None
[testsrv] conn #1 req #1 GET / HTTP/1.1
[testsrv] conn #1 req #2 GET / HTTP/1.1
conns=1 reqs=2 tls_handshakes=1
alpn=None,于是 HTTP/1.1;两个请求在同一条连接上,连接池照常工作。TLSv1_2 是 macOS 的 Security.framework 带来的(rustls 卷那一页解释了它为什么只到 1.2);生产的 Linux 上是 OpenSSL,版本可以协商到 1.3,但没有 ALPN 这一点相同,所以本站对 LLM 服务商、支付网关、arXiv 这些支持 HTTP/2 的上游,说的都是 HTTP/1.1。
升级到 0.13.5
在 site-mirror013 上把工作区那一行改成 version = "0.13",其他不动,跑 cargo tree:
error: failed to select a version for `reqwest`.
... required by package `llm v0.1.0 (.../site-mirror013/crates/llm)`
... which satisfies path dependency `llm` (locked to 0.1.0) of package `module-papers v0.1.0 (.../site-mirror013/crates/modules/papers)`
... which satisfies path dependency `module-papers` (locked to 0.1.0) of package `app v0.1.0 (.../site-mirror013/crates/app)`
versions that meet the requirements `^0.13` are: 0.13.5, 0.13.4, 0.13.3
package `llm` 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
...
第一个错误不是编译器报的,是 cargo 解析依赖时报的:0.13 把 rustls-tls 改名为 rustls(#2900),crates/llm 那一行要跟着改。改完(工作区钉成 =0.13.5)再 cargo check -p module-payment -p module-papers --features module-payment/server,module-papers/ingest:
error[E0599]: no method named `form` found for struct `reqwest::RequestBuilder` in the current scope
--> crates/core/src/auth/mod.rs:307:10
|
302 | TokenAuth::Basic => http_client
| ___________________________-
303 | | .post(spec.token_url)
304 | | .header("Accept", "application/json")
305 | | .header("Content-Type", "application/x-www-form-urlencoded")
306 | | .basic_auth(&client_id, Some(&client_secret))
307 | | .form(&form_params),
| |_________-^^^^
|
help: there is a method `or` with a similar name
|
307 - .form(&form_params),
307 + .or(&form_params),
|
error[E0599]: no method named `form` found for struct `reqwest::RequestBuilder` in the current scope
--> crates/core/src/auth/mod.rs:311:79
|
311 | http_client.post(spec.token_url).header("Accept", "application/json").form(&form_params)
| ^^^^
...
error[E0599]: no method named `json` found for type `_` in the current scope
--> crates/core/src/auth/mod.rs:315:55
|
315 | let token_response: Value = request.send().await?.json().await?;
| ^^^^
...
error: could not compile `app-core` (lib) due to 3 previous errors; 2 warnings emitted
三个错误其实是一个:form 在 0.13 里挂在 form feature 上(#[cfg(feature = "form")]),默认不开;request 的类型推不出来,第三个 json 的错误是连带的。编译器的建议 .or(...) 是按拼写相近给的,不能照做。支付的 host.rs 第 40 行也调了 .form,这次没报,是因为 app-core 先失败了,依赖它的 module-payment 还没轮到;镜像 crate 里同样写法的那一行报的是同一个错误:
error[E0599]: no method named `form` found for struct `RequestBuilder` in the current scope
--> ../site-up012/src/main.rs:56:20
|
56 | client.post(url).form(&form)
| ^^^^
query 也一样挂到了 feature 上,本站没调 .query()(HF 的查询串是 format! 拼的),不受影响。
form() 和 query() 从 0.13 起要单独开 feature#2858(9fa9067,「feat: add query and form crate features」),首次发布在 0.13.0-rc.1 和 0.13.0。提交说明:「Make serde optional by introducing query, form features」,以及「Breaking change since query() and form() are no longer available by default, but must be enabled.」
改之前,Cargo.toml 里 serde = "1.0"、serde_urlencoded = "0.7.1" 是无条件依赖,json = ["dep:serde_json"];改之后两者都是 optional = true,新加 query = ["dep:serde", "dep:serde_urlencoded"]、form = ["dep:serde", "dep:serde_urlencoded"],json 也改成带上 dep:serde。不需要序列化 body 的用户从此可以不编 serde。代价落在本站这种用户身上:.form() 一个字没改,升级后找不到了。上面 OAuth 那两处 E0599 就是它。
把工作区那一行改成 features = ["json", "form"],同一条 cargo check 通过;再加 -p llm -p app-core --tests,测试代码也通过。整个升级在本站代码里就是两行 Cargo.toml:
-reqwest = { version = "0.12", features = ["json"] }
+reqwest = { version = "=0.13.5", features = ["json", "form"] }
-reqwest = { workspace = true, features = ["json", "rustls-tls"] }
+reqwest = { workspace = true, features = ["json", "rustls"] }
本站没用 0.13 删掉的旧方法,use_rustls_tls() 一类改名(0.13 推荐 tls_backend_rustls())只是文档上的软弃用,本站也没调。
运行时变了什么
编过不等于行为不变。同一份 main.rs 在 0.13.5 下(site-up013,feature 是 json、rustls、form)跑同样的请求。明文的三组请求头和 0.12.28 完全一样,变化在 HTTPS 和错误上:
--- profile 200 text body: Err(reqwest::Error { kind: Decode, url: "http://127.0.0.1:18081/user", source: Error("expected value", line: 1, column: 1) })
--- papers 404: Ok(true)
--- https default builder: HTTP/2.0 hello https://localhost:18443/ (conn #1 req #1 total #1)
--- https default builder: HTTP/2.0 hello https://localhost:18443/ (conn #1 req #2 total #2)
[https server log]
[testsrv] conn #1 from 127.0.0.1:64429
[testsrv] conn #1 tls ok, TLSv1_3 alpn=Some("h2")
[testsrv] conn #1 req #1 GET https://localhost:18443/ HTTP/2.0
[testsrv] conn #1 req #2 GET https://localhost:18443/ HTTP/2.0
conns=1 reqs=2 tls_handshakes=1
(HTTP/2 下服务端看到的是完整 URI,HTTP/1.1 下只有路径,这是协议的差别。)整理成表:
| 项 | 本站现在(0.12.28) | 升级后(0.13.5) | 依据 |
|---|---|---|---|
| TLS 后端 | native-tls(Linux:系统 OpenSSL) | rustls,provider 是 aws-lc-rs(进程里没装默认 provider 时) | 0.13.5 default-tls = ["rustls"];default_rustls_crypto_provider |
| 根证书 | OpenSSL 的系统证书目录 | rustls-platform-verifier:Linux 上经 rustls-native-certs 读系统证书,镜像里的 ca-certificates 照样用得上 | TLS 配置 |
| ALPN 与 HTTP 版本 | 无 ALPN,只有 HTTP/1.1 | 报 h2、http/1.1,上游支持就是 HTTP/2 | 上面两段 testsrv 日志 |
| 解码错误 | 没有 URL | 带 URL | #3026(c86f860),首次发布在 0.13.4,CHANGELOG 没写这一条 |
| 重定向时的敏感头 | 换主机或端口时去掉 | 换 scheme 时也去掉 | #3034(03db63a),首次发布在 0.13.4,CHANGELOG「Fix redirect handling to strip sensitive headers when the scheme changes」 |
| 超时、UA、系统代理、重试 | 默认都没有超时,没有 UA,读系统代理,默认重试策略 | 相同 | 两个版本的 ClientBuilder::new |
重定向那一行对本站影响很小:OAuth 的 Authorization 只发给 provider 的固定地址,LLM 的 bearer_auth 发给配置的 base_url,正常不会跨 scheme 重定向。
HTTP/2 这一项会实际改变线上的连接形状:LLM 客户端在进程内共用,同时有几个请求在调模型时,0.12.28 是每个在途请求占一条 HTTP/1.1 连接,0.13.5 下同一个上游只要一条 HTTP/2 连接(第一次并发时 ALPN 结果出来之前的那一轮除外,见连接池与 HTTP 版本)。
依赖图变了什么
在 site-mirror013 上对 papers-ingest 的构建再查一遍:
### reqwest@0.12.28
reqwest v0.12.28
|-- dioxus-fullstack v0.7.9
`-- oauth2 v5.0.0
### reqwest@0.13.5
reqwest v0.13.5
|-- app-core v0.1.0 (.../site-mirror013/crates/core)
|-- llm v0.1.0 (.../site-mirror013/crates/llm)
`-- module-papers v0.1.0 (.../site-mirror013/crates/modules/papers)
oauth2 5.0.0 依赖 reqwest = "0.12",dioxus-fullstack 0.7.9 依赖 reqwest = "0.12.28",它们不会跟着升,于是依赖图里有两个 reqwest。两个是不同的 crate,类型互不相通;本站代码从不把 reqwest 的类型交给这两个库,所以能编过。更换版本后几个包的进出,在 --target x86_64-unknown-linux-gnu 下用 cargo tree -i 查:
native-tls、openssl-sys报「did not match any packages」。native-tls 原来只由 0.12 的default-tls带进来;现在 0.12 那份只剩 dioxus-fullstack 和 oauth2 要的rustls-tls(ring 加 webpki-roots),站点代码用的 0.13 那份是 rustls 加 aws-lc-rs。app 服务端的构建结果相同。- papers-ingest 构建里 rustls 的 feature 从
ring、std、tls12变成aws-lc-rs、aws_lc_rs、ring、std、tls12:sqlx 和 lettre 还要 ring,reqwest 0.13 带来 aws-lc-rs。两个 provider 同时编进来时,任何走进程默认 provider 的入口(比如rustls::ClientConfig::builder())都会 panic,reqwest、sqlx、lettre 自己不受影响;rustls 卷本站的 rustls的「如果哪天某个依赖打开了 rustls 的默认 feature」一节就是这个局面的实测。 - 锁文件新增
aws-lc-rs 1.19.0、aws-lc-sys 0.47.0、cmake 0.1.58、rustls-platform-verifier 0.7.1、rustls-native-certs 0.8.5等 9 个包。aws-lc-sys 要在构建时编译 C 代码;本机的 macOS 构建通过,生产的 Linux 镜像构建本页没有跑。
就是上面那棵树:0.12.28 那份由 dioxus-fullstack 和 oauth2 带进来,0.13.5 那份由站点代码用。对它们分别跑 cargo tree -e features -i reqwest@<版本> --depth 1,各自编进了哪些 TLS feature?按各自版本的 TlsBackend::default(),Client::builder().build() 各会选谁?答案在文末。
动脑筋答案
site-mirror013,--target x86_64-unknown-linux-gnu,只摘 reqwest feature 各行,按字母排序后连成一行:
### reqwest@0.12.28
reqwest feature "__rustls-ring" reqwest feature "__rustls" reqwest feature "__tls" reqwest feature "cookies" reqwest feature "json" reqwest feature "multipart" reqwest feature "rustls-tls-webpki-roots-no-provider" reqwest feature "rustls-tls-webpki-roots" reqwest feature "rustls-tls" reqwest feature "stream"
### reqwest@0.13.5
reqwest feature "__rustls-aws-lc-rs" reqwest feature "__rustls" reqwest feature "__tls" reqwest feature "charset" reqwest feature "default-tls" reqwest feature "default" reqwest feature "http2" reqwest feature "json" reqwest feature "rustls" reqwest feature "system-proxy"
- 0.12.28 那份:没有
default-tls,只有rustls-tls一族。0.12.28 的TlsBackend::default()在default-tls不在时选Rustls,provider 是 ring,根证书是 webpki-roots。连default都没有了(dioxus-fullstack 和 oauth2 都关了 reqwest 的默认 feature),所以它也没有http2、charset、system-proxy:这份 reqwest 只会说 HTTP/1.1,也不读系统代理。 - 0.13.5 那份:
default-tls现在等于rustls,__native-tls不在,选Rustls,provider 是 aws-lc-rs,根证书来自平台校验器。 - 两份都不是 native-tls。升级前整个二进制只走 native-tls,升级后一次 native-tls 都不走,而且同一个进程里同时有 ring 和 aws-lc-rs 两套密码学实现。站点代码只用 0.13.5 那份,0.12.28 那份只在 dioxus-fullstack 和 oauth2 内部用。
小结
- 本站六处建
Client:OAuth 和支付每次调用新建,设连接 5 秒、总 15 秒;LLM 共用一个,总 30 秒;论文采集一次运行两个,60 秒带 UA 和 600 秒给模型。除 OAuth 和支付外都没有connect_timeout。 - reqwest 不加 User-Agent,本站只有取用户信息和论文采集自己设了。
form和json只在没有Content-Type时补上,手写的那个不会重复。 - 编进来的 16 个 feature 是工作区、
crates/llm、dioxus-fullstack、oauth2 的并集;default-tls在,所以全部走 native-tls,没开native-tls-alpn,对任何上游都只说 HTTP/1.1。oauth2 crate 在代码里没有被用到。 - 升级到 0.13.5 只要改两行
Cargo.toml:rustls-tls改名rustls,工作区加form;改之前 cargo 先报 feature 不存在,然后 OAuth 的.form()报 E0599。 - 升级后 HTTPS 变成 rustls 加 aws-lc-rs,本机实测从 TLS 1.2 加 HTTP/1.1 变成 TLS 1.3 加 HTTP/2;依赖图里有两个 reqwest,native-tls 退出,rustls 同时带 ring 和 aws-lc-rs,不能再用进程默认 provider。