源码地图
主线位置:三条主线的地图。请求主线走
async_impl/request.rs、async_impl/client.rs,经过cookie.rs、retry.rs、redirect.rs定义的层;连接主线走connect.rs、dns/、tls.rs、proxy.rs;响应主线走async_impl/response.rs、async_impl/body.rs。本页先看lib.rs怎么把三套实现接到同一组类型名上,再列出src下每个文件管什么,最后用cargo tree看 reqwest 依赖谁、每个下层 crate 在哪一层干活。
flowchart TB app[应用] --> api["Client Request Response"] api --> async["async_impl"] api --> blk["blocking"] api --> wasm["wasm fetch"] blk --> async async --> layers["tower 中间件"] layers --> hu["hyper-util Client"] hu --> conn["connect.rs"] conn --> dns["dns"] conn --> tls["rustls 或 native-tls"] hu --> hyper["hyper 与 h2"]
三套实现,一组名字
reqwest::Client 在不同目标上是不同的类型。lib.rs 定义了两个宏,按目标平台给一组条目加上 cfg:
macro_rules! if_wasm {
($($item:item)*) => {$(
#[cfg(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none")))]
$item
)*}
}
macro_rules! if_hyper {
($($item:item)*) => {$(
#[cfg(not(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none"))))]
$item
)*}
}
(if_wasm、if_hyper)然后各自导出同名的类型:
if_hyper! {
// ...
pub use self::async_impl::{
Body, Client, ClientBuilder, Request, RequestBuilder, Response, Upgraded,
};
pub use self::proxy::{Proxy,NoProxy};
// ...
mod async_impl;
#[cfg(feature = "blocking")]
pub mod blocking;
mod connect;
// ...
}
if_wasm! {
mod wasm;
mod util;
pub use self::wasm::{Body, Client, ClientBuilder, Request, RequestBuilder, Response};
// ...
}
(第 353 行、第 390 行)所以「三套实现共用类型」的准确说法是:共用名字和方法签名,不共用实现。
- 非 WASM 目标上,
reqwest::Client就是async_impl::Client。blocking不是第三套独立实现:blocking::ClientBuilder里装的是一个async_impl::ClientBuilder,blocking::Client里是一个ClientHandle,它用thread::Builder起一个后台线程跑异步客户端,请求经mpsc通道送过去,见 阻塞客户端。 - WASM 目标上,
wasm::Client只是一个Arc<Config>,没有连接池、连接器、tower 层,connect、proxy、tls、dns、redirect、retry、blocking、cookie这些模块根本不编译,见 WASM。 - 两边都编译的只有
error、into_url、response(ResponseBuilderExt)和util,外加httpcrate 转导出的Method、StatusCode、Version、header和url::Url。lib.rs里有一句注释「TODO: removeif_hyperif wasm has been migrated to new config system」,说的是config.rs(按请求覆盖超时等设置的RequestConfig)目前只有非 WASM 用。
lib.rs 还有一个只为编译期检查存在的函数 _assert_impls:断言 Client、Error、Body 是 Send + Sync(Client 还要 Clone),Request、RequestBuilder 是 Send,非 wasm32 上 Response 也是 Send。
src 下的文件
按 tag 统计 src 下 .rs 的行数(含文档注释和文件内的单元测试),合计 20323 行:
| 文件或目录 | 行数 | 管什么 | 讲它的页 |
|---|---|---|---|
async_impl/client.rs | 3196 | Client、ClientBuilder 和它的 Config(六十多个字段)、build 组装连接器和 tower 栈、execute_request、Pending | 一个 Client、执行与 Pending |
async_impl/request.rs | 1151 | Request、RequestBuilder:头、查询、表单、JSON、认证、try_clone | 请求构建 |
async_impl/multipart.rs | 754 | multipart::Form、Part:边界、长度 | 请求体与 multipart |
async_impl/response.rs | 508 | Response:bytes、text(字符集解码)、json、chunk、error_for_status | 响应体 |
async_impl/body.rs | 486 | 请求和响应共用的 Body;总超时、读超时两个响应体包装 | 请求体与 multipart、超时 |
async_impl/h3_client/ | 840 | HTTP/3(实验):自己的连接器和连接池 | 连接池与 HTTP 版本 |
async_impl/upgrade.rs、mod.rs | 75 + 14 | Response::upgrade 和 Upgraded | — |
connect.rs | 2139 | Connector、ConnectorBuilder、ConnectorService:本地套接字、代理隧道、直连三条路;TLS 分支(Inner);Conn 和 TlsInfo;SOCKS;connection_verbose 的读写日志 | 连接器、代理 |
proxy.rs | 948 | Proxy、NoProxy,内部的 Matcher(包 hyper-util 的 Matcher 或自定义闭包) | 代理 |
tls.rs | 922 | Certificate、Identity、CertificateRevocationList、Version、TlsBackend、TlsInfo;NoVerifier、IgnoreHostname 两个危险校验器 | TLS 配置 |
dns/ | 312 | Resolve trait、DynResolver、GaiResolver、HickoryDnsResolver、按域名覆盖的 DnsResolverWithOverrides | DNS |
error.rs | 543 | Error 和 Kind、is_timeout 等判断方法 | 错误 |
retry.rs | 491 | retry::Builder、tower retry::Policy 的实现、默认只重试 HTTP/2 的 GOAWAY 和 REFUSED_STREAM | 重试 |
redirect.rs | 454 | redirect::Policy(默认最多 10 次)、给 tower-http 用的 TowerRedirectPolicy、跨域去敏感头 | 重定向 |
cookie.rs | 292 | CookieStore trait、Jar、CookieService 中间件 | cookie |
blocking/ | 4217 | 阻塞版的 Client、Request、Response、Body、multipart,wait.rs 在当前线程上等一个 future | 阻塞客户端 |
wasm/ | 2186 | 浏览器 fetch 的包装,同名的 Client、Request、Response、Body、multipart | WASM |
| 其余 | 795 | lib.rs(397 行,大半是文档)、util.rs、into_url.rs、config.rs、response.rs | 本页 |
几点读法:
async_impl/一共 7024 行,其中client.rs就占 3196 行。它长不是因为逻辑多:ClientBuilder有约九十个公开方法,每个都带文档注释,多数还带例子;build一个函数从第 409 行到第 1110 行,大半是按 feature 展开的 TLS 配置和 HTTP/3 分支。- 没有
async_impl/decoder.rs。gzip 等解压在 0.12.25 改由 tower-http 的Decompression层做(#2840,删掉了 812 行),reqwest 只负责按 feature 打开对应的算法。 blocking/比async_impl/少不了多少行,因为它把每个公开方法都重写了一遍(多是转发加文档),真正的逻辑在ClientHandle和wait.rs两处。
依赖层次:cargo tree 核实
只写 reqwest = "=0.13.5"(默认 feature)的程序,cargo tree -e normal -p reqwest --depth 1:
reqwest v0.13.5
├── base64 v0.23.1
├── bytes v1.12.1
├── encoding_rs v0.8.42
├── futures-core v0.3.34
├── h2 v0.4.20
├── http v1.5.0
├── http-body v1.1.0
├── http-body-util v0.1.5
├── hyper v1.12.0
├── hyper-rustls v0.27.10
├── hyper-util v0.1.21
├── log v0.4.34
├── mime v0.3.17
├── percent-encoding v2.3.2
├── pin-project-lite v0.2.17
├── rustls v0.23.46
├── rustls-pki-types v1.15.1
├── rustls-platform-verifier v0.7.1
├── sync_wrapper v1.0.2
├── tokio v1.54.1
├── tokio-rustls v0.26.6
├── tower v0.5.3
├── tower-http v0.6.11
├── tower-service v0.3.3
└── url v2.5.8
每个下层 crate 在哪一层:
| crate | 在哪一层 | reqwest 怎么用它 |
|---|---|---|
| hyper 1.12.0 | HTTP/1 和 HTTP/2 的连接协议 | 不直接建连接;hyper::Request / Response、body::Incoming 是栈里流动的类型 |
| hyper-util 0.1.21 | legacy Client、连接池、HttpConnector、代理规则 Matcher | build 里 hyper_util::client::legacy::Client::builder 建出栈底的客户端;HttpConnector 负责 DNS 和 TCP;系统代理从它的 Matcher::from_system 读 |
| tower 0.5.3 | Service / Layer | Retry 层、connector_layer 的装箱和 connect_timeout 的 TimeoutLayer |
| tower-http 0.6.11 | 通用的 HTTP 中间件 | FollowRedirect、Decompression 两层 |
| h2 0.4.20 | HTTP/2 帧和流 | 由 hyper 使用;reqwest 自己只在 retry.rs 里把错误 downcast 成 h2::Error,判断是不是可重试的 GOAWAY / REFUSED_STREAM |
| rustls 0.23.46、hyper-rustls 0.27.10、tokio-rustls 0.26.6 | TLS | build 拼 rustls::ClientConfig;hyper-rustls 的 HttpsConnector 包住 HttpConnector 做握手 |
| rustls-platform-verifier 0.7.1 | 证书校验 | 默认的 ServerCertVerifier,用系统信任库 |
| url 2.5.8 | URL 解析 | IntoUrl、Response::url;它带来 idna 和一串 icu_* crate |
| encoding_rs、mime | charset feature | Response::text 的解码 |
| log | 日志 | reqwest 自己用 log 打日志,hyper-util 用 tracing;本卷用 tracing-subscriber 把两者一起收上来 |
整棵树(去重,含 reqwest 自己和过程宏)是 98 个 crate,比直接用 hyper-util 加 hyper-rustls、rustls-platform-verifier 的同功能程序多出 43 个,多的是 url 带来的 IDNA 和 Unicode 数据、tower-http、system-configuration(macOS 系统代理)、encoding_rs 和 mime。逐项对比见 reqwest 解决什么问题。
仓库里的其他目录
tests/:按功能分文件(client.rs、redirect.rs、proxy.rs、timeouts.rs、retry.rs、cookie.rs、gzip.rs等),tests/support/里是用 hyper 写的测试服务器;badssl.rs连外网,本卷不跑。examples/:simple.rs、blocking.rs、json_typed.rs、form.rs、h3_simple.rs、tor_socks.rs、connect_via_lower_priority_tokio_runtime.rs和一个wasm_github_fetch目录。CHANGELOG.md:每个版本的改动,本卷当资料用,0.13.0 的「Breaking changes」一节和代码核对过。
小结
if_hyper!/if_wasm!按目标平台导出同名的Client、Request、Response、Body:共用名字,不共用实现;blocking建在async_impl上,WASM 是另一套。src合计 20323 行:async_impl/7024、blocking/4217、wasm/2186、connect.rs2139,client.rs一个文件 3196 行。- 没有自己的解压器、重定向循环和连接池:分别是 tower-http 的
Decompression、FollowRedirect和 hyper-util 的池。 - 直接依赖 25 个,整棵树 98 个 crate;hyper-util 在栈底,tower / tower-http 在中间,rustls 经 hyper-rustls 接进连接器。