Expect: 100-continue 是「正文我还没发,先问一句你要不要」。
服务端回 100 才继续发,回别的就省下了整段正文。
上一节是身份,这一节是「省流量」。 它靠一个临时状态码 100 完成,是 HTTP 里少数需要三次交互的流程。
一、它解决的是一个很具体的浪费
上传 1 GB 的文件,如果服务端一看首部就知道不行(没登录、磁盘满了、格式不对),
客户端没必要先把 1 GB 传完再收到 401。
Expect: 100-continue 的流程是:客户端先只发首部,
服务端判断后回 100 Continue(继续发)或者一个终态状态码(别发了)。
注意 100 是临时响应,1xx 这一整类都是临时的:它不结束这次请求,后面还会有真正的最终响应。
二、实测的两条路
小正文(20 字节):客户端只发首部,先收到 HTTP/1.1 100 Continue,再发正文,
最后收到 200 和正文回显。一共三次交互。
大正文(200 字节):服务端一看超过阈值,直接回 417 Expectation Failed,正文一个字节都没发。
客户端省下了 200 字节——换成 1 GB 就是 1 GB。
注意 417 这个码专门就是干这个的:我不接受你的这个期望。
它几乎只在这个场景出现,所以很多人没见过。
三、客户端不能死等 100
规范明确要求:客户端发出 Expect 之后要设一个短超时(常见实现是 1 秒左右),
等不到 100 就直接把正文发出去。
为什么?因为很多老服务端和中间代理压根不认这个字段,它不会回 100,而是在收完正文后直接回 200。
客户端如果死等,就会卡在那里直到连接超时。
这个「等不到就发」的补偿逻辑,是这条机制能安全推广的关键。凡是「协商」,都得考虑对端不配合的情况。
四、动手 Demo:先发首部,等 100 再发正文
服务端规定:正文超过 100 字节就直接回 417,否则回 100 并接收。 客户端先只发首部,读到一个响应后再决定发不发正文。
head, _, _ = raw.partition(b'\r\n\r\n')
s.sendall(head + b'\r\n\r\n') # 先只发首部
first, pending = H.recv_response(s)
st1, _h1, _b1 = H.split_response(first)
print(u'先收到', st1) # 应该是 100 Continue
s.sendall(body) # 这才发正文
second, pending = H.recv_response(s, pending)
st2, _h2, b2 = H.split_response(second)
print(u'再收到', st2) # 200 OK
实跑输出:
--------------------------------------------------------------------
Expect: 100-continue:正文还没发,先问一句
--------------------------------------------------------------------
第一种:小正文(20 字节),带 Expect: 100-continue
先收到 HTTP/1.1 100 Continue
再收到 HTTP/1.1 200 OK
正文回显 b'01234567890123456789'
第二种:大正文(200 字节),服务端直接拒
服务端 HTTP/1.1 417 Expectation Failed
正文 too big
客户端省下的 200 字节正文一个都没发
服务端收到的请求数 2 个
第 1 个 POST /upload HTTP/1.1 | Expect=100-continue | 已读到正文 0 字节
第 2 个 POST /upload HTTP/1.1 | Expect=100-continue | 已读到正文 0 字节
这个机制解决一个实际问题:上传 1 GB 的文件,
如果服务端一看就不认(鉴权失败、磁盘满了),
客户端没必要先把 1 GB 传完再收到 401。
先发首部问一句,服务端回 100 才继续,回别的就省了。
注意 100 是个**临时响应**:客户端可能收到它,也可能收不到(服务端直接回终态)。
所以客户端不能死等 100,得有超时——等不到就直接发,这是规范要求的。
小正文那组:先收到 HTTP/1.1 100 Continue,再收到 HTTP/1.1 200 OK,
正文回显 01234567890123456789。两次响应,一次请求。
大正文那组:直接收到 417 Expectation Failed,正文 too big。客户端那 200 字节一个都没发出去。
最后那张表统计了服务端收到的两个请求,
两个都显示「已读到正文 0 字节」——说明客户端在收到 100 之前确实一个字节正文都没发。
这个细节是这条机制的全部价值所在:在花掉带宽之前先确认。
30 秒实验
- 把服务端的阈值从 100 改成 10,重跑,看 20 字节那组会不会被拒。
- 把客户端改成「不等 100 直接发正文」,看服务端还能不能正常处理。
- 想一想:如果中间代理不认 Expect,它会怎么处理?客户端会看到什么?
常见误区
- 「100 是最终响应」——它是临时的 1xx,后面还会有真正的响应。
- 「客户端必须等 100」——等不到(超时)就该直接发正文。
- 「417 是客户端的错」——它表示服务端不接受这个期望,客户端换个方式重试即可。
自测三问(点开对答案)
1. `Expect: 100-continue` 解决什么问题? —— 在发送大正文之前先确认服务端愿不愿意收,避免白传。 2. 服务端不想收时回什么? —— `417 Expectation Failed`,并且可以直接关闭连接。 3. 为什么客户端不能死等 100? —— 很多服务端和代理解不支持这个字段,不会回 100,死等会一直卡到超时。省流量的说完了。下一个字段是个几乎没人用的遗留物。
- ☐ (本节完,下一节 6.4.7 From)