HTTP协议中你必须知道的三种数据格式

  实习中的一个主要工作就是分析 HTTP 中的协议,自己也用 Python 写过正则表达式对 HTTP 请求和响应的内容进行匹配,然后把关键字段抽离出来放到一个字典中以备使用(可以稍微改造一下就是一个爬虫工具)。

  HTTP 协议中的很多坑,自己都遇到过,我就针对自己遇到的几种 HTTP 常见的数据格式,来做一个总结。

  对于 Zlib,一点也不陌生,我们平时用它来压缩文件,常见类型有 zip、rar 和 7z 等。Zlib 是一种流行的文件压缩算法,应用十分广泛,尤其是在 Linux 平台。当应用 Zlib 压缩到一个纯文本文件时,效果是非常明显的,大约可以减少70%以上的文件大小,这取决于文件中的内容。

  Zlib 也适用于 Web 数据传输,比如利用 Apache 中的 Gzip (后面会提到,一种压缩算法) 模块,我们可以使用 Gzip 压缩算法来对 Apache 服务器发布的网页内容进行压缩后再传输到客户端浏览器。这样经过压缩后实际上降低了网络传输的字节数,最明显的好处就是可以加快网页加载的速度。

  网页加载速度加快的好处不言而喻,节省流量,改善用户的浏览体验。而这些好处并不仅仅限于静态内容,PHP 动态页面和其他动态生成的内容均可以通过使用 Apache 压缩模块压缩,加上其他的性能调整机制和相应的服务器端 缓存规则,这可以大大提高网站的性能。因此,对于部署在 Linux 服务器上的 PHP 程序,在服务器支持的情况下,建议你开启使用 Gzip Web 压缩。

  压缩算法不同,可以产生不同的压缩数据(目的都是为了减小文件大小)。目前 Web 端流行的压缩格式有两种,分别是 Gzip 和 Defalte。

  Apache 中的就是 Gzip 模块,Deflate 是同时使用了 LZ77 算法与哈夫曼编码(Huffman Coding)的一个无损数据压缩算法。Deflate 压缩与解压的源代码可以在自由、通用的压缩库 zlib 上找到。

  其实说这么多,总结一句话,Deflate 是一种压缩算法,是 huffman 编码的一种加强。 deflate 与 gzip 解压的代码几乎相同,可以合成一块代码。

  如果请求文件是HTML、CSS等静态文件,Web服务器到压缩缓冲目录中检查是否已经存在请求文件的最新压缩文件;

  如果请求文件的压缩文件不存在,Web服务器向浏览器返回未压缩的请求文件,并在压缩缓冲目录中存放请求文件的压缩文件;

  如果请求文件是动态文件,Web服务器动态压缩内容并返回浏览器,压缩内容不存放到压缩缓存目录中。

  说了这么多,下面举一个例子,打开抓包软件,访问我们学校的官网( ,请求头如下:

  实际上 Gzip 网站要远比 Deflate 多,之前写过一个简单爬虫从 hao123的主页开始爬,爬几千个网页(基本涵盖所有常用的),专门分析响应体的压缩类型,得到的结果是:

  响应头的 Encoding 字段很有帮助,比如我们写个正则表达式匹配响应头是什么压缩:

  匹配到内容为空说明没有压缩,为 gzip 说明响应体要经过 gzip 解压,为 deflate 说明为 deflate 压缩。

  在python中有zlib库,它可以解决gzip、deflate和zlib压缩。这三种对应的压缩方式分别是:

  当然,对于gzip文件,也可以使用python的gzip包来解决,可以参考下面的代码:

  也可以在解压的时候自动加入头检测,把32加入头中就可以触发头检测,例如:

  刚接触这些东西的时候,每天都会稀奇古怪的报一些错误,基本上 Google 一下都能解决。

  分块传输编码(Chunked transfer encoding)是超文本传输协议(HTTP)中的一种数据传输机制,允许 HTTP 由网页服务器发送给客户端应用( 通常是网页浏览器)的数据可以分成多个部分。分块传输编码只在 HTTP 协议 1.1 版本(HTTP/1.1)中提供。

  通常,HTTP 应答消息中发送的数据是整个发送的,Content-Length 消息头字段表示数据的长度。数据的长度很重要,因为客户端需要知道哪里是应答消息的结束,以及后续应答消息的开始。然而,使用分块传输编码,数据分解成一系列数据块,并以一个或多个块发送,这样服务器可以发送数据而不需要预先知道发送内容的总大小。通常数据块的大小是一致的,但也不总是这种情况。

  HTTP 分块传输编码允许服务器为动态生成的内容维持 HTTP 持久链接。通常,持久链接需要服务器在开始发送消息体前发送 Content-Length 消息头字段,但是对于动态生成的内容来说,在内容创建完之前是不可知的。 分块传输编码允许服务器在最后发送消息头字段。对于那些头字段值在内容被生成之前无法知道的情形非常重要,例如消息的内容要使用散列进行签名,散列的结果通过 HTTP 消息头字段进行传输。没有分块传输编码时,服务器必须缓冲内容直到完成后计算头字段的值并在发送内容前发送这些头字段的值。 HTTP 服务器有时使用压缩 (gzip 或 deflate)以缩短传输花费的时间。分块传输编码可以用来分隔压缩对象的多个部分。在这种情况下,块不是分别压缩的,而是整个负载进行压缩,压缩的输出使用本文描述的方案进行分块传输。在压缩的情形中,分块编码有利于一边进行压缩一边发送数据,而不是先完成压缩过程以得知压缩后数据的大小。

  如果一个 HTTP 消息(请求消息或应答消息)的 Transfer-Encoding 消息头的值为 chunked,那么,消息体由数量未定的块组成,并以最后一个大小为 0 的块为结束。每一个非空的块都以该块包含数据的字节数(字节数以十六进制表示)开始,跟随一个 CRLF(回车及换行),然后是数据本身,最后块 CRLF 结束。在一些实现中,块大小和 CRLF 之间填充有白空格(0x20)。

  最后一块是单行,由块大小(0),一些可选的填充白空格,以及 CRLF。最后一块不再包含任何数据,但是可以发送可选的尾部,包括消息头字段。

  消息最后以 CRLF 结尾。例如下面就是一个 chunked 格式的响应体。

  Transfer-Encoding: chunked字段可以看出响应体是否为 chunked 压缩,chunked 数据很有意思,采用的格式是 长度 内容 长度 ..0 ,而且长度还是十六进制的,最后以 0 结尾(不保证都有)。因为上面的数据是 gzip 压缩,看起来不够直观,下面举个简单的例子:

  上述例子 chunked 解码后的数据 ababa12345...,另外 是不可见的,我手动加的。

  实际中,我们会同时遇到既时 chunked 又是压缩数据的响应,这个时候处理的思路应该是:先处理 chunked,在处理压缩数据,顺序不能反。

  http 协议本身的原始方法不支持 multipart/form-data 请求,那这个请求自然就是由这些原始的方法演变而来的,具体如何演变且看下文:

  multipart 的数据格式有一定的特点,首先是头部规定了一个 ${bound},上面那个例子中的 ${bound} 为 ::,由多个内容相同的块组成,每个块的格式以--加 ${bound} 开始的,然后是该部分内容的描述信息,然后一个 ,然后是描述信息的具体内容。如果传送的内容是一个文件的话,那么还会包含文件名信息,以及文件内容的类型。

  小结,要发送一个 multipart/form-data 的请求,需要定义一个自己的 ${bound} ,按照格式来发请求就好,对于 multipart 的数据格式并没有过多介绍,感觉和 chunked 很类似,不难理解。

  本文介绍的三种数据格式,都比较基础,一些框架自动把它们处理,比如爬虫。还有图像上传,对于 multipart/data 格式的请求头,了解一些概念性的东西也非常有意思。共勉。

  每日头条、业界资讯、热点资讯、八卦爆料,全天跟踪微博播报。各种爆料、内幕、花边、资讯一网打尽。百万互联网粉丝互动参与,TechWeb官方微博期待您的关注。