Rust in Everything
基于 reqwest 0.13.5

连接

请求主线走到 hyper-util 的 legacy Client 时,先去连接池里找一条能用的连接;找不到,才进入这一部分讲的连接主线:reqwest 的 Connector 拿到一个只有 scheme 和 authority 的 Uri,解析主机名,建 TCP,有代理时先打通隧道,是 HTTPS 就做 TLS 握手,最后交回一个 Conn,由池子按 ALPN 的结果决定在上面跑 HTTP/1.1 还是 HTTP/2。

这条线上真正属于 reqwest 的代码不多。TCP 建连和 Happy Eyeballs 在 hyper-util 的 HttpConnector 里,握手在 rustls 或 native-tls 里,连接池也是 hyper-util 的,它们分别在 hyper 卷和 rustls 卷讲过。reqwest 做的是组装和选择:用哪个 resolver、哪个 TLS 后端、哪个 crypto provider、哪个证书校验器、ALPN 报什么、哪个代理拦截这个地址,以及把超时套在哪一层。这些选择几乎都在 ClientBuilder::build 里一次做完,连接时不再变化。

代码分布(按 tag v0.13.5 统计 .rs 行数,含测试和文档注释):src/connect.rs 2139 行,src/tls.rs 922 行,src/dns/ 312 行,src/proxy.rs 948 行;组装它们的代码在 src/async_impl/client.rs 的 ClientBuilder::build 里。

  1. 连接器:ConnectorBuilder、ConnectorService、Connector 三个类型;connector_layer 让用户在外面套 tower 层,层看到的请求类型不可命名,每条新连接才调用一次;Inner 的 TLS 分支、Conn 带回的代理标记、negotiated_h2 和 TlsInfo(经 CONNECT 隧道时恒缺);connect_timeout 同时约束 hyper-util 里的 TCP 和整个连接器调用。
  2. DNS:Resolve trait 与 DynResolver;默认 getaddrinfo 跑在 tokio 的阻塞线程池,hickory-dns feature 一打开就成为默认;resolve 覆盖的匹配与端口规则;connect_timeout 在多个地址之间平分。
  3. TLS 配置:0.13 默认 rustls 加 aws-lc-rs 和平台校验器,但编进 native-tls 时默认仍是 native-tls;provider 先取进程默认;四种证书校验器与两个 danger 开关各跳过了什么;ALPN 与 HTTP 版本;tls_backend_preconfigured 跳过的配置;实测默认 ClientHello。
  4. 代理:Proxy 的几种写法和 NoProxy 规则,系统代理从哪里读,HTTPS 走 CONNECT 隧道、HTTP 写绝对形式的请求行,SOCKS 与代理认证。
  5. 连接池与 HTTP 版本:reqwest 怎么配置 hyper-util 的连接池,HTTP/2 由 ALPN 决定还是 http2_prior_knowledge 指定,HTTP/3 的独立连接池。
flowchart TB
  pool["hyper-util 连接池"] -- 没有可用连接 --> conn["Connector"]
  conn --> dns["DNS:目标或代理"]
  dns --> tcp["TCP"]
  tcp --> proxy{"经代理?"}
  proxy -- 是 --> tunnel["CONNECT 或 SOCKS"]
  proxy -- 否 --> tls["TLS 与 ALPN"]
  tunnel --> tls
  tls --> c["Conn"]
  c --> pool

图里的每个节点都在 build 时配置好:resolver 见 DNS,TLS 见 TLS 配置,代理匹配见 代理,它们怎么被装进 Connector 见 连接器。连接交回以后的复用规则见 连接池与 HTTP 版本;一次请求从头到尾经过这几页的哪些代码,见 一次请求。

💬 关联讨论

发起讨论
加载中...