bigywater.tech
网络协议 · 83

HTTP 代理与 CONNECT 隧道

企业网络、翻墙工具、抓包软件都离不开 HTTP 代理。明文 HTTP 经代理时请求行有什么不同?HTTPS 为什么要 CONNECT?代理到底能看到什么、不能看到什么?代理多花了几个往返?这篇自己写了一个最小代理,在本机把这些问题逐个量出来。

SNI = example.com
CONNECT 隧道里代理能从第一批字节读到的唯一明文信息(除 CONNECT 目标外)
525 → 1161 ms
客户端↔代理 100 ms 往返时,直连与经代理访问 https://example.com/ 的耗时中位数
Basic dXNlcjpwYXNz
代理认证里 base64 编码的凭据(解码即 user:pass)

明文 HTTP

实测 · 本机执行输出
  客户端经代理发出的请求行:GET http://example.com/ HTTP/1.1
  直连时的请求行是:GET / HTTP/1.1(origin-form,只有路径);经代理变成了 absolute-form,因为代理需要知道要连哪台主机
  代理收到的头部名字:['host', 'user-agent', 'accept-encoding', 'accept', 'connection'];代理转发请求时加了 Via: 1.1 w83proxy,去掉了 Proxy-* 和原来的 Connection 头(改成 Connection: close);最终客户端拿到 200,577 字节
  代理能看到什么:完整 URL、全部头部(包括 Cookie / Authorization 等)和整个消息体——明文 HTTP 对代理是完全透明的

请求行用绝对形式带完整 URL;代理看得到一切并可以修改头部。

CONNECT 隧道

实测 · 本机执行输出
  客户端发给代理的第一行:CONNECT example.com:443 HTTP/1.1;代理回复 200 Connection Established;之后客户端 200,收到 577 字节
  代理中转的字节:客户端→服务器 1751 字节,服务器→客户端 6544 字节(都是 TLS 密文)
  代理在转发的第一批字节里看到的 TLS ClientHello 里的 SNI(服务器名,明文)= example.com;它看不到 URL 路径、头部、Cookie 和消息体
  客户端校验的证书是 example.com 自己的(不是代理的):requests 成功且没有证书错误——说明隧道是端到端的 TLS

代理回 200 之后只搬字节;TLS 是端到端的,代理看到 CONNECT 目标和 SNI,看不到 URL 与内容。

额外延迟

实测 · 本机执行输出
  直连:[529, 490, 522, 689] ms,中位 525 ms
  经代理(客户端↔代理 往返 100 ms):[1889, 1114, 1208, 1047] ms,中位 1161 ms
  经代理多出 636 ms(中位数之差)
  直连各阶段(中位 ms):TCP 连到源站 157;TLS 握手 188;HTTP 请求到首字节 375;合计 720
  经代理各阶段(中位 ms):连到代理 101;CONNECT 往返 264;TLS 握手 362;HTTP 请求到首字节 472;合计 1199
  对照:TLS 握手和 HTTP 请求这两个阶段,经代理的耗时比直连多出的部分,就是隧道里多经过的那一段 100 ms 往返(客户端↔代理)

每个原本的往返现在都多穿过一段客户端↔代理;连到代理 1 个 RTT,CONNECT 再 1 个 RTT。

失败与认证

实测 · 本机执行输出
  CONNECT 到被禁止的主机:代理返回 403;客户端 → ProxyError:HTTPSConnectionPool(host='blocked.example', port=443): ProxyError('Unable to connect to pr
  CONNECT 到连不上的目标(127.0.0.1:9):代理返回 502;客户端 → ProxyError:HTTPSConnectionPool(host='127.0.0.1', port=9): ProxyError('Unable to connect to proxy', OS
  代理要求认证:不带凭据 → 状态 407,Proxy-Authenticate = Basic realm="w83"
  带上凭据(Proxy-Authorization: Basic …)→ 状态 200;代理收到的 Proxy-Authorization = Basic dXNlcjpwYXNz(Basic 只是 base64 编码:user:pass,不是加密)

403 / 502 对客户端都是 ProxyError;407 要求 Proxy-Authorization,Basic 只是 base64。

分步演示

代理与 CONNECT
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64(Apple 芯片)/ Python 3.14.8 / tcpdump(libpcap)/ scapy 2.8 / dpkt / dnspython / h2 / aioquic / OpenSSL / curl 8.7.1;抓包在 lo0(环回)上做,少数实验用真实网卡 en0(这台机器上 en0 是 Wi-Fi,tcpdump 看到的是系统转成以太网格式的帧,所以文中的“以太网头”都指这个 14 字节的头)或访问公网(会在文章里注明);需要丢包 / 延迟 / 限速时,用一个只作用于 lo0 端口 9400-9499 的 dummynet 辅助脚本(netlab);所有服务端都是本机自己起的进程;时间戳取自 tcpdump 的 -tt,数字每次运行会有小幅抖动。代理是我写的最小转发代理(线程 + 套接字搬字节,监听 lo0 的 9400~9499 端口),不是任何现成软件;HTTPS 目标是真实的 example.com:443,延迟用 netlab 只作用在客户端↔代理这一段;分阶段计时每种只测 3 次
    • 没有验证:MITM 代理(自签 CA 解密 HTTPS)的行为;HTTP/2 over CONNECT;SOCKS 代理;代理自动配置(PAC / WPAD);HTTPS 代理(客户端到代理本身再加密);TLS 阶段多出 74 ms 的原因;浏览器对代理的行为
    京ICP备2026067502号-1京公网安备11010502063487号