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

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

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

Zlib 压缩数据

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

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

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

Gzip 压缩两种类型

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

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

更高压缩率的 Deflate 是 7-zip 所实现的。AdvanceCOMP 也使用这种实现,它可以对 gzip、PNG、MNG 以及
ZIP 文件进行压缩从而得到比 zlib 更小的文件大小。在 Ken Silverman的 KZIP 与 PNGOUT
中使用了一种更加高效同时要求更多用户输入的 Deflate 程序。

deflate 使用 inflateInit(),而 gzip 使用 inflateInit2() 进行初始化,比
inflateInit() 多一个参数: -MAX_WBITS,表示处理 raw deflate 数据。因为 gzip 数据中的 zlib
压缩数据块没有 zlib header 的两个字节。使用 inflateInit2 时要求 zlib 库忽略 zlib header。在
zlib 手册中要求 windowBits 为 8..15,但是实际上其它范围的数据有特殊作用,如负数表示 raw deflate。

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

更多知识请见 维基百科 zlib

Web 服务器处理数据压缩的过程

Web服务器接收到浏览器的HTTP请求后,检查浏览器是否支持HTTP压缩(Accept-Encoding 信息);

如果浏览器支持HTTP压缩,Web服务器检查请求文件的后缀名;

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

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

如果请求文件的最新压缩文件已经存在,则直接返回请求文件的压缩文件;

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

举个栗子

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


  1. GET /_css/tpl2/system.css HTTP/1.1 
  2. Host: www.ecnu.edu.cn 
  3. Connection: keep-alive 
  4. User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.59 Safari/537.36 
  5. Accept: text/css,*/*;q=0.1 
  6. Referer: http://www.ecnu.edu.cn/ 
  7. Accept-Encoding: gzip, deflate 
  8. Accept-Language: zh-CN,zh;q=0.8 
  9. Cookie: a10-default-cookie-persist-20480-sg_bluecoat_a=AFFIHIMKFAAA  

在第七行, Accept-Encoding 显示的是 gzip, deflate,这句话的意思是,浏览器告诉服务器支持 gzip 和
deflate 两种数据格式,服务器收到这种请求之后,会进行 gzip 或 deflate 压缩(一般都是返回 gzip 格式的数据)。

Python 的 urllib2 就可以设置这个参数:


  1. request = urllib2.Request(url) 
  2. request.add_header('Accept-encoding', 'gzip') 
  3. //或者设置成 deflate 
  4. request.add_header('Accept-encoding', 'deflate') 
  5. //或者两者都设置 
  6. request.add_header('Accept-encoding', 'gzip, deflate')  

服务器给的响应一般如下:


  1. HTTP/1.1 200 OK 
  2. Date: Sat, 22 Oct 2016 11:41:19 GMT 
  3. Content-Type: text/javascript;charset=utf-8 
  4. Transfer-Encoding: chunked 
  5. Connection: close 
  6. Vary: Accept-Encoding 
  7. tracecode: 24798560510951725578102219 
  8. Server: Apache 
  9. Content-Encoding: gzip 
  10.  
  11. 400a 
  12. ............ks#I. ...W...,....>..T..]..Z...Y..].MK..2..L..(略) 
  13. //响应体为压缩数据  

从响应头来看,Content-Encoding: gzip 这段话说明响应体的压缩方式是 gzip
压缩,一般有几种情况,字段为空表示明文无压缩,还有 Content-Encoding: gzip 和 Content-Encoding:
deflate 两种。

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

  1. Accept-Encoding 不设置参数:会返回一个无压缩的响应体(浏览器比较特别,他们会自动设置 Accept-Encoding: gzip: deflate 来提高传输速度);
  2. Accept-Encoding: gzip,100% 的网站都会返回 gzip 压缩,但不保证互联网所有网站都支持 gzip(万一没开启);
  3. Accept-Encoding: deflate:只有不到 10% 的网站返回一个 deflate 压缩的响应,其他的则返回一个没有压缩的响应体。
  4. Accept-Encoding: gzip, deflate:返回的结果也都是 gzip 格式的数据,说明在优先级上 gzip 更受欢迎。

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


  1. (?<=Content-Encoding: ).+(?=\r\n) 

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

Python 中的 zlib 库

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


  1. RFC 1950 (zlib compressed format) 
  2. RFC 1951 (deflate compressed format) 
  3. RFC 1952 (gzip compressed format)  

虽说是 Python 库,但是底层还是 C(C++) 来实现的,这个 http-parser 也是 C 实现的源码,Nodejs 的 http-parser 也是 C 实现的源码,zlib 的 C 源码在这里。C 真的好牛逼呀!

在解压缩的过程中,需要选择 windowBits 参数:


  1. to (de-)compress deflate format, use wbits = -zlib.MAX_WBITS 
  2. to (de-)compress zlib format, use wbits = zlib.MAX_WBITS 
  3. to (de-)compress gzip format, use wbits = zli  

例如,解压gzip数据,就可以使用zlib.decompress(data, zlib.MAX_WBITS | 16),解压deflate数据可以使用zlib.decompress(data,- zlib.MAX_WBITS)。

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


  1. >>> import gzip 
  2. >>> import StringIO 
  3. >>> fio = StringIO.StringIO(gzip_data) 
  4. >>> f = gzip.GzipFile(fileobj=fio) 
  5. >>> f.read() 
  6. 'test' 
  7. >>> f.close()  

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


  1. >>> zlib.decompress(gzip_data, zlib.MAX_WBITS|32) 
  2. 'test' 
  3. >>> zlib.decompress(zlib_data, zlib.MAX_WBITS|32) 
  4. 'test'  

以上参考 stackoverflow How can I decompress a gzip stream with zlib?

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

分块传输编码 chunked

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

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

分块传输的优点

HTTP 1.1引入分块传输编码提供了以下几点好处:

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

注:以上内容来自于维基百科

分块传输的格式

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

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

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


  1. HTTP/1.1 200 OK 
  2. Date: Wed, 06 Jul 2016 06:59:55 GMT 
  3. Server: Apache 
  4. Accept-Ranges: bytes 
  5. Transfer-Encoding: chunked 
  6. Content-Type: text/html 
  7. Content-Encoding: gzip 
  8. Age: 35 
  9. X-Via: 1.1 daodianxinxiazai58:88 (Cdn Cache Server V2.0), 1.1 yzdx147:1 (Cdn  
  10. Cache Server V2.0) 
  11. Connection: keep-alive 
  12.  
  13. ....k.|W.. 
  14. 166 
  15. ..OO.0...&~..;........]..(F=V.A3.X..~z...-.l8......y....).?....,....j..h .6 
  16. ....s.~.>..mZ .8/..,.)B.G.`"Dq.P].f=0..Q..d.....h......8....F..y......q.....4 
  17. {F..M.A.*..a.rAra.... .n>.D 
  18. ..o@.`^.....!@ $...p...%a\D..K.. .d{2...UnF,C[....T.....c....V...."%.`U......? 
  19. D....#..K..<.....D.e....IFK0.<...)]K.V/eK.Qz...^....t...S6...m...^..CK.XRU?m.. 
  20. .........Z..#Uik...... 
  21. 0  

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


  1. 5\r\n 
  2. ababa\r\n 
  3. f\r\n 
  4. 123451234512345\r\n 
  5. 14\r\n 
  6. 12345123451234512345\r\n 
  7. 0\r\n  

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

和 gzip 一样,一样可以写一个正则表达式来匹配:


  1. (?<=Transfer-Encoding: ).+(?=\r\n) 

处理 chunked 数据

从前面的介绍可以知道,response-body 部分其实由 length(1) \r\n data(1) \r\n length(2) \r\n data(2)…… 循环组成,通过下面的函数进行处理,再根据压缩类型解压出最终的数据。

Python 处理的过程如下:


  1. unchunked = b'' 
  2. pos = 0 
  3. while pos <= len(data): 
  4.     chunkNumLen = data.find(b'\r\n', pos)-pos 
  5.     //从第一个元素开始,发现第一个\r\n,计算length长度 
  6.     chunkLen=int(data[pos:pos+chunkNumLen], 16) 
  7.     //把length的长度转换成int 
  8.     if chunkLen == 0: 
  9.         break 
  10.         //如果长度为0,则说明到结尾 
  11.     chunk = data[pos+chunkNumLen+len('\r\n'):pos+chunkNumLen+len('\r\n')+chunkLen] 
  12.     unchunked += chunk 
  13.     //将压缩数据拼接 
  14.     pos += chunkNumLen+len('\r\n')+chunkLen+len('\r\n') 
  15.     //同时pos位置向后移动 
  16.  
  17. return unchunked 
  18. //此时处理后unchunked就是普通的压缩数据,可以用zlib解压函数进行解压  

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

MultiPart 数据

MultiPart 的本质就是 Post 请求,MultiPart出现在请求中,用来对一些文件(图片或文档)进行处理,在请求头中出现
Content-Type: multipart/form-data; boundary=::287032381131322 则表示为
MultiPart 格式数据包,下面这个是 multipart 数据包格式:


  1. POST /cgi-bin/qtest HTTP/1.1 
  2. Host: aram 
  3. User-Agent: Mozilla/5.0 Gecko/2009042316 Firefox/3.0.10 
  4. Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 
  5. Accept-Language: en-us,en;q=0.5 
  6. Accept-Encoding: gzip,deflate 
  7. Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7 
  8. Keep-Alive: 300 
  9. Connection: keep-alive 
  10. Referer: http://aram/~martind/banner.htm 
  11. Content-Type: multipart/form-data; boundary=::287032381131322 
  12. Content-Length: 514 
  13.  
  14. --::287032381131322 
  15. Content-Disposition: form-data; name="datafile1"; filename="r.gif" 
  16. Content-Type: image/gif 
  17.  
  18. GIF87a.............,...........D..; 
  19. --::287032381131322 
  20. Content-Disposition: form-data; name="datafile2"; filename="g.gif" 
  21. Content-Type: image/gif 
  22.  
  23. GIF87a.............,...........D..; 
  24. --::287032381131322 
  25. Content-Disposition: form-data; name="datafile3"; filename="b.gif" 
  26. Content-Type: image/gif 
  27.  
  28. GIF87a.............,...........D..; 
  29. --::287032381131322—  

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

  1. multipart/form-data 的基础方法是 post,也就是说是由 post 方法来组合实现的
  2. multipart/form-data 与 post 方法的不同之处:请求头,请求体。
  3. multipart/form-data 的请求头必须包含一个特殊的头信息:Content-Type,且其值也必须规定为
    multipart/form-data,同时还需要规定一个内容分割符用于分割请求体中的多个 post
    内容,如文件内容和文本内容自然需要分割,不然接收方就无法正常解析和还原这个文件。具体的头信息如:Content-Type:
    multipart/form-data; boundary=${bound},${bound}
    代表分割符,可以任意规定,但为了避免和正常文本重复,尽量使用复杂一点的内容,如::287032381131322
  4. multipart/form-data 的请求体也是一个字符串,不过和 post 的请求体不同的是它的构造方式,post 是简单的 name=value 值连接,而 multipart/form-data 则是添加了分隔符等内容的构造体。

维基百科上关于 multipart的介绍。

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

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

总结

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

参考全列在文章中了

作者:songjz

来源:51CTO

时间: 2024-09-11 00:06:15

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

Word中输入立方米符号的三种方法

  Word中输入立方米符号的三种方法         Word中输入立方米符号方法一:输入法输入 其实现在有些输入法中集成了很多特殊符号,例如搜狗拼音中就有立方米符号,我们只需要打出立方米的拼音,就会出现一个立方米符号的选项. Word中输入立方米符号方法二:利用制作上标的方法 用制作上标的方法可以做出立方米符号的效果,但这种方法其实还可以细分为几种不同的操作,下面一一进行介绍. 一.在Word文档中输入3,然后将其选中,切换到"开始"选项卡,单击"上标"按钮即可

jsp页面中插入css样式的三种方法总结

 本篇文章主要是对jsp页面中插入css样式的三种方法进行了总结介绍,需要的朋友可以过来参考下,希望对大家有所帮助 1. 外部样式   当样式需要应用于很多页面时,外部样式表将是理想的选择.在使用外部样式表的情况下,你可以通过改变一个文件来改变整个站点的外观.每个页面使用<link>标签链接到样式表.<link>标签在(文档的)头部:   <head> <link rel="stylesheet"  type="text/css&qu

分析在Worklight中开发本地功能的三种模式

文章将分析在 Worklight 中开发本地功能的三种模式:调用 Cordova 支持的本地功能:通过 Worklight common API 调用开发的本地功能:编写 Cordova 的插件,调用本地功能.然后通过相应的实例展现不同方法的使用模式,向读者展现 Worklight 在和本地功能结合上的能力.最后分析各自的优缺点和使用的环境. 纯 web 模式的局限性 在 Worklight 架构下,纯 web 模式虽然可以像本地应用一样被安装在手机上,但是所提供的服务和传统的网页相比,几乎没有

C/C++程序开发中实现信息隐藏的三种类型_C 语言

无论是模块化设计,还是面向对象设计,还是分层设计,实现子系统内部信息的对外隐藏都是最关键的内在要求.以本人浅显的经验,把信息隐藏按照程度的不同分成(1)不可见不可用(2)可见不可用(3)可见可用. 1 不可见不可用 就是说模块内部的变量.结构体.类定义对外部而已完全隐藏,外部对此一无所知.常用的实现方法就是利用不透明指针,请参见我的博文C语言开发函数库时利用不透明指针对外隐藏结构体细节. 这种方法同样适用于C++语言,一种可能的实现方式为面向接口编程. 头文件 IMyClass.h class

JS中动态创建元素的三种方法总结(推荐)_javascript技巧

1.动态创建元素一 document.write() 例如向页面中输出一个 li 标签 <pre class="html" name="code"><span style="font-size:12px;"><script> document.write("<li>123</li>"); </script></span> body标签中就会插入

JavaScript中数组去除重复的三种方法_javascript技巧

废话不多说了,具体方法如下所示: 方法一:返回新数组每个位子类型没变 function outRepeat(a){ var hash=[],arr=[]; for (var i = 0; i < a.length; i++) { hash[a[i]]!=null; if(!hash[a[i]]){ arr.push(a[i]); hash[a[i]]=true; } } console.log(arr); } outRepeat([2,4,4,5,"a","a"

MVC3中,在control里面三种Html代码输出形式

MVC3中,在control里面三种Html代码输出形式:ViewData["msg"] = "<br /> Title <br />"; 1.页面显示效果: @Html.Raw() : 不直接输出html代码,常用场合(使用编辑器 保存文章图片等时)    Title    使用:@Html.Raw(ViewData["msg"].ToString()) 2.页面显示效果: <br /> Title <

android中Webview实现截屏三种方式小结

本人最近学习了android中Webview实现截屏三种方式,下面我来记录一下,有需要了解的朋友可参考.希望此文章对各位有所帮助. 第一种方式 通过调用webview.capturePicture(),得到一个picture对象,根据图像的宽和高创建一个Bitmap,再创建一个canvas,绑定bitmap,最后用picture去绘制. //获取Picture对象 Picture picture = wv_capture.capturePicture(); //得到图片的宽和高(没有reflec

在Flash动画中使字体清晰的三种方法

flash动画 Flash显示静态文本的时候,文字显得很模糊,中间甚至粘成一块 解决的办法有3个 1) 使用设备字体 当你在flash中使用静态文本的时候,Flash会插入字体轮廓信息,并进行抗锯齿处理,所以轮廓会显得很模糊,使用设备字体后,Flash不再插入字体轮廓信息,只是在客户端播放时调用客户端的字体信息,也不会进行抗锯齿处理,这样的做结果有: a. 字体在12pt以下时很清晰,但在比较大(大约18pt以上)时有明显的锯齿 b. 如果客户端不存在相应的字体,则显示会出现预料外的情况,肯定不