Rust in Everything
基于 reqwest 0.13.5

reqwest 源码解析

reqwest 是 Rust 里最常用的 HTTP 客户端。README 的第一句是「An ergonomic, batteries-included HTTP Client for Rust」:连接池、重定向、代理(包括系统代理)、TLS、gzip 解压、cookie、JSON 都替你接好了。它自己不解析 HTTP,也不管理连接:一个 Client 是一个 Arc,里面是 hyper-util 的 legacy Client,外面套着几层 tower 中间件(重试、cookie、重定向、解压)。连接由 reqwest 的 Connector 建立:解析 DNS,用 hyper-util 的 HttpConnector 开 TCP,需要时先打代理隧道,再用 rustls(或 native-tls)包一层 TLS。blocking::Client 在后台线程上跑一个异步客户端;编译到 wasm32 时,同名的类型换成浏览器 fetch 的包装。这几句话落在哪些代码上、和直接用 hyper-util、ureq 比能测出什么,见 reqwest 解决什么问题。

锁定的版本

项本卷说明
reqwest0.13.5,tag v0.13.5 → de55373(2026-09-08,「v0.13.5」)crates.io 上 reqwest-0.13.5 包的 .cargo_vcs_info.json 记的就是这个 commit
历史首个提交 2016-06-30,tag 上可达 1319 个提交0.13.0 发布于 2025-12-30,0.12.0 发布于 2024-03-20
tag 之后master 到 8e1c806(2026-10-20)又有 5 个提交,没有发版其中 aff2ddb 把 tower-http 升到 0.7,8e1c806 去掉了派发请求时的一次 HeaderMap 克隆;某页讲的代码在这之后改过,会提一句
下层hyper 1.12.0、hyper-util 0.1.21、tower 0.5.3、tower-http 0.6.11、h2 0.4.20、rustls 0.23.46、hyper-rustls 0.27.10、rustls-platform-verifier 0.7.1、aws-lc-rs 1.19.0一个新项目写 reqwest = "=0.13.5" 实际解析到的版本,见 怎么读这一卷
本站0.12.28,默认 TLS 是 native-tls在 08 在本站中 讲,包括升级到 0.13 要改什么

源码链接一律指向 tag,形如 ConnectorService;hyper-util、tower、tower-http 的链接指向上表各自的 tag。

讲什么

  • 发请求:RequestBuilder 把错误攒到 send 时才报;Client::execute_request 补默认头、拼 hyper::Request;请求依次穿过解压、重定向、cookie、重试四层(解压和 cookie 只在开了对应 feature 时才有),进 hyper-util;Pending 这个 future 管总超时和读超时。
  • 连接:Connector / ConnectorService 的三条路(本地套接字、代理、直连);DNS 的两个实现(getaddrinfo 线程池、hickory);rustls 配置怎么拼出来,rustls-platform-verifier 怎么接系统信任库;HTTP 和 SOCKS 代理、系统代理;reqwest 怎么配置 hyper-util 的连接池,HTTP/2 由 ALPN 决定。
  • 响应与策略:Response 的几种读法和连接何时回池;三种超时各在哪一层;重定向、重试、错误、cookie。
  • 阻塞与 WASM:blocking::Client 怎么用一个后台线程和通道包住异步客户端;wasm/ 下哪些功能不存在。
  • 只提一句:HTTP/3(http3 feature,要 --cfg reqwest_unstable)只读源码;Upgraded(HTTP 升级);native-tls 分支只讲和 rustls 不同的地方。

三条主线

全卷沿三条主线展开,每页开头标出自己在哪条线上:

  • 请求主线(一次 client.get(url).send().await):RequestBuilder::send → Client::execute_request(默认头、代理认证头、拼 hyper::Request)→ Decompression(补 Accept-Encoding)→ FollowRedirect → CookieService → Retry → HyperService → hyper-util Client::request:从池里取连接或新建一条(连接主线)→ 写请求、读响应头 → PendingRequest 包成 Response。
  • 连接主线(一条新连接怎么建起来):hyper-util 的池没有空闲连接时调 Connector::call(uri) → ConnectorService::call 先看代理 → connect_with_maybe_proxy:hyper_rustls::HttpsConnector 里的 HttpConnector 解析 DNS(DynResolver)、按 Happy Eyeballs 开 TCP → 有代理时先 CONNECT 隧道 → TLS 握手,ALPN 决定 HTTP/1.1 还是 HTTP/2 → 交回 hyper-util 做 HTTP 握手、放进池。
  • 响应主线(响应体怎么读完):Response::bytes / text / json / chunk / bytes_stream → Body 外面的总超时和读超时包装 → 解压层 → hyper 的 Incoming → 连接何时还回池 → text 的字符集解码。

每条线的每一步落在哪个函数、哪一页讲,见 怎么读这一卷。三条线在本机上从头到尾的一次实跑见 一次请求。

flowchart TB
  intro[00 导读] --> overview[01 全景]
  overview --> req[02 发请求]
  req --> conn[03 连接]
  conn --> resp["04 响应与策略"]
  resp --> bw["05 阻塞与 WASM"]
  bw --> design[06 设计取舍]
  design --> pit[07 常见坑]
  pit --> site[08 在本站中]
  site --> hands[09 动手]

实验环境

关于行为的说法(几条连接、几次握手、耗时多少、发了哪些头、报什么错)都配一个小程序和它的真实输出。平台是 M2 Max、macOS 27、rustc 1.99.0(aarch64)。实验程序依赖 crates.io 上的 reqwest = "=0.13.5",打开默认 feature 加 json、stream、blocking、cookies、gzip、multipart、hickory-dns、socks、query、form。所有请求都打到本机 127.0.0.1 上自己起的服务器(HTTP/1.1、h2c、带 ALPN 的 HTTPS,证书用 rcgen 现签,可配置延迟、重定向、断开),不连外网。这台机器设置了系统代理,每个实验客户端都调 .no_proxy(),原因见 怎么读这一卷。

各部分

  1. 00 导读:锁定版本与下层版本;三条主线每一步落在哪;feature 逐个核对(打开 native-tls 会把默认后端从 rustls 换走,打开 hickory-dns 会换掉默认 resolver);src 各模块的分工、行数和依赖层次。
  2. 01 全景:和直接用 hyper-util、ureq 比代码量、依赖数、二进制大小和本机吞吐;两次 HTTPS 请求的完整日志,第一次 34 ms 里 26 ms 是 aws-lc-rs 首次初始化;Client 是一个 8 字节的 Arc,每个请求新建 Client 在本机要多花约 2 ms。
  3. 02 发请求:请求构建、执行与 Pending、请求体与 multipart。
  4. 03 连接:连接器、DNS、TLS 配置、代理、连接池与 HTTP 版本。
  5. 04 响应与策略:响应体、超时、重定向、重试、错误、cookie。
  6. 05 阻塞与 WASM:阻塞客户端、WASM。
  7. 06 设计取舍:「电池全带」的默认值和代价,建在 hyper-util 上,tower 层的组装,0.13 换默认 TLS。
  8. 07 常见坑:二十个坑,报错原文或现象做标题,每个都先跑出最小复现。
  9. 08 在本站中:本站 OAuth、LLM 客户端、论文采集三处怎么用 reqwest 0.12.28,两种 TLS 都编进来时走哪个,升级到 0.13 要改什么。
  10. 09 动手:直接用 hyper、hyper-util 和 rustls 拼一个约 300 行的小客户端,和 reqwest 对照少了什么。

适合谁读

  • 用过 reqwest、想知道 send().await 背后发生了什么的读者:先读 01 全景 的三页,再按需要读 02 到 04。
  • 在 reqwest、hyper-util、ureq 之间做选择的读者:reqwest 解决什么问题 和 06 设计取舍 给出能测的差别。
  • 需要的前置知识:HTTP/1.1 和 HTTP/2 的基本概念(keep-alive、ALPN、多路复用);Rust 的 async/await 和 Future;tower 的 Service / Layer(见 tower 卷)。连接池、HTTP/1 和 HTTP/2 的实现在 hyper 卷 讲,TLS 握手在 rustls 卷 讲,本卷只讲 reqwest 怎么配置和组装它们。

💬 关联讨论

发起讨论
加载中...