HiHuo
首页
博客
手册
工具
关于
首页
博客
手册
工具
关于
  • 代理技术全栈手册

    • 代理技术全栈手册 - HiHuo
    • 原理篇

      • 第01章 代理是什么:正向 / 反向 / 透明 / 隧道的统一模型
      • 第02章 代理与网络层级:L3 / L4 / L5 / L7 在哪里截断流量
      • 第03章 一个请求穿过代理的一生:连接生命周期全景
    • 协议篇

      • 第04章 HTTP 代理协议:绝对 URI、CONNECT 隧道、转发头与连接池
      • 第05章 HTTPS 与 TLS 代理:终止 / 透传 / MITM / SNI / mTLS
      • 第06章 SOCKS 协议:SOCKS4/4a/5 与 UDP ASSOCIATE 报文级解析
      • 第07章 HTTP/2、gRPC 与 HTTP/3(QUIC) 代理的挑战
      • 第08章 代理自动配置:PAC / WPAD / 系统代理 / NO_PROXY
    • 层级与转发篇

      • 第09章 L4 代理:TCP/UDP 转发与连接级负载均衡
      • 第10章 L7 代理:协议感知与基于内容的路由
      • 第11章 透明代理:iptables REDIRECT/DNAT、TPROXY 与 eBPF 劫持
      • 第12章 数据搬运的艺术:splice / sendfile / 零拷贝 / io_uring
    • 组件横评篇

      • 第13章 Nginx / OpenResty:反向代理、upstream 与 Lua 可编程
      • 第14章 HAProxy:L4/L7、ACL、健康检查与 stick table
      • 第15章 Envoy:xDS 动态配置与 filter chain,为何是云原生数据面
      • 第16章 Traefik / Caddy:自动服务发现与自动 HTTPS
      • 第17章 Squid 与正向/缓存代理:企业出网、缓存与审计
      • 第18章 mitmproxy:抓包、改包、脚本化调试
      • 第19章 内网穿透与隧道:frp / gost / SSH 隧道 / ngrok
      • 第20章 科学上网生态的技术原理(技术中立)
    • 多语言手写篇

      • 第21章 Go:100 行手写 HTTP/CONNECT + SOCKS5 代理
      • 第22章 Rust:基于 tokio 的高性能 TCP 代理
      • 第23章 Python:asyncio 实现,适合调试与脚本
      • 第24章 C:epoll 裸写与零拷贝,及语言选型对比
    • 容器与K8s篇

      • 第25章 Docker 里的代理:HTTP_PROXY、build/pull 与 daemon 配置
      • 第26章 Sidecar 与流量劫持:Istio init-container 的 iptables 原理
      • 第27章 Ingress 与南北流量:Ingress-nginx 与 Gateway API
      • 第28章 Egress 与出网治理:出口网关、registry mirror、审计
      • 第29章 Service Mesh 数据面:Envoy Sidecar 全链路
    • 进阶篇

      • 第30章 可编程代理:Lua / Wasm / eBPF / xDS,代理的"软件定义"
      • 第31章 性能调优:并发模型、连接池、超时与重试、压测
      • 第32章 排错决策树:502 / 504 / 握手失败 / 环路 / 泄漏
      • 第33章 代理安全:开放代理、SSRF、凭证泄漏与攻击面
    • 底层机制篇

      • 第34章 代理的背压与流控:一个代理最难的部分
      • 第35章 socket 与 TCP 状态机:半关闭、超时、连接生命周期
      • 第36章 HTTP/2 帧、流控与 HPACK:h2 代理的内部机制
      • 第37章 负载均衡算法推导与韧性状态机
      • 第38章 Capstone:把玩具代理改造成生产级骨架
    • 综合实战篇

      • 第39章 企业多跳转发链:拓扑、协议矩阵与贯穿性难题
      • 第40章 端到端实战:把 6 类流量全代理通
      • 第41章 更刁钻的流量:gRPC、长轮询、WebRTC、大文件、双向流
      • 第42章 可落地完整参考实现:一套能跑的多协议转发栈
    • 附录

      • 附录 A:代理协议报文速查(HTTP / SOCKS / PAC / PROXY protocol)
      • 附录 B:组件选型决策树
      • 附录 C:抓包与命令速查

第04章 HTTP 代理协议:绝对 URI、CONNECT 隧道、转发头与连接池

学习目标

  • 掌握 HTTP 请求的四种 request-target 形式,理解正向代理为何用"绝对 URI"
  • 搞懂 hop-by-hop 头与 end-to-end 头的区别,知道代理为什么"必须删头"
  • 读懂 Via、X-Forwarded-For、Forwarded 这套转发溯源头,及其信任边界
  • 用 407 + Proxy-Authorization 完成代理认证
  • 吃透 CONNECT 隧道:从报文、200 响应到隧道内的裸字节流

前置知识

  • 第01章 代理统一模型(绝对 URI vs 相对路径已在此首次出现)
  • 网络手册 · 应用层协议

原理

一、request-target 的四种形式

HTTP 请求行长这样:方法 请求目标 版本。这个"请求目标"(request-target)有四种形式(RFC 7230 §5.3),代理协议的一切从这里展开:

形式样例用在哪
origin-formGET /index.html HTTP/1.1直连源站、反向代理(最常见)
absolute-formGET http://example.com/index.html HTTP/1.1正向代理(客户端→代理)
authority-formCONNECT example.com:443 HTTP/1.1只用于 CONNECT 建隧道
asterisk-formOPTIONS * HTTP/1.1只用于服务器级 OPTIONS

为什么正向代理必须用 absolute-form? 因为代理面向"任意目标"。客户端如果只发 GET /index.html,代理根本不知道该把请求转给谁。Host 头虽然也带主机名,但 absolute-form 是协议层面明确告诉代理"完整 URL 在此,请转发"。这就是 第01章 里"目标写在请求里 vs 写在配置里"的协议落点。

客户端 → 正向代理:  GET http://example.com/p HTTP/1.1   (absolute-form)
正向代理 → 源站:    GET /p HTTP/1.1                       (代理转成 origin-form)
                     Host: example.com

一个合格的正向代理收到 absolute-form 后,会剥掉 scheme 和 host,转成 origin-form 再发给源站——因为源站不需要、有时也不接受 absolute-form。

二、hop-by-hop 头 vs end-to-end 头(代理为什么"删头")

这是 HTTP 代理最容易被忽视、却最关键的一条规则。HTTP 头分两类(RFC 2616 §13.5.1):

  • end-to-end(端到端)头:要一路传到最终接收方。如 Host、Content-Type、Cache-Control。
  • hop-by-hop(逐跳)头:只对当前这一跳连接有意义,代理必须删除,不得转发。

固定的 hop-by-hop 头有 8 个:

Connection         Keep-Alive        Proxy-Authenticate
Proxy-Authorization   TE             Trailer*
Transfer-Encoding  Upgrade

(Trailer 在新规范中归类有争议,实践中按逐跳处理最安全。)

更重要的是:Connection 头里列出的字段名也都是 hop-by-hop,代理必须一并删除。例如客户端发 Connection: close, X-Foo,代理要删掉 Connection 自身、Foo 头。

为什么这事关安全? 如果代理不删 hop-by-hop 头,会引发:

  • 连接走私(Request Smuggling):Transfer-Encoding / Content-Length 跨跳不一致时,攻击者可在一个请求里"偷藏"另一个请求
  • 信息泄漏:Proxy-Authorization(你的代理密码)被原样转发到源站
┌─────────┐   Connection: keep-alive, Foo   ┌────────┐   (删除 Connection 及 Foo)   ┌────────┐
│ Client  │ ──────────────────────────────▶ │ Proxy  │ ────────────────────────────▶ │ Origin │
│         │   Proxy-Authorization: ...      │        │   (删除!不外泄代理凭证)      │        │
└─────────┘                                 └────────┘                               └────────┘

三、转发溯源头:Via / X-Forwarded-For / Forwarded

代理"一拆为二"后,源站默认只看到代理的 IP。要让源站知道"真实客户端是谁、经过了哪些代理",靠这几个头:

头作用样例
Via标记请求经过的每一跳代理(协议+版本+标识)Via: 1.1 squid-prod (squid/5.7)
X-Forwarded-For事实标准:记录原始客户端及沿途代理 IP(链式追加)X-Forwarded-For: 203.0.113.7, 10.0.0.2
X-Forwarded-Proto原始请求的协议(http/https),TLS 终止后用它告诉后端X-Forwarded-Proto: https
X-Forwarded-Host原始 HostX-Forwarded-Host: example.com
ForwardedRFC 7239 的标准化版本,合并上面三者Forwarded: for=203.0.113.7;proto=https;by=10.0.0.2

XFF 的链式语义:每经过一个代理,就在 X-Forwarded-For 右侧追加自己看到的来源 IP:

Client(203.0.113.7) → 代理A → 代理B → Origin
Origin 看到:X-Forwarded-For: 203.0.113.7, <代理A的IP>
                              └最左是真实客户端┘ └右侧是沿途代理┘

⚠️ 信任边界:X-Forwarded-For 是客户端可伪造的普通头!只有"你自己掌控的、紧挨着的那个代理追加的那一段"可信。取真实客户端 IP 时,绝不能无脑取最左值——必须从你信任的代理往外数。这是 Web 安全里最常见的 IP 伪造点,详见 第33章 代理安全。

四、代理认证:407 与 Proxy-Authorization

代理认证和源站认证是两套独立机制,状态码和头都不同:

角色质询状态码质询头应答头
源站认证401 UnauthorizedWWW-AuthenticateAuthorization
代理认证407 Proxy Authentication RequiredProxy-AuthenticateProxy-Authorization

握手流程:

Client → Proxy:  GET http://example.com/ HTTP/1.1
Proxy  → Client: 407 Proxy Authentication Required
                 Proxy-Authenticate: Basic realm="corp-proxy"
Client → Proxy:  GET http://example.com/ HTTP/1.1
                 Proxy-Authorization: Basic dXNlcjpwYXNz      ← base64(user:pass)
Proxy  → Client: 200 OK ...(认证通过,开始转发)

注意 Proxy-Authorization 是 hop-by-hop 头——代理用完必须删掉,绝不能转发给源站(否则你的代理密码就泄漏给外部网站了)。

五、CONNECT:明文代理如何代理加密流量

正向代理能改写明文 HTTP,但 HTTPS 是加密的,代理无法解析。解法是 CONNECT:让代理只做盲转字节的 L4 隧道。

① Client → Proxy:  CONNECT example.com:443 HTTP/1.1     (authority-form)
                    Host: example.com:443
                    Proxy-Authorization: ...(如需认证)

② Proxy 建立到 example.com:443 的 TCP 连接

③ Proxy → Client:  HTTP/1.1 200 Connection Established  (无响应体)

④ 此后这条 TCP 连接变成纯隧道:
   Client ══ TLS ClientHello / 证书 / 加密应用数据 ══▶ Origin
   代理只在两个 socket 间 bidirectional copy,看不懂任何明文

CONNECT 的几个要点:

  • 响应是 2xx(习惯用 200,reason 短语写啥都行),且没有 body、没有 Content-Length
  • 隧道建立后,这条连接不再是 HTTP,代理不能再往里塞头
  • 出于安全,代理通常限制 CONNECT 的目标端口(只放行 443 等),否则会沦为任意 TCP 跳板(开放代理风险,见 第33章)
  • 想看明文?只能做 MITM:代理动态签发证书、终止 TLS 再重新加密——这是 第05章 和 mitmproxy(第18章)的主题

六、连接管理与 Proxy-Connection 的历史包袱

代理与客户端、代理与源站之间都希望复用 TCP 连接(keep-alive)省握手开销。但 Connection 是 hop-by-hop 头,早期代理实现有 bug:会把 Connection: keep-alive 原样透传,导致问题。于是浏览器发明了非标准的 Proxy-Connection 头来"绕过"老代理。

结论:Proxy-Connection 是历史遗留,不是标准。现代实践:

  • 客户端↔代理、代理↔源站是两段独立的 keep-alive,各自管理连接池
  • 代理对源站维护连接池复用后端连接(性能关键,见 第31章 性能调优)
  • 看到 Proxy-Connection 知道它是怎么来的即可,自己写代理别依赖它

命令 / 实验

实验一:看正向代理对头的处理(Via 与删头)

用 Squid 起一个标准正向代理(apt install squid,默认听 3128):

curl -v -x http://127.0.0.1:3128 http://httpbin.org/headers

httpbin.org/headers 会把它最终收到的头原样回显。你会观察到:

{
  "headers": {
    "Host": "httpbin.org",
    "User-Agent": "curl/8.5.0",
    "Via": "1.1 squid-prod (squid/5.7)",          // ← 代理加的溯源头
    "X-Forwarded-For": "203.0.113.7"               // ← 代理追加的真实客户端 IP
    // 注意:没有 Proxy-Connection、没有 Proxy-Authorization —— 被代理删掉了
  }
}

对照 第01章实验一:客户端发出的请求行是 GET http://httpbin.org/headers(absolute-form),但 httpbin 收到的是 origin-form——证明代理做了"绝对 URI → 相对路径"的转换。

实验二:触发 407 代理认证

给 Squid 配上 Basic 认证后:

# 不带凭证 → 407
curl -v -x http://127.0.0.1:3128 http://example.com/
# < HTTP/1.1 407 Proxy Authentication Required
# < Proxy-Authenticate: Basic realm="corp-proxy"

# 带凭证 → 通过(curl 自动算 base64 并加 Proxy-Authorization)
curl -v -x http://alice:secret@127.0.0.1:3128 http://example.com/
# > Proxy-Authorization: Basic YWxpY2U6c2VjcmV0
# < HTTP/1.1 200 OK

实验三:观察 CONNECT 隧道的完整握手

curl -v -x http://127.0.0.1:3128 https://example.com/ 2>&1 | head -25

关键输出(已精简注释):

* Establish HTTP proxy tunnel to example.com:443
> CONNECT example.com:443 HTTP/1.1           ← authority-form 隧道请求
> Host: example.com:443
> Proxy-Connection: Keep-Alive
>
< HTTP/1.1 200 Connection established         ← 代理:隧道已开
<
* CONNECT phase completed
* ALPN: offers h2,http/1.1
* TLS connection using TLSv1.3 ...             ← 此后是 TLS,代理看不懂
> GET / HTTP/2                                 ← 这个 GET 是加密发给源站的,代理无感

抓包侧证:CONNECT 之后代理两端的流量是无法解析的 TLS record:

sudo tcpdump -i any -n -A 'tcp port 3128' 2>/dev/null | head -30
# 你会先看到明文 "CONNECT example.com:443" 和 "200 Connection established",
# 之后全是不可读的二进制(TLS)——这就是隧道的"盲转"本质。

排错

现象可能原因排查方向
代理返回 502 Bad Gateway代理到源站这一跳失败(DNS 解析不了、源站拒连、源站返回非法响应)在代理上直接 curl 目标;查源站可达性
代理返回 504 Gateway Timeout代理等源站响应超时查源站负载、代理的 read timeout 配置
CONNECT 被拒 403/405代理的端口白名单不含目标端口,或禁用了 CONNECT查代理 ACL(Squid 的 Safe_ports/SSL_ports)
源站拿到的客户端 IP 全是代理 IP未配 X-Forwarded-For,或后端没解析它代理加 XFF;后端配 set_real_ip_from(Nginx)等
代理密码泄漏到源站代理未删 Proxy-Authorization(实现 bug)抓源站侧入流量确认;换合规代理
HTTPS 经代理后证书报错代理在做 MITM(动态签证书)检查是否企业根证书;见 第05章
偶发请求"串味"/响应错配hop-by-hop 头未删导致请求走私统一 Transfer-Encoding/Content-Length 处理;见 第33章

本章小结

  • 正向代理用 absolute-form(绝对 URI)接收请求,再转成 origin-form 发给源站。
  • 代理必须删除 hop-by-hop 头(含 Connection 里点名的字段),这既是协议要求也是安全底线。
  • Via/X-Forwarded-For/Forwarded 负责转发溯源;XFF 可伪造,取真实 IP 要从可信代理往外数。
  • 代理认证用 407 + Proxy-Authorization,与源站的 401/Authorization 是两套。
  • CONNECT 让 HTTP 代理用 L4 隧道盲转加密流量,是代理 HTTPS 的标准手段;想看明文得做 MITM。

下一章进入 第05章 HTTPS 与 TLS 代理,把"终止 / 透传 / MITM / SNI 路由 / mTLS"这套 TLS 代理的玩法讲透。

Next
第05章 HTTPS 与 TLS 代理:终止 / 透传 / MITM / SNI / mTLS