Rust in Everything
基于 hyper 1.12.0

客户端

客户端主线是「请求 → 字节 → 响应」。hyper 1.x 只保留了最底下的一段:给它一条连接,它负责在上面发请求、收响应。连接从哪里来、用完放到哪里、失败了换哪一条,都在 hyper-util 的 client::legacy 里,reqwest 用的就是它。前四章从下往上,后两章分别是可以插进 legacy Client 的代理连接器,和不依赖它的连接池积木:

  1. conn::http1 客户端:handshake 返回 SendRequest 和要自己 spawn 的 Connection;请求通道是无界 mpsc 加 want 标志,连接空闲时才就绪,每次只放进一个请求;请求在各个环节失败时报什么,dispatch task is gone 什么时候出现。
  2. legacy Client:request 算出池的键、拿连接、补 Host、改 URI 形式再发;retry_canceled_requests 只重发还没写出、且连接是复用来的请求,实测这个窗口很窄。
  3. 连接池:按 (scheme, authority) 分组的空闲连接,取连接与新建连接赛跑,空闲超时和清理任务,HTTP/2 连接的共享。
  4. HttpConnector:连接器就是 tower::Service<Uri>,DNS 解析、Happy Eyeballs、TCP 选项;TLS 为什么要另外套一层。
  5. 代理连接器:Tunnel 与 SOCKS:包着内层连接器的 Service<Uri>,先连代理再握手;目标缺端口一律按 443,200 之后早到的字节被丢掉,SOCKS5 用户名、密码、主机名合计不能超过 249 字节。
  6. client::pool:四块积木:singleton、cache、negotiate、map 四个 tower 积木,legacy Client 不用;串行时能拼成 h1/h2 分流的池,并发时 Singleton 会 panic。
flowchart TB
  client["legacy Client:request"] --> pool["连接池:取或新建"]
  pool --> proxy["代理连接器:Tunnel / SOCKS"]
  proxy --> connector["HttpConnector:DNS 与 TCP"]
  pool --> connector
  connector --> handshake["conn::http1 handshake"]
  pool --> sendreq["SendRequest:请求通道"]
  handshake --> sendreq
  handshake --> conntask["Connection 任务:Dispatcher"]
  sendreq --> conntask
  conntask -->|"响应"| client

Connection 任务里跑的 Dispatcher 和服务端是同一个,见 HTTP/1 连接;HTTP/2 的客户端连接见 HTTP/2 客户端。

💬 关联讨论

发起讨论
加载中...