📘 第六章 HTTP首部
📄 一句话概括

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 秒实验

  1. 把服务端的阈值从 100 改成 10,重跑,看 20 字节那组会不会被拒。
  2. 把客户端改成「不等 100 直接发正文」,看服务端还能不能正常处理。
  3. 想一想:如果中间代理不认 Expect,它会怎么处理?客户端会看到什么?

常见误区

  1. 「100 是最终响应」——它是临时的 1xx,后面还会有真正的响应。
  2. 「客户端必须等 100」——等不到(超时)就该直接发正文。
  3. 「417 是客户端的错」——它表示服务端不接受这个期望,客户端换个方式重试即可。
自测三问(点开对答案) 1. `Expect: 100-continue` 解决什么问题? —— 在发送大正文之前先确认服务端愿不愿意收,避免白传。 2. 服务端不想收时回什么? —— `417 Expectation Failed`,并且可以直接关闭连接。 3. 为什么客户端不能死等 100? —— 很多服务端和代理解不支持这个字段,不会回 100,死等会一直卡到超时。

省流量的说完了。下一个字段是个几乎没人用的遗留物。

相关笔记