Http: End of Story

2019-03-31

只有满怀自信的人,才能在任何地方都怀有自信沉浸在生活中,并实现自己的意志。
–高尔基

EOF 和可靠的文件系统

很多文件格式的结尾会带有 EOF 的标识,这意味着当前的文件结束了,如果这个文件被某一个文件流使用的话,这意味这个流也应该被关闭了。对于一个可靠的文件系统来说,这是一个非常好的实践。

网路的层级是分明的,文件系统也不例外。我们以 Linux 的文件系统作为例子(只考虑硬盘读取出的文件)。应用层之下也就是 Kernel 层, System Call Interface (SCI) 层为应用提供可靠的可以调用的系统调用,这些系统调用有被许多语言封装成脚本语言和应用本身的函数,比如说 Node 里面的 FS 模块就是用来调用由 Node 封装好的 System Call 的。 SCI 之下是 Virtual File System (CFS) 层提供无论是硬盘中的文件还是其他硬件的文件的读取函数,这些方式被 SCI 使用来提供统一的 API 接口。硬盘分区也会在这一阶段被处理,其之下是 General Block Device 层,隐藏硬件设备实现的细节,提供可以虚拟的硬件访问函数,在这一层之上,软件无需关心硬件的驱动标准和访问方式。再往下就完全是硬件的范畴了,这篇文章讨论不动。

总而言之,这和网络的多层协议的设计其实是同源同根的,每一层都只关心自己需要关心的范畴,而最终形成的 API 在大部分情况下也不需要关心包括 相应不及时时重试 等很不应用的情况。

说了这么多的重点在什么呢? EOF 作为一个应用层的文件结束标识,这样的使用的合理的。

HTTP 和不可靠的应用层?

很多小朋友肯定要问了 HTTP 不是一个应用层协议吗?为什么不能就用 EOF 这样的标识来代表请求的结尾呢?

这个问题的答案其实可以概括为 先问是不是再问为什么。实际上, \n\n 就是 HTTP 协议的结束手段,比如说在一个 GET 请求的 HEADER 区结束的时候一个双换行就可以告诉接受方的 TCP 维护程序我的请求没有了,你可以返回结果了。同理, POST 请求的第一个双回车意味着 HEADER 区结束了,接下来是 BODY 区,而第二个双回车代表 BODY 区也结束了,接下来请返回结果。

但是,当我们在同一个 TCP 通道里面需要传多个相关,数量未定的时候,在某些意义上将这个就没有那么可靠了。

这个类比其实有些无力,也不太完美,我想说明的是 HTTP 在分块传输的时候会遇到有序且包数量未定的问题,而这不是双回车可以解决的。

和双回车也没什么区别

HTTP 的头 Transfer-Encoding 负责说明多包分块的情况,而 Transfer-Encoding: chunked 代表包的数量是不定的,全都收起来然后拼接才能得到结果。

每一个块都从这个包的数据量开始,这是一个标识字节数的 16 进制数字。紧接着是一个单换行(似乎是 \r\n 而不是 \n),然后是数据本身,最后以一个单换行结束。这是 HTTP 1.1 规定的但是实际上在有些实现,比如说 Chrome 实际上在包的大小和第一个单换行之前有一个空格符也就是 0x20

每一个块的数据都应该被拼接起来,最后一个包是一个由空格开头,有的时候有一些额外的空格,一些 HEADER 值,有些是尾部特殊的 HEADER, 最后以单换行结尾的包,这意味着所有的内容都传输完了。这其实和 EOF 或者双回车也没什么区别,只是 EOF 本身就是一个文件。

到底还是引用层

实际上我们可以发现,应用层的实现其实省略了大量的重试,内容的验证等工作,实际上还是站在 TCP 的基础上再进行调整。就比如说确保包的顺序的需求就不需要 HTTP 协议操心,这已经被 TCP 解决了。

Go Back

随便看看 :D