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:抓包与命令速查

第07章 HTTP/2、gRPC 与 HTTP/3(QUIC) 代理的挑战

学习目标

  • 理解 HTTP/2 的多路复用为何让代理"不能再简单 splice",必须理解帧层
  • 搞懂代理 gRPC 的三个坑:trailers、长连接复用、流式不能缓冲
  • 理解 HTTP/3 = QUIC 给代理出的新题:UDP、连接迁移、加密程度、MASQUE
  • 会用 curl --http2/--http3、openssl -alpn 观察协议协商

前置知识

  • 第04章 HTTP 代理协议、第05章 TLS 代理(ALPN)
  • 第02章 网络层级(L4 splice vs L7 解析)

原理

从 HTTP/1.1 说起:代理为什么曾经很简单

HTTP/1.1 是文本协议、一条连接一次一个请求(pipelining 实际已死)。所以 L7 代理处理它很直白:读一个请求 → 转发 → 读一个响应 → 转回。一条 TCP 连接就对应一条请求流,第21章 那种"解析+转发"模型够用。

HTTP/2 和 HTTP/3 打破了"一连接一请求"的假设,代理的复杂度陡增。

HTTP/2 给代理的三道题

HTTP/2 是二进制、多路复用协议:一条 TCP 连接上并发跑多个 stream,每个 stream 是一个独立的请求/响应,数据被切成 frame 交错传输。

            一条 TCP 连接
   ┌──────────────────────────────────┐
   │ [stream1帧][stream3帧][stream1帧] │   多个请求的帧交错在一条连接上
   │ [stream5帧][stream1帧][stream3帧] │
   └──────────────────────────────────┘

题①:不能再盲转字节。 L4 splice 看到的是一堆交错的二进制帧,无法按请求路由。L7 的 HTTP/2 代理必须解析帧层,把帧重组成一个个 stream,才能按请求做路由/改写。这就是为什么 HTTP/2 反向代理(Nginx、Envoy)比 HTTP/1 代理重得多。

题②:HPACK 头压缩要重算。 HTTP/2 用 HPACK 压缩头部,且压缩状态在一条连接内是有状态的。代理一旦改了头,就得在自己这一侧重新维护 HPACK 上下文、重新编码——不能简单透传压缩后的字节。

题③:连接复用语义变了。 客户端↔代理可能是 h2(多路复用),代理↔后端可能是 h1(多条连接)。代理要做 h2↔h1 的"翻译":把一条 h2 连接上的 N 个 stream,映射到后端的 N 条 h1 连接(或一条 h2)。这种协议降级/升级是反向代理的常见配置。

Client ══ h2(1条连接, N个stream) ══▶ [代理解帧] ── h1(N条连接) ──▶ 后端

gRPC = HTTP/2 + 三个额外要求

gRPC 跑在 HTTP/2 之上,但对代理有 HTTP/2 之外的硬要求:

  1. 必须支持 trailers(尾部头):gRPC 的状态码 grpc-status 放在 响应的 trailer(响应体之后),不是普通响应头。代理若不转发 trailers,客户端永远收不到调用结果状态 → 表现为 gRPC 调用挂起或报错。
  2. 不能缓冲、必须流式:gRPC 有双向流式(client/server streaming)。代理若像处理普通响应那样"缓冲完整个 body 再转"(第03章 的 buffering),流式 RPC 就会卡死。必须配 proxy_buffering off 类选项。
  3. 长连接复用:gRPC 在一条 h2 连接上持续跑多个调用。代理若过早关连接或不正确复用,性能崩塌。

结论:代理 gRPC 要选原生支持 h2 + trailers + 流式的代理。Envoy 是 gRPC 代理的事实标准(第15章),Nginx 需 grpc_pass 且版本够新,老配置常踩 trailers 的坑。

HTTP/3 = QUIC:代理的范式转变

HTTP/3 把传输层从 TCP 换成了 QUIC(跑在 UDP 上 + 内置 TLS 1.3)。这对代理是范式级冲击:

变化①:从 TCP 到 UDP。 代理/负载均衡几十年的积累都是围绕 TCP 的(连接跟踪、splice、四元组哈希)。QUIC 是 UDP,这些大多要重写。很多 L4 LB 对 QUIC 的支持仍不成熟。

变化②:多路复用下沉到 QUIC,无队头阻塞。 HTTP/2 的多路复用在 TCP 之上,一个丢包阻塞所有 stream(TCP 队头阻塞)。QUIC 在传输层就做了独立 stream,丢包只影响单个 stream。代理要理解 QUIC stream 才能做 L7。

变化③:连接迁移(Connection Migration)。 QUIC 连接由 Connection ID 标识,而非 IP+端口四元组。客户端从 WiFi 切到 4G、IP 变了,连接不断——靠 Connection ID 续上。这要求负载均衡器按 Connection ID 路由,不能再按四元组哈希,否则迁移后的包会被分到错误的后端 → 连接中断。

客户端 WiFi(IP_A) ──QUIC, CID=abc──▶ [LB 按 CID=abc 路由] ──▶ 后端X
客户端 切4G(IP_B) ──QUIC, CID=abc──▶ [LB 必须仍按 CID 路由到] ──▶ 后端X  ✅ 不断连
                                      (若按四元组哈希 → 路由到后端Y → 断!)

变化④:加密程度更高。 QUIC 把更多控制信息也加密了,代理能"偷看"的更少,SNI 嗅探类技巧进一步受限(叠加 第05章 的 ECH)。

变化⑤:代理 UDP/QUIC 的新标准 MASQUE。 传统 HTTP 代理用 CONNECT 隧道 TCP;要隧道 UDP/QUIC,需要新机制——MASQUE 工作组的 CONNECT-UDP(RFC 9298)让 HTTP 代理能转发 UDP 数据报,是"用 HTTP/3 代理 HTTP/3"的基础。

协议协商:ALPN 与 Alt-Svc

  • ALPN:TLS 握手时,客户端在 ClientHello 里列出支持的协议(h2、http/1.1),服务器/代理选一个。代理做 TLS 终止时,它和客户端 ALPN 协商的结果,决定了客户端这侧用 h1 还是 h2——与后端那侧可以不同。
  • Alt-Svc:HTTP/3 不能在 TLS(TCP)里用 ALPN 协商(因为 h3 在 UDP 上)。服务器先用 h1/h2 响应,附带 Alt-Svc: h3=":443" 头,告诉客户端"我也支持 h3,下次走 UDP 443",客户端据此升级。代理要正确转发或改写 Alt-Svc。

一张表:三代协议对代理的要求

维度HTTP/1.1HTTP/2HTTP/3
传输层TCPTCPQUIC/UDP
多路复用❌(一连接一请求)✅(TCP 之上,有队头阻塞)✅(QUIC 层,无队头阻塞)
代理可否 splice✅(隧道时)❌ 须解帧❌ 须解 QUIC
连接标识四元组四元组Connection ID
头压缩无HPACKQPACK
代理难度低中高
代理 UDP 的隧道——MASQUE/CONNECT-UDP

️ 实现 / 命令

实验一:看 ALPN 协商结果

# 看服务器/代理通过 ALPN 选了什么协议
openssl s_client -connect example.com:443 -alpn h2,http/1.1 </dev/null 2>/dev/null \
  | grep -i "ALPN"
# ALPN protocol: h2     ← 协商成了 HTTP/2

实验二:强制 curl 走 h2 / h3 经过代理

# HTTP/2 经代理(CONNECT 隧道后端到端 h2,代理只盲转——透传模式没问题)
curl --http2 -x http://127.0.0.1:3128 https://example.com/ -s -o /dev/null -w "%{http_version}\n"
# 2

# HTTP/3(需 curl 编译了 HTTP3 支持;走 UDP,传统 HTTP 代理无法 CONNECT 隧道它)
curl --http3 https://example.com/ -s -o /dev/null -w "%{http_version}\n"
# 3

关键观察:HTTP 正向代理用 CONNECT 透传 h2/h3 是可以的(盲转密文,第05章);但要在 L7理解并路由 h2/h3,代理就必须原生支持对应协议栈。

实验三:Envoy 做 gRPC 反向代理(最小配置片段)

# Envoy 对 gRPC(h2 + trailers + 流式)原生友好
http_filters:
  - name: envoy.filters.http.router
route_config:
  virtual_hosts:
    - name: grpc_svc
      domains: ["*"]
      routes:
        - match: { prefix: "/", grpc: {} }   # 显式匹配 gRPC
          route:
            cluster: grpc_backend
            timeout: 0s                        # 流式 RPC 不能设响应超时
clusters:
  - name: grpc_backend
    http2_protocol_options: {}                 # 关键:对后端也用 h2

http2_protocol_options: {} 让 Envoy 对后端讲 h2,timeout: 0s 避免流式被超时切断——这两条正是 gRPC 代理的命门。

实验四:用 grpcurl 验证 trailers 是否被正确转发

# 经代理调 gRPC,若 trailers 丢失会卡住或报 "missing grpc-status"
grpcurl -plaintext -proxy http://127.0.0.1:8080 grpc.example.com:443 list
# 正常返回服务列表 = trailers 通了

排错

现象根因解决
gRPC 调用挂起 / missing grpc-status代理没转发 trailers换支持 trailers 的代理(Envoy)或升级 Nginx + grpc_pass
流式 gRPC 卡死、收不到流代理缓冲了响应体关缓冲:proxy_buffering off / Envoy 默认流式
h2 客户端经代理变成 h1代理只支持 h1 或 ALPN 没配 h2代理侧开启 h2、配 ALPN
HTTP/3 经传统代理不通h3 在 UDP,CONNECT 隧道不了用支持 MASQUE 的代理,或回落 h2
QUIC 连接迁移后断连LB 按四元组哈希而非 Connection IDLB 启用 QUIC-aware(按 CID 路由)
Alt-Svc 不生效、客户端不升级 h3代理吞掉了 Alt-Svc 头确保代理透传/正确改写 Alt-Svc

本章小结

  • HTTP/2 的多路复用让代理不能再盲转,必须解帧、重组 stream、重算 HPACK;还要做 h2↔h1 翻译。
  • gRPC 在 h2 上加了三条硬要求:转发 trailers、流式不缓冲、长连接复用,Envoy 是事实标准。
  • HTTP/3 = QUIC(UDP+TLS1.3):UDP 化、无队头阻塞、按 Connection ID 路由(连接迁移)、加密更彻底、需 MASQUE 隧道 UDP。
  • ALPN 协商 h1/h2,Alt-Svc 通告 h3;代理两侧协议可不同。

下一章 第08章,我们回到客户端侧:浏览器/系统/程序到底怎么"知道"该用哪个代理——PAC、WPAD、系统代理与 NO_PROXY 的全部门道与坑。

Prev
第06章 SOCKS 协议:SOCKS4/4a/5 与 UDP ASSOCIATE 报文级解析
Next
第08章 代理自动配置:PAC / WPAD / 系统代理 / NO_PROXY