📘 第五章 与HTTP协作的Web服务器
📄 一句话概括

网关是能把 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 秒实验

  1. 给后端加一个会返回异常的分支,让网关把它映射成 502,再看客户端能不能区分。
  2. 把 key 改成一个不存在的、很长的字符串,看 1 字节长度字段会怎么溢出。
  3. 想一想:如果你负责的网关把后端的所有错误都回 500,排障时你会缺哪些信息?

常见误区

  1. 「网关就是代理的另一个叫法」——代理转发 HTTP,网关翻译协议。
  2. 「网关可以让后端更快」——它通常更慢,多一跳。它的价值是解耦和统一入口。
  3. 「后端出错统一回 500 就行」——502 和 504 的区别对调用方很重要。
自测三问(点开对答案) 1. 网关和代理最本质的区别是什么? —— 代理在同一个协议里搬字节;网关要在两种不同协议之间做翻译,得认识两边的语法。 2. 后端超时和后端连不上,分别该回什么状态码? —— 超时回 504 Gateway Timeout,连不上回 502 Bad Gateway。 3. 为什么说网关能提升安全性? —— 它可以把内网的协议和拓扑藏在后面,只在客户端与网关之间加密。

网关要翻译,代理要转发,还有一种中介两样都不干——它只负责把字节原样搬来搬去。

相关笔记