Rust in Everything
基于 reqwest 0.13.5

本站的 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,HTTPS localhost:18443),这样它自己依赖的 reqwest 0.13.5 和那一堆 feature 不会并进镜像 crate 的依赖图。
  • 每个客户端都 .no_proxy():这台 Mac 开着系统代理 127.0.0.1:7890,原因见下文。

六处 Client

调用方代码Client 活多久超时请求读响应
OAuth 换 tokenhandle_callback每次回调一个连接 5 秒,总 15 秒.form(),Basic 方式另加 basic_auth.json(),不看状态码
OAuth 取用户信息fetch_profile和上一行共用同上手写 Authorization: Bearer,User-Agent: apperror_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! 拼的),不受影响。

📝NOTE
冷知识: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 镜像构建本页没有跑。
❗IMPORTANT
动脑筋:升级以后,papers-ingest 里的两个 reqwest 各会用哪个 TLS 后端?

就是上面那棵树: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。

💬 关联讨论

发起讨论
加载中...