實習中的一個主要工做就是分析 HTTP 中的協議,本身也用 Python 寫過正則表達式對 HTTP 請求和響應的內容進行匹配,而後把關鍵字段抽離出來放到一個字典中以備使用(能夠稍微改造一下就是一個爬蟲工具)。javascript
HTTP 協議中的不少坑,本身都遇到過,我就針對本身遇到的幾種 HTTP 常見的數據格式,來作一個總結。css
對於 Zlib,一點也不陌生,咱們平時用它來壓縮文件,常見類型有 zip、rar 和 7z 等。Zlib 是一種流行的文件壓縮算法,應用十分普遍,尤爲是在 Linux 平臺。當應用 Zlib 壓縮到一個純文本文件時,效果是很是明顯的,大約能夠減小70%以上的文件大小,這取決於文件中的內容。html
Zlib 也適用於 Web 數據傳輸,好比利用 Apache 中的 Gzip (後面會提到,一種壓縮算法) 模塊,咱們可使用 Gzip 壓縮算法來對 Apache 服務器發佈的網頁內容進行壓縮後再傳輸到客戶端瀏覽器。這樣通過壓縮後實際上下降了網絡傳輸的字節數,最明顯的好處就是能夠加快網頁加載的速度。java
網頁加載速度加快的好處不言而喻,節省流量,改善用戶的瀏覽體驗。而這些好處並不只僅限於靜態內容,PHP 動態頁面和其餘動態生成的內容都可以經過使用 Apache 壓縮模塊壓縮,加上其餘的性能調整機制和相應的服務器端 緩存規則,這能夠大大提升網站的性能。所以,對於部署在 Linux 服務器上的 PHP 程序,在服務器支持的狀況下,建議你開啓使用 Gzip Web 壓縮。node
壓縮算法不一樣,能夠產生不一樣的壓縮數據(目的都是爲了減少文件大小)。目前 Web 端流行的壓縮格式有兩種,分別是 Gzip 和 Defalte。python
Apache 中的就是 Gzip 模塊,Deflate 是同時使用了 LZ77 算法與哈夫曼編碼(Huffman Coding)的一個無損數據壓縮算法。Deflate 壓縮與解壓的源代碼能夠在自由、通用的壓縮庫 zlib 上找到。git
更高壓縮率的 Deflate 是 7-zip 所實現的。AdvanceCOMP 也使用這種實現,它能夠對 gzip、PNG、MNG 以及 ZIP 文件進行壓縮從而獲得比 zlib 更小的文件大小。在 Ken Silverman的 KZIP 與 PNGOUT 中使用了一種更加高效同時要求更多用戶輸入的 Deflate 程序。github
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服務器接收到瀏覽器的HTTP請求後,檢查瀏覽器是否支持HTTP壓縮(Accept-Encoding 信息);
若是瀏覽器支持HTTP壓縮,Web服務器檢查請求文件的後綴名;
若是請求文件是HTML、CSS等靜態文件,Web服務器到壓縮緩衝目錄中檢查是否已經存在請求文件的最新壓縮文件;
若是請求文件的壓縮文件不存在,Web服務器向瀏覽器返回未壓縮的請求文件,並在壓縮緩衝目錄中存放請求文件的壓縮文件;
若是請求文件的最新壓縮文件已經存在,則直接返回請求文件的壓縮文件;
若是請求文件是動態文件,Web服務器動態壓縮內容並返回瀏覽器,壓縮內容不存放到壓縮緩存目錄中。
說了這麼多,下面舉一個例子,打開抓包軟件,訪問咱們學校的官網( www.ecnu.edu.cn ),請求頭以下:
GET /_css/tpl2/system.css HTTP/1.1 Host: www.ecnu.edu.cn Connection: keep-alive User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.59 Safari/537.36 Accept: text/css,*/*;q=0.1 Referer: http://www.ecnu.edu.cn/ Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.8 Cookie: 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') //或者設置成 deflate request.add_header('Accept-encoding', 'deflate') //或者二者都設置 request.add_header('Accept-encoding', 'gzip, deflate')
服務器給的響應通常以下:
HTTP/1.1 200 OK Date: Sat, 22 Oct 2016 11:41:19 GMT Content-Type: text/javascript;charset=utf-8 Transfer-Encoding: chunked Connection: close Vary: Accept-Encoding tracecode: 24798560510951725578102219 Server: Apache Content-Encoding: gzip 400a ............ks#I. ...W...,....>..T..]..Z...Y..].MK..2..L..(略) //響應體爲壓縮數據
從響應頭來看,Content-Encoding: gzip
這段話說明響應體的壓縮方式是 gzip 壓縮,通常有幾種狀況,字段爲空表示明文無壓縮,還有 Content-Encoding: gzip 和 Content-Encoding: deflate 兩種。
實際上 Gzip 網站要遠比 Deflate 多,以前寫過一個簡單爬蟲從 hao123
的主頁開始爬,爬幾千個網頁(基本涵蓋全部經常使用的),專門分析響應體的壓縮類型,獲得的結果是:
Accept-Encoding 不設置參數:會返回一個無壓縮的響應體(瀏覽器比較特別,他們會自動設置 Accept-Encoding: gzip: deflate 來提升傳輸速度);
Accept-Encoding: gzip,100% 的網站都會返回 gzip 壓縮,但不保證互聯網全部網站都支持 gzip(萬一沒開啓);
Accept-Encoding: deflate:只有不到 10% 的網站返回一個 deflate 壓縮的響應,其餘的則返回一個沒有壓縮的響應體。
Accept-Encoding: gzip, deflate:返回的結果也都是 gzip 格式的數據,說明在優先級上 gzip 更受歡迎。
響應頭的 Encoding 字段頗有幫助,好比咱們寫個正則表達式匹配響應頭是什麼壓縮:
(?<=Content-Encoding: ).+(?=\r\n)
匹配到內容爲空說明沒有壓縮,爲 gzip
說明響應體要通過 gzip 解壓,爲 deflate
說明爲 deflate 壓縮。
在python中有zlib庫,它能夠解決gzip、deflate和zlib壓縮。這三種對應的壓縮方式分別是:
RFC 1950 (zlib compressed format) RFC 1951 (deflate compressed format) RFC 1952 (gzip compressed format)
雖然說是 Python 庫,可是底層仍是 C(C++) 來實現的,這個 http-parser
也是 C 實現的源碼,Nodejs 的 http-parser
也是 C 實現的源碼,zlib
的 C 源碼在這裏。C 真的好牛逼呀!
在解壓縮的過程當中,須要選擇 windowBits 參數:
to (de-)compress deflate format, use wbits = -zlib.MAX_WBITS to (de-)compress zlib format, use wbits = zlib.MAX_WBITS 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包來解決,能夠參考下面的代碼:
>>> import gzip >>> import StringIO >>> fio = StringIO.StringIO(gzip_data) >>> f = gzip.GzipFile(fileobj=fio) >>> f.read() 'test' >>> f.close()
也能夠在解壓的時候自動加入頭檢測,把32加入頭中就能夠觸發頭檢測,例如:
>>> zlib.decompress(gzip_data, zlib.MAX_WBITS|32) 'test' >>> zlib.decompress(zlib_data, zlib.MAX_WBITS|32) 'test'
以上參考 stackoverflow How can I decompress a gzip stream with zlib?。
剛接觸這些東西的時候,天天都會稀奇古怪的報一些錯誤,基本上 Google 一下都能解決。
分塊傳輸編碼(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 格式的響應體。
HTTP/1.1 200 OK Date: Wed, 06 Jul 2016 06:59:55 GMT Server: Apache Accept-Ranges: bytes Transfer-Encoding: chunked Content-Type: text/html Content-Encoding: gzip Age: 35 X-Via: 1.1 daodianxinxiazai58:88 (Cdn Cache Server V2.0), 1.1 yzdx147:1 (Cdn Cache Server V2.0) Connection: keep-alive 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...... 0
Transfer-Encoding: chunked
字段能夠看出響應體是否爲 chunked 壓縮,chunked 數據頗有意思,採用的格式是 長度\r\n內容\r\n長度\r\n..0\r\n,並且長度仍是十六進制的,最後以 0\r\n 結尾(不保證都有)。由於上面的數據是 gzip 壓縮,看起來不夠直觀,下面舉個簡單的例子:
5\r\n ababa\r\n f\r\n 123451234512345\r\n 14\r\n 12345123451234512345\r\n 0\r\n
上述例子 chunked 解碼後的數據 ababa12345...
,另外 \r\n 是不可見的,我手動加的。
和 gzip 同樣,同樣能夠寫一個正則表達式來匹配:
(?<=Transfer-Encoding: ).+(?=\r\n)
從前面的介紹能夠知道,response-body 部分其實由 length(1) \r\n data(1) \r\n length(2) \r\n data(2)…… 循環組成,經過下面的函數進行處理,再根據壓縮類型解壓出最終的數據。
Python 處理的過程以下:
unchunked = b'' pos = 0 while pos <= len(data): chunkNumLen = data.find(b'\r\n', pos)-pos //從第一個元素開始,發現第一個\r\n,計算length長度 chunkLen=int(data[pos:pos+chunkNumLen], 16) //把length的長度轉換成int if chunkLen == 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位置向後移動 return unchunked //此時處理後unchunked就是普通的壓縮數據,能夠用zlib解壓函數進行解壓
實際中,咱們會同時遇到既時 chunked 又是壓縮數據的響應,這個時候處理的思路應該是:先處理 chunked,在處理壓縮數據,順序不能反。
MultiPart 的本質就是 Post 請求,MultiPart出如今請求中,用來對一些文件(圖片或文檔)進行處理,在請求頭中出現 Content-Type: multipart/form-data; boundary=::287032381131322
則表示爲 MultiPart 格式數據包,下面這個是 multipart 數據包格式:
POST /cgi-bin/qtest HTTP/1.1 Host: aram User-Agent: Mozilla/5.0 Gecko/2009042316 Firefox/3.0.10 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: en-us,en;q=0.5 Accept-Encoding: gzip,deflate Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7 Keep-Alive: 300 Connection: keep-alive Referer: http://aram/~martind/banner.htm Content-Type: multipart/form-data; boundary=::287032381131322 Content-Length: 514 --::287032381131322 Content-Disposition: form-data; name="datafile1"; filename="r.gif" Content-Type: image/gif GIF87a.............,...........D..; --::287032381131322 Content-Disposition: form-data; name="datafile2"; filename="g.gif" Content-Type: image/gif GIF87a.............,...........D..; --::287032381131322 Content-Disposition: form-data; name="datafile3"; filename="b.gif" Content-Type: image/gif GIF87a.............,...........D..; --::287032381131322—
http 協議自己的原始方法不支持 multipart/form-data 請求,那這個請求天然就是由這些原始的方法演變而來的,具體如何演變且看下文:
multipart/form-data 的基礎方法是 post,也就是說是由 post 方法來組合實現的
multipart/form-data 與 post 方法的不一樣之處:請求頭,請求體。
multipart/form-data 的請求頭必須包含一個特殊的頭信息:Content-Type,且其值也必須規定爲 multipart/form-data,同時還須要規定一個內容分割符用於分割請求體中的多個 post 內容,如文件內容和文本內容天然須要分割,否則接收方就沒法正常解析和還原這個文件。具體的頭信息如:Content-Type: multipart/form-data; boundary=${bound},${bound} 表明分割符,能夠任意規定,但爲了不和正常文本重複,儘可能使用複雜一點的內容,如::287032381131322
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 格式的請求頭,瞭解一些概念性的東西也很是有意思。共勉。
參考全列在文章中了
歡迎來個人博客交流。