正向代理与反向代理:原理、协议与HTTP实现

一、正向代理与反向代理的区别

一句话说清:

正向代理 反向代理
替谁办事 客户端 服务端
隐藏谁 隐藏客户端 隐藏服务端
部署在哪 客户端这一侧 服务端这一侧
客户端知道目标吗 知道 不知道(以为代理就是目标)
典型场景 翻墙、公司内网访问外网 负载均衡、SSL 终止、CDN
1
2
3
4
5
正向代理:
你 → [代理] → 谷歌 谷歌只看到代理 IP,不知道是你

反向代理:
用户 → [Nginx] → 后端服务 用户以为 Nginx 就是服务器,不知道后面还有一堆

二、核心疑问:Host 明明是目标地址,请求怎么跑到代理服务器去了?

这是每个学代理的人都会困惑的问题。来看一个典型的代理请求:

1
2
3
GET http://ccs.kentonly.com/api/v1/contents HTTP/1.1
Host: ccs.kentonly.com
Proxy-Authorization: Basic xxxxx

Host 头里写的是 ccs.kentonly.com,那这个请求凭什么能到达代理服务器?

答案是:Host 头只负责 HTTP 层,不负责 TCP 层。代理地址根本不在 HTTP 报文里。

代理地址在哪配?——不在 HTTP 协议里,在客户端配置里

HTTP 协议本身没有任何字段用来指定代理地址。代理的域名或 IP 是在操作系统、浏览器、或命令行层面配置的:

配置方式 在哪里设
Windows 系统代理 设置 → 网络和 Internet → 代理 → 手动设置代理
macOS 系统代理 系统设置 → 网络 → 高级 → 代理
浏览器代理 Chrome/Edge 设置 → 系统 → 打开计算机的代理设置
curl 命令行 curl -x http://proxy:8080
环境变量 export http_proxy=http://proxy:8080
代码里 axiosrequests 等库的 proxy 参数

这些配置告诉客户端一件事:TCP 连接不要连 Host 头里的域名,改连代理地址。

分两层看就清楚了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
┌─────────────────────────────────────────────────┐
│ 第 1 层:TCP — 谁来决定连哪个 IP? │
│ │
│ 客户端配置了代理 proxy.example.com:8080 │
│ → DNS 解析 proxy.example.com → 拿到代理 IP │
│ → TCP 三次握手 → 和代理 IP:8080 建立连接 │
│ │
│ Host 头在这里完全没有参与! │
└─────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────┐
│ 第 2 层:HTTP — 跑在已经建好的 TCP 连接上 │
│ │
│ GET http://ccs.kentonly.com/api/v1/contents │
│ Host: ccs.kentonly.com │
│ │
│ 代理收到后,从请求行里提取出 ccs.kentonly.com │
│ 代理自己去 DNS 解析 → 连目标服务器 → 转发请求 │
│ 目标服务器返回响应 → 代理原路送回客户端 │
└─────────────────────────────────────────────────┘

curl 看得更直观:

1
2
3
4
5
6
7
8
# 不配代理 — 直连
curl http://ccs.kentonly.com/api
# TCP → 47.238.145.36:80 ← 域名 ccs.kentonly.com 对应的 IP

# 配了代理 — 改道
curl -x http://proxy.example.com:8080 http://ccs.kentonly.com/api
# TCP → proxy.example.com:8080 ← 代理的 IP
# 然后在已建立的 TCP 连接上,发送 HTTP 请求,Host 仍为 ccs.kentonly.com

总结

1
2
不配代理:客户端 DNS 解析 Host 域名 → TCP 直连目标
配了代理:客户端 DNS 解析代理域名 → TCP 连代理 → HTTP 请求里写完整 URL,代理再转发

Host 头自始至终不参与 TCP 建连。 它就是告诉代理”帮我去哪儿”,而代理地址是客户端配置决定的,属于 TCP 层的事。


三、HTTP 协议如何支持代理访问

清楚了”代理地址不在 HTTP 里”之后,再看 HTTP 协议本身怎么支持代理。

明文代理:请求行写完整 URL

1
2
3
4
5
6
7
8
9
10
普通请求(无代理):
GET /api/v1/contents HTTP/1.1
Host: ccs.kentonly.com
↑ 路径是相对的,Host 头告诉服务器用哪个虚拟主机

代理请求:
GET http://ccs.kentonly.com/api/v1/contents HTTP/1.1
Host: ccs.kentonly.com
Proxy-Authorization: Basic xxxxx ← 可选:代理需要认证时用
↑ 路径是完整 URL,代理从中解析目标地址

区别只有请求行的格式——普通请求写 相对路径,代理请求写 绝对 URL。这是 HTTP/1.1 规范(RFC 7230)本身就支持的,不是额外的”代理协议”。

HTTPS 代理:CONNECT 隧道

HTTPS 是加密的,代理不能看内容,所以需要另一种方式——打一条 TCP 隧道:

1
2
3
4
5
6
7
8
9
客户端 → 代理:
CONNECT ccs.kentonly.com:443 HTTP/1.1
Host: ccs.kentonly.com

代理 → 客户端:
HTTP/1.1 200 Connection Established ← 隧道打通!

之后双方在这条隧道里直接传加密的 TLS 数据,
代理只是一个搬运工,完全看不懂内容。

两种模式对比:

模式 GET 完整 URL CONNECT 隧道
适用协议 HTTP(明文) HTTPS(加密)
代理能看内容吗 ✅ 能(可缓存、过滤、改写) ❌ 不能(端到端加密)
代理能缓存吗 ✅ 可以 ❌ 纯转发
HTTP 版本 HTTP/1.0+ HTTP/1.1+

这就是为什么 curl -x http://proxy:8080 https://google.com —— 代理只知道你连了 google.com,但你搜了什么它完全看不见。CONNECT 打隧道,TLS 加密是端到端的。


四、完整流程图解

最后用一个完整的例子串联所有概念——浏览器配了代理后访问 http://ccs.kentonly.com

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
你的电脑                         代理服务器  proxy:8080         目标服务器  47.238.145.36:80
│ │ │
│ 1. 检查系统代理配置 │ │
│ 发现配置了 proxy:8080 │ │
│ │ │
│ 2. DNS 解析 "proxy" │ │
│ → 拿到代理 IP │ │
│ │ │
│ 3. TCP SYN ─────────────────→ │ │
│ ←───────────────── TCP SYN-ACK │ │
│ TCP ACK ───────────────────→ │ 三次握手完成,连接建立 │
│ │ │
│ 4. GET http://ccs.kentonly.com/ │ │
│ Host: ccs.kentonly.com │ │
│ ──────────────────────────────→ │ │
│ │ 5. 从请求行提取 ccs.kentonly.com
│ │ DNS 解析 → 47.238.145.36 │
│ │ TCP SYN ────────────────→│
│ │ ←──────────────────── SYN-ACK│
│ │ TCP ACK ─────────────────→│
│ │ │
│ │ 6. GET / HTTP/1.1 │
│ │ Host: ccs.kentonly.com │
│ │ ────────────────────────────→│
│ │ │
│ │ 7. ←── HTTP/1.1 200 OK ────│
│ │ (index.html) │
│ │ │
│ 8. ←── HTTP/1.1 200 OK ────── │ │
│ (index.html) │ │

关键点:第 4 步发的 HTTP 请求里 Host 是 ccs.kentonly.com,但这个 TCP 包的目标 IP 是代理的 IP(第 3 步决定的),不是 ccs.kentonly.com 的 IP。HTTP 头里的 Host 和 IP 包头的目标 IP 是两码事


总结

  1. 正向代理隐藏客户端,反向代理隐藏服务端——部署位置不同,替谁”瞒天过海”不同
  2. 代理地址不在 HTTP 协议里,在客户端配置里——浏览器设置、系统代理、环境变量、curl -x,这些告诉客户端 TCP 该连谁。HTTP 报文里的 Host 只是告诉代理”最终去哪”
  3. HTTP 原生支持代理——明文用请求行写完整 URL,HTTPS 用 CONNECT 打隧道,都不是额外协议