返回首页

HTTP 怎么结束

HTTP 双换行结尾、Transfer-Encoding: chunked 的包格式,以及它们和文件系统 EOF 的类比。

发布 2019年3月31日 标签 #http #networking

~/posts/http-end-of-story $ cat post.md

/ 语言 EN / 中文
/ 主题 / /

EOF 与可靠的文件系统

很多文件格式以 EOF 标识收尾——表示文件结束。如果这个文件被某个文件流读取,意味着这个流也可以关掉了。对于可靠的文件系统来说这是一个很合理的实践。

文件系统也是分层的。以 Linux 为例(只考虑硬盘上的文件):

  • 应用层 之下是 Kernel 层
  • System Call Interface (SCI) 层向应用提供可靠的系统调用接口。这些系统调用经常被各种语言封装成脚本语言的函数——比如 Node 的 fs 就是封装好的 SCI 调用。
  • SCI 之下是 Virtual File System (VFS) 层,统一暴露”读取一个文件”这样的语义,不管文件实际来自硬盘还是其它设备。硬盘分区也在这里处理。
  • VFS 之下是 General Block Device 层,隐藏具体硬件的实现细节,给上层提供一个虚拟化的块设备访问接口。
  • 再往下就是硬件,本文不展开。

这和网络协议的分层是同源同根的——每一层只关心自己范围内的事情。最终给应用看的 API 大多数情况下不需要操心”响应不及时要重试”这类底层问题。

铺垫这么多想说的其实就是:EOF 作为应用层的”文件结束”标识,是合理的

HTTP 和应用层结束

很多人会问:HTTP 是应用层协议,那它为什么不能直接用 EOF 这样的标识收尾呢?

答案是”先问是不是,再问为什么”——HTTP 协议本来就有自己的结束标识\n\n(更准确是 \r\n\r\n)。

举例:一个 GET 请求的 header 区结束时,一个双换行就告诉对端”这个请求结束了,你可以回复了”。POST 请求里,第一个双换行表示 header 区结束、接下来是 body;第二个双换行表示 body 也结束了,可以处理。

但当我们在同一条 TCP 连接里要传输多个相关的包,且数量不定的时候,双换行就不够用了。

和双换行也没差多远

HTTP 头 Transfer-Encoding 用来描述分块传输的细节。Transfer-Encoding: chunked 表示包数量未定——客户端要收完所有 chunk 再拼起来才能得到完整结果。

每个 chunk 的格式:

  1. 一个 16 进制的数字,表示这个 chunk 的字节数。
  2. 紧跟一个换行(按规范是 \r\n,但有些实现比如 Chrome 在大小和 \r\n 之间会塞一个空格 0x20)。
  3. 数据本身。
  4. 一个换行结尾。

所有 chunk 的数据拼接起来就是完整内容。最后一个 chunk 是一个”空 chunk”——以一个空格开头,可能带些额外的尾部 header(trailing headers),以一个换行结尾,标记整个传输结束。

这个模式和 EOF 或者双换行差别不大,只是 EOF 本身就是一个文件流的概念。

归根结底还是应用层

总的看,应用层的实现把”重试”、“内容校验”等等都省了——靠的是站在 TCP 之上。比如包的顺序问题 HTTP 完全不需要操心,TCP 已经处理过了。

返回首页