网关是能把 HTTP 翻译成另一种协议的中介。代理只搬字节,网关要听懂两边的语法。
上一节代理的活儿是转发。有些场景光转发不够——后头那个服务压根不会说 HTTP。这时候需要网关。
一、一句话分清代理和网关
代理转发的还是 HTTP,进去 HTTP、出来 HTTP,只是换了个地址; 网关要在两个协议之间做翻译,进去 HTTP、出来可能是任何东西。 所以网关必须同时理解两边的语法:知道 HTTP 的路径对应后端的哪个调用, 知道后端的错误码该映射成哪个 HTTP 状态码。 书里有个说法值得记住:利用网关可以提高通信的安全性,因为可以在客户端与网关之间加密, 把内网的协议和拓扑藏在网关后面。
二、几种常见的网关
| 名字 | 翻译的是 | 例子 |
|---|---|---|
| 数据库网关 | HTTP ↔ 数据库协议 | 把 SQL 查询包装成 REST 接口 |
| 支付网关 | HTTP ↔ 银行内部协议 | 对接银联、第三方支付 |
| API 网关 | HTTP ↔ 内部多个微服务 | 鉴权、限流、聚合、协议转换 |
| SSL 网关 | HTTPS ↔ HTTP | 在网关处解密,内网走明文 |
| 协议网关 | HTTP ↔ WebSocket 等 | 长连接服务对外只暴露 HTTP 入口 |
| 表格里最后一行「SSL 网关」就是书里说的「由 HTTP 服务器等负责安全性的网关」。 | ||
| 今天它有个更常见的名字:TLS 卸载。 |
三、网关的代价
翻译不是免费的。Demo 里同一个查询,走 HTTP 是 42 字节,走自定义二进制只有 6 字节,体积比 7 倍。
但这个比较的结论不能反过来读——那 36 字节换来的,是任何工具都能读懂、任何语言都有库、
任何人都能调试。
真正的代价在别处:多一跳延迟、多一个故障点、多一层需要对账的错误语义映射。
错误映射最容易被做错。
后端超时该回 504 Gateway Timeout,后端连不上该回 502 Bad Gateway,而不是统统回 500。
统一回 500 会让调用方完全无法区分「我的请求有问题」和「你的后端挂了」。
另外网关天然是单点。它挂了,后面所有服务都不可达。所以生产上的网关都是多实例加健康检查。
四、动手 Demo:HTTP 与自定义二进制之间的往返翻译
后端服务只会说一种五字节的二进制协议:0x01 加一字节 key 长度加 key 加 0x02,
回 0x81 加四字节大端整数。网关负责把 GET /kv/cpu 翻成它,再把结果翻回 HTTP 正文。
def gateway(raw, st):
first, hdrs, _b = H.parse_headers(raw)
path = first.split(u' ')[1]
key = path.rsplit(u'/', 1)[-1].encode(u'utf-8')
# 翻译:HTTP 路径 -> 二进制 key
wire = b'\x01' + bytes([len(key)]) + key + b'\x02'
s = socket.create_connection((u'127.0.0.1', b_port))
s.sendall(wire)
back = s.recv(5)
val = struct.unpack(u'>I', back[1:5])[0]
# 翻回来:二进制整数 -> HTTP 正文
return reply(u'HTTP/1.1 200 OK', [(u'X-Gateway', u'http-to-bin')],
(u'%s = %d' % (key, val)).encode(u'utf-8'))
实跑输出:
--------------------------------------------------------------------
网关:把 HTTP 请求翻译成自定义二进制协议再翻回来
--------------------------------------------------------------------
客户端只会说 HTTP,后端只会说二进制,中间夹一个网关
后端协议:0x01 + 1 字节 key 长度 + key + 0x02 → 0x81 + 4 字节大端整数
后端服务端口 127.0.0.1:57328
网关端口 127.0.0.1:57329
GET /kv/cpu HTTP/1.1 200 OK | 正文 cpu = 37
GET /kv/mem HTTP/1.1 200 OK | 正文 mem = 62
GET /kv/disk HTTP/1.1 200 OK | 正文 disk = 88
GET /kv/gpu HTTP/1.1 200 OK | 正文 gpu = 0
网关实际跟后端交换的字节(客户端完全看不见):
请求方向 6 字节
文本 \x01\x03cpu\x02
十六 01 03 63 70 75 02
响应方向 5 字节
文本 \x81\x00\x00\x00%
十六 81 00 00 00 25
同一个查询走 HTTP 42 字节
同一个查询走二进制 6 字节
体积比(HTTP/二进制) 7.0 倍
网关翻译次数 4 次
代理不改协议,只搬字节;网关要认识两边的语法,做的是翻译。
数据库网关、支付网关、API 网关,名字里的「网关」都是这个意思。
四个查询全部成功:cpu = 37、mem = 62、disk = 88、gpu = 0。
最后一个 0 是因为后端表里没有 gpu 这个 key,
落到了默认值——网关要负责把这种「查到了吗」的差异体现出来,而不是一律回 200 让调用方猜。
网关实际跟后端交换的字节:请求 6 字节 01 03 63 70 75 02,响应 5 字节 81 00 00 00 25。
0x25 就是 37。客户端从头到尾不知道后端说的是什么语言。
同一个查询走 HTTP 42 字节、走二进制 6 字节、
体积比 7.0 倍——这 36 字节的差价就是 HTTP 的可读性和通用性。
30 秒实验
- 给后端加一个会返回异常的分支,让网关把它映射成
502,再看客户端能不能区分。 - 把 key 改成一个不存在的、很长的字符串,看 1 字节长度字段会怎么溢出。
- 想一想:如果你负责的网关把后端的所有错误都回 500,排障时你会缺哪些信息?
常见误区
- 「网关就是代理的另一个叫法」——代理转发 HTTP,网关翻译协议。
- 「网关可以让后端更快」——它通常更慢,多一跳。它的价值是解耦和统一入口。
- 「后端出错统一回 500 就行」——502 和 504 的区别对调用方很重要。
自测三问(点开对答案)
1. 网关和代理最本质的区别是什么? —— 代理在同一个协议里搬字节;网关要在两种不同协议之间做翻译,得认识两边的语法。 2. 后端超时和后端连不上,分别该回什么状态码? —— 超时回 504 Gateway Timeout,连不上回 502 Bad Gateway。 3. 为什么说网关能提升安全性? —— 它可以把内网的协议和拓扑藏在后面,只在客户端与网关之间加密。网关要翻译,代理要转发,还有一种中介两样都不干——它只负责把字节原样搬来搬去。
- ☐ (本节完,下一节 5.2.3 隧道)