🌐 第四章 Web网站的运行原理
📄 一句话概括

代理服务器就是"替你去取"的那台中间机器:你的请求不直接发给 Web 服务器,而是先发给代理,由它代你去取、再把结果给你。它的特殊之处在于站在应用层——它看得懂 HTTP,所以能做的事远不止"转发":缓存省带宽、统一出口与管控、隐藏客户端、负载均衡、SSL 卸载、挡住攻击。它有两个方向:正向代理替客户端出面(服务器不知道你是谁,要你自己配)、反向代理替服务器出面(客户端根本不知道它存在,网站自己装的)。记住一句话就不会搞混:判断方向不看名字,看它替谁出面、藏起来的是谁。

📝 本节和前面几节的关系

本节把 4.4 讲 URL、4.5 讲 HTTP、4.6 讲缓存、4.7 讲 Cookie 这四笔账串起来用一次:

  • 发给代理时,请求行里的 URL 会变成完整的(4.4 那条"域名不发送、只在 Host 头里"的规矩,在这里出现了例外)
  • 代理自己也能缓存,所以 4.6 那些缓存头不只给浏览器看
  • 代理看得见 Cookie(4.7 那张纸条),这既是它的能力,也是它的风险

一、代理是什么

代理(Proxy)= 替别人办事的中间人。 网络里的代理服务器,就是站在你和目标服务器之间、替你去取数据的那台机器。

平时你的请求是这样的:

浏览器 ──────────────────→ Web 服务器

配了代理之后:

浏览器 ──→ [代理服务器] ──→ Web 服务器
              ↑ 它替你去问
📝 代理最关键的一个定位:它站在应用层

收 1.6 讲过分层。对照着看就很清楚:

路由器 NAT 代理服务器
在哪一层 网络层 网络层(改地址) 应用层
干什么 按 IP 找路 换掉地址让他能回来 代表你重新发一次请求
看得懂内容吗 看不懂 看不懂 完全看得懂(HTTP 头、URL、Cookie 全在它眼前)
典型位置 网络出口 网络出口 客户端一侧 / 服务器一侧

一句话分清:NAT 换的是"地址",代理换的是"谁在问"。 NAT 是"在门口换个门牌号"(收 3.14),代理是"我替你去跑一趟"。

正因为看得懂内容,代理才能做缓存、过滤、改写、统计——这些路由器一件都做不了。

二、为什么要用代理

代理的价值可以归成六条,前三条是"客户端这一侧"的理由,后三条是"服务器这一侧"的理由:

理由 谁受益 说明
省带宽、加速 整个局域网 单位里 50 个人都要看同一个页面,代理取一次,50 个人共用(缓存,收 4.6)
统一出口与管控 公司 / 学校 谁能上网、能上哪些站、什么时段能上,全在代理这里裁
隐藏客户端 客户端自己 外面只看到代理的 IP(收 3.13 全局地址与私有地址)
负载均衡 服务器 把请求分给背后十台机器
SSL 卸载 服务器 加密解密这件累活交给代理,后面的机器只处理明文
挡住攻击 / 加速分发 服务器 提前挡掉恶意流量;CDN 就是反向代理的规模化

顺带一提,你其实早就用过代理:抓包调试工具(Fiddler、Charles)本质就是"跑在你本机上的一个正向代理"——流量先经过它,它才有机会给你看。

三、正向代理与反向代理——本节最容易搞混的一处

(配图一)

名字只差一个字,站的位置却完全相反。判断方法只一条:看它替谁出面。

正向代理(Forward Proxy)——替客户端出面

反向代理(Reverse Proxy)——替服务器出面

对照表

正向代理 反向代理
站在哪一侧 客户端这一侧 服务器这一侧
替谁出面 客户端 服务器
藏起来的是 客户端(服务器不知道你是谁) 服务器(客户端不知道背后有几台)
谁来配置 你要主动配(浏览器 / 系统 / 环境变量) 网站自己装,你无感
常见用途 统一出口与管控、缓存加速、抓包调试 负载均衡、SSL 卸载、CDN、防攻击
💡 一个反直觉但极常见的事实

你平时上的那些大网站,前面基本都站着反向代理(nginx、HAProxy、云厂商的负载均衡器)。

你请求 www.example.com,实际先到那台反向代理,由它决定这一趟交给后面哪一台服务器——而这些你完全看不到。收 1.7 讲"加机器就能扛更多访问"时,前面那道门就是它。

四、请求穿过代理时,报文变了什么

(配图二)

这是本节最值得亲手验证的一点。

平常的请求行:只有路径

直连时,请求行里只写路径,域名放在 Host 头里(收 4.4 第七节):

GET /index.html HTTP/1.1
Host: shop.example.com

发给代理的请求行:URL 是完整的

发给代理时不一样:

GET http://shop.example.com/index.html HTTP/1.1
Host: shop.example.com

为什么? 因为一台代理要替很多个目标服务。它必须从请求行本身就能看出"这一趟要去哪儿"——不能只靠 Host 头(同一条连接上可能连续问好几个不同的站)。所以在代理这条路上,URL 必须是完整的(这种写法叫 absolute-form)。

代理转发出去时:又把域名取下来

代理从完整 URL 里取出主机名 → 用 DNS 查出 IP(收 3.21)→ 建立一条属于它自己的连接 → 转发出去的请求行又变回只有路径:

GET /index.html HTTP/1.1
Host: shop.example.com

代理会加上的几个头

头 作用
Via 这趟请求经过过哪些代理
X-Forwarded-For 原始客户端的 IP(反向代理必加)
X-Forwarded-Proto 客户端原本用的是 http 还是 https
X-Real-IP 同 X-Forwarded-For,只放最后一个地址
📝 这几个头正是"反向代理之后,服务器怎么知道真实来访者"的答案

反向代理之后,服务器看到的连接全部来自代理。想知道"真正是谁在访问",只能靠代理主动加上 X-Forwarded-For。

但它不可全信:客户端自己也带这个头来伪造,代理只是往后追加。所以规范做法是只信最靠近自己的那一台代理写入的值。

五、代理和缓存(收 4.6 那笔账)

代理本身也是一台"有资格缓存的中间人"。别人刚取过的东西,第二次它就直接给你,连目标服务器都不打扰(配图二那条绿色回环)。

这就解释了一件事:4.6 讲的 Cache-Control 不只给浏览器看,也给中间人看。

指令 意思
Cache-Control: public 谁都能存(浏览器、代理、CDN 都可以)
Cache-Control: private 只有浏览器能存,中间人一律不许存
Cache-Control: no-store 谁都别存,一次性的
Cache-Control: s-maxage=600 专门给共享缓存(也就是代理)规定的寿命
🚨 一个真实事故的机理:动态页面不能被共享缓存

如果登录后的页面(带你的名字、购物车)被代理缓存了,下一个访问同一个地址的人就会看到你的内容——这就是"缓存污染"事故。

所以那些"因人而异"的响应,服务器一定会写 Cache-Control: private 或 no-store。这也是 4.7 的会话为什么要靠 Cookie 而不能靠缓存。

六、HTTPS 时代的代理:隧道

HTTP 是明文,代理什么都看得见。HTTPS 呢?

浏览器不会明文告诉代理"我要取哪个页面的内容",而是先发一条:

CONNECT shop.example.com:443 HTTP/1.1

这叫作建立隧道(tunnel)。之后双方只是原样转发字节,加密内容代理看不到。

但要注意它仍然知道:

⚠️ 三个和代理有关的安全现实
  1. 别随便用陌生的代理:HTTP 明文下,你的 URL、Cookie(4.7 那张纸条)、表单内容全在它眼前。免费代理是最便宜的窃取手段。
  2. 公司的代理可能看得见 https 内容:如果单位给你的电脑装了自签根证书,代理就能"中间拆开看、再重新加密"。所以公司设备上的 https 并不等于安全。
  3. Secure 标记的 Cookie 仍然要带走 HTTPS(收 4.7):隧道能保护内容,但域名和去向始终暴露。

七、动手 Demo:亲手做一个代理,看它眼里的世界

第 1 步 · 先看本机现在有没有配代理

netsh winhttp show proxy

(Windows 上还有一处:设置 → 网络和 Internet → 代理。两个地方是分开的,别弄混。)

第 2 步 · 写一个"会说话"的极简代理

它不真的转发,只把代理看到的第一行原样显示出来——这一下正是本节的重点。

新建 mini_proxy.py:

import socket, threading

def handle(conn):
    data = conn.recv(65535)
    if not data:
        conn.close()
        return
    line = data.split(b"\r\n")[0].decode("utf-8", "ignore")
    print("代理看到的第一行:", line)
    parts = line.split()
    if len(parts) < 2:
        conn.close()
        return
    url = parts[1]
    host = url.split("/")[2] if url.count("/") > 2 else url
    path = "/" + url.split("/", 3)[3] if url.count("/") > 2 else "/"
    body = ("我是代理服务器。\n\n"
            "你发来的完整 URL 是:\n" + url + "\n\n"
            "我替你拆成两部分:\n"
            "  主机名 = " + host + "\n"
            "  路径   = " + path + "\n")
    raw = body.encode("utf-8")
    head = ("HTTP/1.1 200 OK\r\n"
            "Content-Type: text/plain; charset=utf-8\r\n"
            "Content-Length: " + str(len(raw)) + "\r\n\r\n")
    conn.sendall(head.encode() + raw)
    conn.close()

s = socket.socket()
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(("127.0.0.1", 8888))
s.listen(5)
print("代理已在 127.0.0.1:8888 等你(Ctrl+C 退出)")
while True:
    conn, _ = s.accept()
    threading.Thread(target=handle, args=(conn,), daemon=True).start()

第 3 步 · 起起来

python mini_proxy.py

第 4 步 · 用 curl 走一趟代理(最安全的验证方式)

curl -x http://127.0.0.1:8888 http://example.com

你会看到返回的内容变成了那段"我是代理服务器……",同时脚本窗口打印出一行:

代理看到的第一行: GET http://example.com/ HTTP/1.1

这就是本节的核心现场:发给代理的请求行里,URL 是完整的。(对比一下:平时直连时那一行只有 /。)

第 5 步 · 对比直连和过代理两种请求行

curl -v http://example.com/                             # 直连:看 > GET / HTTP/1.1
curl -x http://127.0.0.1:8888 -v http://example.com/    # 过代理:看 > GET http://example.com/ HTTP/1.1

把两行并排看一眼,4.4 第七节的规矩和它的例外就同时在眼前了。

第 6 步 · 让浏览器也走一次(做完记得改回来)

Windows:设置 → 网络和 Internet → 代理 → 手动设置代理 → 地址填 127.0.0.1,端口填 8888 → 保存。

然后浏览器打开 http://example.com(必须是 http,不是 https)——页面会变成代理给你写的那段话,同时脚本窗口又打印一行。

⚠️ 做完立刻关掉代理设置

不关的话,你这台机器上所有浏览器流量都会往那个已经关掉的端口发,表现为"上不了网"。

命令行里那条 curl -x 更安全——只影响这一次命令,所以推荐先用它。

八、30 秒实验

  1. 30 秒:netsh winhttp show proxy —— 看一眼你这台机器现在有没有被谁"代着走"。
  2. 30 秒:curl -x http://127.0.0.1:8888 -v http://example.com —— 在输出里找 > GET http://…(完整 URL 那一行)。
  3. 30 秒:curl -v http://example.com 同样找 > 那一行 —— 两行一比,就是"直连 vs 过代理"的全部差别。

九、常见误区

  1. "代理和 VPN 是一回事。" —— 层次不同:代理通常只管某一种协议的流量(HTTP 代理只管网页),VPN 在网络层把全部流量接管(收 3.14 的隧道是另一回事)。
  2. "用了代理就匿名了。" —— 只是把 IP 换成了代理的。你的 Cookie(收 4.7)、浏览器特征、登录状态照样带着。
  3. "反向代理也要用户自己配。" —— 完全不用、也感觉不到。它是网站自己装在入口的,用户访问的还是同一个域名。
  4. "代理看不懂 HTTPS,所以用 HTTPS 就绝对安全。" —— 内容它看不到,但域名、流量大小、访问时间它都知道;而且公司装了根证书的代理能拆开看。
  5. "代理和 NAT 是一回事。" —— 完全不同层:NAT 在网络层换地址,代理在应用层替你重新发请求(第一节那张表)。
  6. "代理能让所有网站都快。" —— 只有被访问过、又允许被缓存的内容才快(收 4.6)。登录后的页面、no-store 的内容一点都不会快。
  7. "请求行里的 URL 一直就是只有路径。" —— 直连时是;发给代理时是完整的(本节第四节)。
  8. "X-Forwarded-For 可以当作真实 IP 用。" —— 客户端可以伪造它,只有最靠近你的那台代理写入的值才可信。
  9. "代理服务器只是转发,出不了大事。" —— 它是单点:它挂了整个局域网都上不了网;它也是隐私汇聚点:所有人的上网记录都经过它。
  10. "免费代理只是慢一点,没别的坏处。" —— HTTP 明文下,它能看到你所有的 URL、Cookie 和表单内容。这是最廉价的一种窃取手段。

十、动手自测

❓ 自测一下
  1. 代理服务器站在哪一层?这一层给了它什么路由器没有的能力?
  2. 正向代理和反向代理怎么区分?各自藏起来的是谁?
  3. 反向代理用户需要配置吗?为什么?
  4. 平时直连时请求行里写什么?发给代理时又写什么?为什么会有这个差别?
  5. 代理转发给目标服务器时,会把请求行改回什么样?
  6. X-Forwarded-For 是干什么的?它可信吗?
  7. Cache-Control: private 和 public 对代理分别意味着什么?为什么登录后的页面绝不能被代理缓存?
  8. HTTPS 穿过代理时,代理还能知道什么、不能知道什么?
  9. 代理和 NAT 的本质差别是什么?
  10. 为什么说"免费代理"是最廉价的窃取手段?

答案: ① 站在应用层。它看得懂 HTTP(URL、头部、Cookie 全在眼前),所以能做缓存、过滤、改写、统计——这些网络层的路由器一样都做不了。② 看替谁出面:正向代理替客户端出面、藏起客户端(服务器不知道真正来访者是谁);反向代理替服务器出面、藏起服务器(客户端不知道背后有几台机器)。③ 不需要。反向代理是网站自己装在入口的,用户访问的还是那个域名,感觉不到它存在。④ 直连时只写路径(GET /index.html HTTP/1.1,域名在 Host 头里,收 4.4);发给代理时写完整 URL(GET http://shop.example.com/index.html HTTP/1.1)。因为一台代理要替很多目标服务,必须从请求行就能看出这趟要去哪儿。⑤ 代理从完整 URL 里取出主机名(再用 DNS 查 IP),转发出去的请求行又变回只有路径,Host 头照旧。⑥ 它记录原始客户端的 IP(反向代理之后服务器看到的全是代理,只能靠它知道真实来访者)。不可全信——客户端能自己伪造,只有最靠近你的那台代理写入的值才信。⑦ public = 谁都能存(浏览器、代理、CDN);private = 只有浏览器能存,中间人一律不许。登录后的页面因人而异,一旦被共享缓存存下,下一个人就会看到你的内容(缓存污染事故),所以必须 private 或 no-store。⑧ 能知道:你访问的域名(CONNECT 那行里写着)、访问时间、流量大小;不能知道:页面内容(隧道里是加密的)——除非中间装了根证书做拆解。⑨ NAT 在网络层换地址("在门口换个门牌号");代理在应用层替你重新发一次请求("我替你去跑一趟")。⑩ 因为 HTTP 是明文,代理能看到你所有的 URL、Cookie 和表单内容——Cookie 往往就是登录凭证(收 4.7),拿到它等于拿到你的登录状态。

十一、下一步学什么

相关笔记