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

第41章 更刁钻的流量:gRPC、长轮询、WebRTC、大文件、双向流

学习目标

  • 在多跳链里正确代理 gRPC(端到端 h2/trailers)与浏览器侧的 gRPC-Web
  • 理解长轮询为何是"伪装成 HTTP 的长连接",及它的代理要点
  • 看清 WebRTC 为什么不能像 TCP 那样代理:信令走 WS、媒体走 UDP/TURN
  • 处理大文件上传/下载的请求缓冲、体积上限与背压

前置知识

  • 第39章 协议矩阵/三铁律、第07章/第36章 h2 内部
  • 第09章 UDP 代理、第34章 背压

原理

第40章 通了常见 6 类,这章啃硬骨头——它们要么打破"HTTP 请求-响应"假设,要么根本不走 TCP。

gRPC:端到端 h2 + trailers,断一跳就废

gRPC = HTTP/2 + length-prefixed 消息 + trailers 里放 grpc-status(第07章/第36章)。在多跳链里,它的铁律是:

整条链必须端到端保持 h2 + 转发 trailers。 任何一跳把 h2 降级成 h1(再到后端),gRPC 就废——h1 没有 trailers、没有 h2 帧。

每跳的正确做法:

  • nginx:用 grpc_pass(不是 proxy_pass!)转发到 gRPC 后端,监听 http2:
    server {
        listen 443 ssl http2;
        location / { grpc_pass grpc://backend:50051; }   # grpcs:// 则到后端也 TLS
    }
    
  • ingress-nginx:加注解声明后端是 gRPC:
    nginx.ingress.kubernetes.io/backend-protocol: "GRPC"
    
  • Envoy/Mesh:原生 h2,是 gRPC 代理首选(第15章)

gRPC 的负载不均(重灾区):一条 h2 长连接复用所有调用,L4 连接级 LB 把它焊死到一个后端(第09章/第39章),扩容后新 Pod 没流量。解法三选一:

  1. L7 请求级 LB(Envoy/Linkerd 在 stream 粒度分发)
  2. 客户端侧 LB:gRPC 客户端用 headless Service(DNS 返回所有 Pod IP)+ 内置 round_robin
  3. proxyless gRPC(xDS 直连,去掉中间代理)

gRPC-Web:浏览器进不了原生 gRPC

浏览器无法发原生 gRPC——fetch/XHR 读不到 HTTP trailers,也控制不了 h2 帧。所以浏览器用 gRPC-Web:把 gRPC 编码成浏览器能处理的形式(trailer 放进 body 末尾),再由一个翻译代理还原成真 gRPC 发给后端:

  浏览器 ──gRPC-Web(HTTP/1.1 or h2)──▶ [翻译代理] ──真 gRPC(h2+trailers)──▶ 后端
                                       Envoy grpc_web filter / grpcwebproxy

所以浏览器要调 gRPC 服务,链里必须有一跳做 gRPC-Web 翻译(Envoy 的 envoy.filters.http.grpc_web,或独立 grpcwebproxy)。少了这一跳,前端报"trailers 缺失"类错误。

长轮询:伪装成 HTTP 的长连接

长轮询(long polling,Comet)是 WebSocket/SSE 不可用时的回退:

  客户端 → GET /poll (普通 HTTP 请求)
  服务端 → 【挂起这个请求】,直到有数据 or 超时,才返回
  客户端 → 收到响应后【立即再发一个 /poll】,如此循环

它看起来是普通 HTTP,但每个请求被服务端held open 很久。代理要点:

  • 长 read 超时(每跳,铁律一)——否则请求在"挂起等数据"时被代理当成超时掐断
  • 不缓冲(铁律二)——响应来得晚,缓冲没意义还增延迟
  • 大量 held-open 连接:N 个客户端 = N 条挂起的连接占着 fd/内存(第35章)——比普通 HTTP 吃资源
  • 没有 Upgrade(区别于 WS),所以不用动逐跳头

WebRTC:信令走代理、媒体不走代理

WebRTC 是这章最反直觉的——它的媒体流根本不经过你的反向代理:

  ① 信令(offer/answer/ICE 候选)走 WebSocket/HTTPS → 经正常 L7 多跳链
  ② 媒体(音视频,SRTP over UDP)走 P2P 直连;NAT 打不通时经 TURN 中继
                                            ↑ STUN/ICE 做 NAT 穿透
  • 信令:普通 WebSocket(第40章),走你的 nginx/Ingress L7 链
  • 媒体:UDP(SRTP/DTLS),是 P2P 或经 TURN 中继。TURN 服务器(如 coturn)本质就是一个 UDP 中继代理(第09章 UDP 伪会话)——当双方 NAT 无法直连时,媒体经 TURN 转发
  • K8s 落地难点:UDP + 大量端口 + NAT。TURN 通常用 hostNetwork 或 LoadBalancer(UDP),不走 ingress-nginx(它对 UDP 支持有限,虽有 udp-services ConfigMap)。媒体端口范围大,要在防火墙/SG 放行

一句话:"代理 WebRTC" = 用 L7 链代理它的信令(WS)+ 部署 TURN 中继它的媒体(UDP)。 别指望反向代理转发音视频。HTTP/3/QUIC(第07章)同理也是 UDP,代理方式和 TCP 完全不同。

大文件上传/下载:缓冲、体积上限与背压

大文件踩三个坑,全和前面的机制呼应:

  1. 请求体缓冲(上传):nginx 默认会把请求体缓冲完才转发给后端(proxy_request_buffering on),大上传 → 占内存/落临时文件/增延迟。流式上传要 proxy_request_buffering off,但关了就没法重试(body 已流走)——权衡。
  2. 体积上限(每跳,木桶):nginx 默认 client_max_body_size 1m,超了 413 Request Entity Too Large。每跳都要放开:
    client_max_body_size 0;     # 0 = 不限(边缘)
    
    nginx.ingress.kubernetes.io/proxy-body-size: "0"   # ingress 跳
    
    漏一跳就 413——又是木桶效应。
  3. 背压(下载,第34章):慢客户端下大文件,代理必须流式 + 背压,否则内存爆;下载侧配合 sendfile/零拷贝(第12章)。

双向流:半关闭 + 双向背压同时上

gRPC 双向流、或任何全双工长连接,两个方向同时活跃——第35章 的半关闭(一个方向先结束要 CloseWrite 而非 close)和 第34章 的双向独立背压(每个方向各自一套流控)在这里同时成为必须。玩具代理在双向流下最容易暴露 bug。


️ 实现 / 验证

gRPC 经 ingress-nginx

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: grpc-svc
  annotations:
    nginx.ingress.kubernetes.io/backend-protocol: "GRPC"   # 关键
spec:
  ingressClassName: nginx
  tls: [{ hosts: [grpc.example.com], secretName: grpc-tls }]   # gRPC 基本要 TLS(h2)
  rules:
  - host: grpc.example.com
    http: { paths: [{ path: /, pathType: Prefix, backend: { service: { name: grpc-backend, port: { number: 50051 } } } }] }
grpcurl grpc.example.com:443 list                 # 通 = 端到端 h2+trailers OK
# 失败常见: "unexpected HTTP status" = 某跳把 h2 降级了

大文件上传放开每跳上限

# 故意传 100MB,验证没被 413 截
curl -X POST --data-binary @big-100mb.bin https://app.example.com/upload -w "%{http_code}\n"
# 413 = 某跳 client_max_body_size/proxy-body-size 没放开

WebRTC 信令 + TURN 媒体

# 信令是 WebSocket,按第40章 WS 方式代理验证(101)
# TURN 媒体:用 coturn,部署为 hostNetwork/LoadBalancer(UDP),不走 ingress
turnutils_uclient -v -t -u user -w pass turn.example.com    # 测 TURN 中继可用性

排错

现象根因解决
gRPC "unexpected HTTP status/RST_STREAM"某跳 h2 降级 h1 / 没转 trailers每跳 grpc_pass/backend-protocol: GRPC,用 Envoy
gRPC 负载严重不均L4 连接级焊死长 h2 连接L7 请求级 LB / headless 客户端 LB
浏览器调 gRPC 报 trailers 缺失没有 gRPC-Web 翻译跳加 Envoy grpc_web / grpcwebproxy
长轮询请求频繁 504挂起请求被某跳超时掐每跳长 read 超时 + 关缓冲
上传大文件 413某跳 body size 限制每跳放开 client_max_body_size/proxy-body-size
上传大文件代理内存高/慢请求体被缓冲proxy_request_buffering off(牺牲重试)
WebRTC 能连但无音视频媒体 UDP/TURN 没通部署 TURN(coturn),放行 UDP 端口范围
双向流一方向卡死/截断半关闭或单向背压处理错CloseWrite 传 FIN + 双向独立背压(第34/35章)

本章小结

  • gRPC 要端到端 h2 + trailers(grpc_pass/backend-protocol: GRPC/Envoy),负载不均要 L7/客户端 LB;浏览器侧必须有 gRPC-Web 翻译跳。
  • 长轮询是伪装成 HTTP 的长连接:长超时、不缓冲、吃 fd。
  • WebRTC 信令走 WS(经 L7 链)、媒体走 UDP 经 TURN 中继(不走反向代理);QUIC/HTTP3 同属 UDP 范畴。
  • 大文件:每跳放开体积上限(木桶)、按需关请求缓冲、下载靠流式+背压+零拷贝;双向流让半关闭与双向背压同时成为必须。

下一章 第42章 可落地完整参考实现,把第 39-41 章全部落成一套能 kubectl apply 的 demo 栈:两级 nginx + Ingress + code-server + SSE + SSH + gRPC,含逐协议验证清单。

Prev
第40章 端到端实战:把 6 类流量全代理通
Next
第42章 可落地完整参考实现:一套能跑的多协议转发栈