实习中的主要任务之一就是分析HTTP中的协议。我也用Python写了正则表达式来匹配HTTP请求和响应的内容,然后把关键词段提取出来,放入字典中使用(可以稍微修改一下,做个爬虫工具)。我在HTTP协议中遇到过很多陷阱。我将我遇到的几种常见的HTTP数据格式做一个总结。Zlib压缩数据对于Zlib并不陌生。我们通常用它来压缩文件。常见类型包括zip、rar和7z。Zlib是一种流行的文件压缩算法,被广泛使用,尤其是在Linux平台上。将Zlib压缩应用于纯文本文件时,效果会非常显着,文件大小会减少70%以上,具体取决于文件的内容。Zlib也适用于网页数据传输。例如,利用Apache中的Gzip(后文提到的一种压缩算法)模块,我们可以利用Gzip压缩算法,将Apache服务器发布的网页内容进行压缩,然后传输到客户端进行浏览。设备。这样,经过压缩后,实际上减少了网络传输的字节数。最明显的好处是可以加快网页的加载速度。更快的网页加载的好处是不言而喻的,节省流量,提高用户的浏览体验。而且这些好处不仅限于静态内容,PHP动态页面等动态生成的内容可以通过使用Apache压缩模块进行压缩,再加上其他性能调整机制和相应的服务器端缓存规则,可以大大提高性能网站。因此,对于部署在Linux服务器上的PHP程序,如果服务器支持,建议开启Gzip网页压缩。两种Gzip压缩具有不同的压缩算法,可以产生不同的压缩数据(目的是减小文件大小)。目前Web上流行的压缩格式有两种,分别是Gzip和Defalte。Apache是??Gzip模块,Deflate是同时使用LZ77算法和哈夫曼编码的无损数据压缩算法。Deflate压缩和解压缩的源代码可以在免费的通用压缩库zlib中找到。7-zip实现了更高压缩比的Deflate。AdvanceCOMP也使用此实现,它可以压缩gzip、PNG、MNG和ZIP文件以实现比zlib更小的文件大小。KenSilverman的KZIP和PNGOUT中使用了需要更多用户输入的更高效的Deflate程序。deflate使用inflateInit(),gzip使用inflateInit2()进行初始化,比inflateInit()多了一个参数:-MAX_WBITS,表示处理rawdeflate数据。因为gzip数据中的zlib压缩数据块没有zlib头的两个字节。使用inflateInit2时要求zlib库忽略zlib标头。zlib手册中要求windowBits为8..15,但实际上其他范围内的数据都有特殊作用,比如负数代表rawdeflate。其实说了这么多,总之Deflate是一种压缩算法,是huffman编码的增强。deflate和gzip的解压代码几乎一样,可以合成一段代码。有关更多信息,请参阅维基百科zlib。Web服务器处理数据压缩的过程Web服务器收到浏览器的HTTP请求后,检查浏览器是否支持HTTP压缩(Accept-Encoding信息);如果浏览器支持HTTP压缩,Web服务器检查请求文件的后缀名;如果请求文件是HTML、CSS等静态文件,Web服务器在压缩缓冲区目录中检查请求文件的压缩文件是否已经存在;如果请求文件的压缩文件不存在,则Web服务器将未压缩的请求文件返回给浏览器。并将请求文件的压缩文件存放在压缩缓冲区目录中;如果请求文件的压缩文件已经存在,则直接返回请求文件的压缩文件;如果请求的文件是动态文件,Web服务器会动态压缩内容返回给浏览器,压缩后的内容不存放在压缩缓存目录中。举个栗子,说这么多。这是一个例子。打开抓包软件,访问我校官网(www.ecnu.edu.cn)。请求头如下:GET/_css/tpl2/system.cssHTTP/1.1Host:www.ecnu.edu.cnConnection:keep-aliveUser-Agent:Mozilla/5.0(WindowsNT10.0;WOW64)AppleWebKit/537.36(KHTML,likeGecko)Chrome/54.0.2840.59Safari/537.36Accept:text/css,*/*;q=0.1Referer:http://www.ecnu.edu.cn/Accept-Encoding:gzip,deflateAccept-Language:zh-CN,zh;q=0.8Cookie:a10-default-cookie-persist-20480-sg_bluecoat_a=AFFIHIMKFAAA在第七行,Accept-Encoding显示gzip、deflate。这句话的意思是浏览器告诉服务器支持两种数据格式,gzip和deflate。服务端收到这个请求后,会进行gzip或者deflate压缩(一般返回gzip格式的数据)。Python的urllib2可以设置这个参数:request=urllib2.Request(url)request.add_header('Accept-encoding','gzip')//或者设置为deflaterequest.add_header('Accept-encoding','deflate')//或者同时设置request.add_header('Accept-encoding','gzip,deflate')服务器给出的响应一般如下:HTTP/1.1200OKDate:Sat,22Oct201611:41:19GMTContent-Type:text/javascript;charset=utf-8Transfer-Encoding:chunkedConnection:closeVary:Accept-Encodingtracecode:24798560510951725578102219Server:ApacheContent-Encoding:gzip400a.........ks#I....W...,....>..T..]..Z...Y..].MK..2..L..(略)//响应体为压缩数据从响应头来看,Content-Encoding:gzip它可见响应体的压缩方式为gzip压缩。一般来说,有几种情况。如果该字段为空,则表示未压缩明文。还有两种类型:Content-Encoding:gzip和Content-Encoding:deflate。事实上,Gzip网站比Deflate多得多。之前写了一个简单的爬虫从hao123的首页爬取,爬取了上千个网页(基本涵盖了所有常用的),具体分析了响应体的压缩类型。结果是:Accept-Encoding不设置参数:会返回一个未压缩的responsebody(浏览器比较特殊,会自动设置Accept-Encoding:gzip:deflate以提高传输速度);Accept-Encoding:gzip,100%的网站会返回gzip压缩,但不保证网上所有网站都支持gzip(万一没有启用);Accept-Encoding:deflate:不到10%的网站返回deflate压缩响应,其他网站返回未压缩的响应主体。Accept-Encoding:gzip,deflate:返回结果都是gzip格式的数据,说明gzip在优先级上更受欢迎。响应标头的编码字段非常有用。比如我们写一个正则表达式来匹配响应头的压缩:(?<=Content-Encoding:).+(?=\r\n)如果内容为空,则表示没有压缩,对于gzip,表示响应体要用gzip解压,deflate,表示deflate压缩。Python中的zlib库python中有zlib库,它解决了gzip、deflate和zlib压缩。对应的三种压缩方式分别是:RFC1950(zlibcompressedformat)RFC1951(deflatecompressedformat)RFC1952(gzipcompressedformat)虽然是Python库,但是底层还是由C(C++)实现的。这个http-parser也是C实现的源码,Nodejshttp-parser也是C实现的源码,zlib的C源码在这里。C真的厉害!解压过程中,需要选择windowBits参数:to(de-)compressdeflateformat,使用wbits=-zlib.MAX_WBITSto(de-)compresszlibformat,使用wbits=zlib.MAX_WBITSto(de-)compressgzipformat,使用wbits=zli例如,要解压缩gzip数据,可以使用zlib.decompress(data,zlib.MAX_WBITS|16),要解压缩deflate数据,可以使用zlib.decompress(data,-zlib.MAX_WBITS)。当然对于gzip文件,也可以使用python的gzip包来解决,可以参考如下代码:>>>importgzip>>>importStringIO>>>fio=StringIO.StringIO(gzip_data)>>>f=gzip.GzipFile(fileobj=fio)>>>f.read()'test'>>>f.close()也可以在解压时自动添加header检测,在header中添加32可以触发header检测,例如:>>>zlib.decompress(gzip_data,zlib.MAX_WBITS|32)'test'>>>zlib.decompress(zlib_data,zlib.MAX_WBITS|32)'test'参考stackoverflow如何使用zlib解压缩gzip流?刚开始接触这些东西的时候,每天都会报一些奇怪的错误,基本上Google一下就可以解决。Chunkedtransferencoding分块传输编码(Chunkedtransferencoding)是超文本传输??协议(HTTP)中的一种数据传输机制,它允许将Web服务器发送给客户端应用程序(通常是Web浏览器)的HTTP数据分成多个部分。分块传输编码仅在HTTP协议(HTTP/1.1)的1.1版中可用。通常,HTTP响应报文中发送的数据是完整发送的,Content-Length报文头域表示数据的长度。数据的长度很重要,因为客户端需要知道回复消息在哪里结束以及后续回复消息从哪里开始。然而,使用分块传输编码,数据被分解成一系列数据块并以一个或多个块发送,这样服务器就可以发送数据而无需预先知道所发送内容的总大小。数据块的大小通常是统一的,但情况并非总是如此。分块传输编码的优点在HTTP1.1中引入分块传输编码提供了以下好处:HTTP分块传输编码允许服务器为动态生成的内容维护HTTP持久连接。通常情况下,持久连接需要服务器在开始发送消息体之前先发送Content-Length头字段,但是对于动态生成的内容,直到内容创建后才知道。分块传输编码允许服务器一次发送消息头字段。这对于在生成内容之前无法知道头字段的值的情况很重要,例如使用哈希对消息内容进行签名,并且通过HTTP消息头字段传输哈希的结果。如果没有分块传输编码,服务器必须缓冲内容,直到完成计算头字段值并在发送内容之前发送这些头字段值。HTTP服务器有时使用压缩(gzip或deflate)来减少传输时间。分块传输编码可用于分隔压缩对象的多个部分。在这种情况下,块不是单独压缩的,而是整个有效负载都被压缩,压缩后的输出使用本文描述的方案以块的形式传输。在压缩的情况下,分块编码有利于在压缩数据时发送数据,而不是先完成压缩过程才能知道压缩数据的大小。注:以上内容来自维基百科。分块传输的格式如果HTTP消息(请求消息或响应消息)的Transfer-Encoding消息头的值是分块的,则消息体由不确定数量的块组成,最后一个大小为0的块为Finish.每个非空块以块中包含的数据的字节数开始(字节数以十六进制表示),然后是CRLF(回车和换行),然后是数据本身,最后是数据本身块CRLF。在某些实现中,块大小和CRLF用空白(0x20)填充。***块是一行,由块大小(0)、一些可选的填充空格和CRLF组成。***块不再包含任何数据,但可以发送可选的尾部,包括消息头字段。消息***以CRLF结尾。例如,以下是分块格式的响应主体。HTTP/1.1200OKDate:Wed,06Jul201606:59:55GMTServer:ApacheAccept-Ranges:bytesTransfer-Encoding:chunkedContent-Type:text/htmlContent-Encoding:gzipAge:35X-Via:1.1daodianxinxiazai58:88(CdnCacheServerV47yz:88(CdnCacheServerV47yz:),1(CdnCacheServerV2.0)连接:保持活动a....k.|W..166..OO.0...&~...;.....]..(F=V.A3.X..~z...-.l8...y....).?....,...j..h.6....s.~.>..mZ.8/..,.)B.G.`"Dq.P].f=0..Q..d.....h......8....F..y......q.......4{F..M.A.*..a.rAra.....n>.D..o@.`^.....!@$..p...%a\D..K...d{2...UnF,C[....T...c....V...."%.`U......?D....#..K..<.....D.e....IFK0.<...)]K.V/eK.Qz...^....t...S6...m...^..CK.XRU?m.....Z..#Uik...0Transfer-Encoding:chunked字段可见响应body是分块还是压缩,分块的数据很有意思,使用的格式为length\r\ncontent\r\nlength\r\n..0\r\n,长度为16进制,***以0\r\n结尾(不保证)。因为上面的数据是用gzip压缩的,所以看起来不够直观。这是一个简单的例子:5\r\nababa\r\nf\r\n123451234512345\r\n14\r\n12345123451234512345\r\n0\r\n上面的例子分块解码数据ababa12345...,而\r\n是看不见,我手动添加的。和gzip一样,也可以写一个正则表达式匹配:(?<=Transfer-Encoding:).+(?=\r\n)来处理chunked数据。从前面的介绍我们可以知道response-body部分其实是由length(1)\r\ndata(1)\r\nlength(2)\r\ndata(2)...组成的由循环组成,经过以下函数处理,然后根据压缩类型解压成最终数据。Python处理的过程如下:chunked=b''pos=0whilepos<=len(data):chunkNumLen=data.find(b'\r\n',pos)-pos//从第一个元素开始,find***\r\n,计算长度chunkLen=int(data[pos:pos+chunkNumLen],16)//将length的长度转化为intifchunkLen==0:break//如果长度为0,则表示到最后chunk=data[pos+chunkNumLen+len('\r\n'):pos+chunkNumLen+len('\r\n')+chunkLen]unchunked+=chunk//拼接压缩数据pos+=chunkNumLen+len('\r\n')+chunkLen+len('\r\n')//同时pos位置后移returnunchunked//此时经过处理,unchunked为普通压缩数据,在可以用zlib解压函数解压的情况下,我们会同时遇到chunked和compressed两种数据响应。这时候的处理思路应该是:先处理chunked,再处理压缩数据。顺序不能颠倒。MultiPart数据MultiPart的本质是一个Post请求。MultiPart出现在请求中,用于处理一些文件(图片或文档)。如果内容类型:多部分/表单数据;boundary=::287032381131322出现在requestheader中,表示是一个MultiPart格式的包,下面是multipart包格式:POST/cgi-bin/qtestHTTP/1.1Host:aramUser-Agent:Mozilla/5.0Gecko/2009042316Firefox/3.0.10Accept:text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8Accept-Language:en-us,en;q=0.5Accept-Encoding:gzip,deflateAccept-字符集:ISO-8859-1,utf-8;q=0.7,*;q=0.7Keep-Alive:300Connection:keep-aliveReferer:http://aram/~martind/banner.htmContent-Type:multipart/form-data;boundary=::287032381131322Content-Length:514--::287032381131322Content-Disposition:form-data;name="datafile1";filename="r.gif"Content-Type:image/gifGIF87a.........,........D..;--::287032381131322Content-Disposition:form-data;name="datafile2";filename="g.gif"Content-Type:image/gifGIF87a...............,......D..;--::287032381131322Content-Disposition:form-data;name="datafile3";filename="b.gif"Content-Type:image/gifGIF87a.......,.......D..;--::287032381131322—http协议的原始方法本身不支持multipart/form-data请求,所以这个请求自然是从这些原始方法演化而来的。下面看如何进化:multipart/form-data的基本方法是post,也就是说multipart/form-data和post方法实现的post方法的区别:请求头,请求体multipart/的请求头form-data必须包含一个特殊的头部信息:Content-Type,其值也必须指定为multipart/form-data。同时,还需要指定内容分隔符,用于分隔请求体中的多个post内容。比如文件内容和文本内容自然要分开,否则接收方无法正常解析和还原这个文件。具体的头部信息如:Content-Type:multipart/form-data;boundary=${bound},${bound}代表分隔符,可以任意指定,但是为了避免和普通文本重复,尽量使用更复杂的内容,比如::287032381131322multipart/form的请求体-data也是一个字符串,但是和post的requestbody不同的是它的构造方法。post是简单的name=value值连接,而multipart/form-data是添加Constructwithdelimiters等。维基百科上multipart的介绍。多部分数据格式具有某些特征。首先,在标头中指定${bound}。上例中的${bound}为::287032381131322,由多个内容相同的块组成。每块的格式是--添加${bound}开始,然后是这部分内容的描述信息,然后是一个\r\n,然后是描述信息的具体内容。如果传输的内容是文件,还会包含文件名信息和文件内容的类型。综上所述,发送multipart/form-data请求,需要自己定义${bound},按照格式发送请求即可。multipart数据格式就不多介绍了。感觉和chunked很像,不难理解。综上所述,本文介绍的三种数据格式都是比较基础的,有些框架会自动处理,比如爬虫。还有图片上传,获取多部分/数据格式请求标头的一些概念性内容也很有趣。共勉。参考文献都在文章中列出
