Upgrade 是「我想在这条连接上换个协议」的提议。
服务端同意就回 101,从此这条 TCP 连接上跑的不再是 HTTP。
上一节讲怎么传字节,这一节讲怎么不传 HTTP 了。WebSocket 的握手就是这个词的经典用法。
一、协商只有三步
第一步:客户端在请求里带 Connection: Upgrade 和 Upgrade: h2c(要换的协议名)。
注意两个都要带——Connection: Upgrade 是把 Upgrade 标记成逐跳字段,少了它,
中间的代理会把这个提议转给下一跳,那就问错人了。
第二步:服务端同意就回 101 Switching Protocols,并在自己的 Upgrade 里说明换成了哪个协议。
不同意就照常回 200,客户端得认命。
第三步:101 之后,这条连接上跑的字节就不再遵循 HTTP 格式了。
双方按新协议的规矩来,谁都不许再发 HTTP 报文。
这三步里最容易漏的是第一步的两个首部。
只发 Upgrade 不发 Connection: Upgrade,
中间有代理时提议会被转发到源服务器——而源服务器不在那一跳上,它没资格答应。
二、实测:发 hello 收 HELLO
Demo 里的服务端只认 h2c。不发 Upgrade 时它照常回 200;
发了 Upgrade: h2c 加 Connection: Upgrade,它回 101。
101 之后,客户端在管子里发一个自己定的假协议:一个字节的长度加内容。
服务端收到 hello,回一个长度字节加大写化的内容。客户端收到 HELLO。
这一段最能说明问题:101 之后双方交换的东西,HTTP 的解析器完全看不懂。它不再是 HTTP 了。
三、为什么今天改用 ALPN
Upgrade 只能在「连接已经建立、已经发过一个 HTTP 请求」之后才能协商。
也就是说明文 HTTP/2(h2c)必须先走一个 1.1 请求,多一个往返。
HTTPS 那边有更好的办法:TLS 握手时就能用 ALPN 扩展说清「我支持 h2 和 http/1.1」,服务端当场选定。
省掉了那次额外的往返。
所以现实是:加密的连接用 ALPN,明文的 h2c 基本没人用——中间的代理大多不支持它,
而且明文本来也不该用在公网上。
WebSocket 是另一条路:它确实靠 Upgrade: websocket 起手,但那是应用层协议自己的约定,
跟 HTTP/2 的协商不是一回事。
四、动手 Demo:从 200 到 101,再跑一段非 HTTP 的字节
起一个只认 h2c 的服务端。先不带 Upgrade 请求一次,看它回 200;
再带 Upgrade 请求一次,拿到 101,然后在管子里发一个自定义协议的字节,看服务端回什么。
if hd.get(u'upgrade', u'').lower() == u'h2c':
self.request.sendall(
b'HTTP/1.1 101 Switching Protocols\r\n'
b'Upgrade: h2c\r\n'
b'Connection: Upgrade\r\n\r\n')
while True:
lb = self.request.recv(1)
n = lb[0]
payload = read_n(self.request, n)
self.request.sendall(b'\x01' + bytes([n]) + payload.upper())
else:
self.request.sendall(b'HTTP/1.1 200 OK\r\nContent-Length: 2\r\nConnection: close\r\n\r\nno')
实跑输出:
--------------------------------------------------------------------
Upgrade 与 101:把 HTTP 连接改成别的协议
--------------------------------------------------------------------
先不发 Upgrade,看服务端怎么回:
响应 HTTP/1.1 200 OK
服务端收到的 Upgrade (空)
再发 Upgrade: h2c,走协商:
响应 HTTP/1.1 101 Switching Protocols
Upgrade
Connection
101 之后,这条连接上不再是 HTTP
文本 \x01\x05HELLO
十六 01 05 48 45 4c 4c 4f
发出去的 b'hello'
收回来的 b'HELLO'
说明 服务端在新协议里把内容转成了大写
两次协商 Service 收到 2 次 Upgrade 首部
Upgrade 是「我想换协议」的提议,服务端同意就回 101,
并用自己的 Upgrade 首部说明换成什么。101 之后,
这条 TCP 连接上跑的就不再是 HTTP 了。
历史上 WebSocket 就是这么起来的;今天 HTTP/2 主要走 TLS 上的 ALPN 协商,
h2c(明文 HTTP/2)这条路实际很少用——中间的代理基本都不支持。
第一次不带 Upgrade:服务端回 HTTP/1.1 200 OK,服务端收到的 Upgrade 是空的。
协商没发起,就没得谈。
第二次带 Connection: Upgrade 和 Upgrade: h2c:服务端回 HTTP/1.1 101 Switching Protocols,
并带上 Upgrade 和 Connection 两个首部。
101 之后客户端发 hello,收回来的是 HELLO。这五个字节的往返跟 HTTP 一点关系都没有了。
服务端一共收到两次 Upgrade 首部——一次空,一次 h2c。这就是「协商」的全部:提议、接受、切换。
30 秒实验
- 只发
Upgrade: h2c不发Connection: Upgrade,看服务端还认不认。 - 把服务端改成只认
websocket,重跑,看它是不是照常回 200。 - 想一想:为什么 101 之后不能再发 HTTP 请求?协议上靠什么保证这一点?
常见误区
- 「
Upgrade是让服务端升级版本」——是换协议,不是升级 HTTP 版本号。 - 「101 之后还能发 HTTP」——不能,连接已被新协议接管。
- 「HTTP/2 都靠 Upgrade 协商」——加密的走 ALPN,明文的 h2c 基本没人用。
自测三问(点开对答案)
1. 发起协议切换要带哪两个首部? —— `Connection: Upgrade` 和 `Upgrade: 协议名`。两个都要带。 2. 服务端同意时回什么状态码? —— 101 Switching Protocols,并在自己的 Upgrade 里说明换成了什么。 3. 为什么今天 HTTP/2 不用 Upgrade 协商? —— 因为它要等连接建立后再发一个 1.1 请求,多一个往返;TLS 的 ALPN 能在握手时一次说清。协议切换讲完了。下一个字段是代理链的脚印——第五章已经见过它一次。
- ☐ (本节完,下一节 6.3.8 Via)