关于http中Transfer-Encoding
2019獨角獸企業重金招聘Python工程師標準>>>
本文作為我的博客「HTTP 相關」專題新的一篇,主要討論 HTTP 協議中的 Transfer-Encoding。這個專題我會根據自己的理解,以盡量通俗的講述,結合代碼示例和實際場景來說明問題,歡迎大家關注和留言交流。
Transfer-Encoding,是一個 HTTP 頭部字段,字面意思是「傳輸編碼」。實際上,HTTP 協議中還有另外一個頭部與編碼有關:Content-Encoding(內容編碼)。Content-Encoding 通常用于對實體內容進行壓縮編碼,目的是優化傳輸,例如用 gzip 壓縮文本文件,能大幅減小體積。內容編碼通常是選擇性的,例如 jpg / png 這類文件一般不開啟,因為圖片格式已經是高度壓縮過的,再壓一遍沒什么效果不說還浪費 CPU。
而 Transfer-Encoding 則是用來改變報文格式,它不但不會減少實體內容傳輸大小,甚至還會使傳輸變大,那它的作用是什么呢?本文接下來主要就是講這個。我們先記住一點,Content-Encoding 和 Transfer-Encoding 二者是相輔相成的,對于一個 HTTP 報文,很可能同時進行了內容編碼和傳輸編碼。
Persistent Connection
暫時把 Transfer-Encoding 放一邊,我們來看 HTTP 協議中另外一個重要概念:Persistent Connection(持久連接,通俗說法長連接)。我們知道 HTTP 運行在 TCP 連接之上,自然也有著跟 TCP 一樣的三次握手、慢啟動等特性,為了盡可能的提高 HTTP 性能,使用持久連接就顯得尤為重要了。為此,HTTP 協議引入了相應的機制。
HTTP/1.0 的持久連接機制是后來才引入的,通過?Connection: keep-alive?這個頭部來實現,服務端和客戶端都可以使用它告訴對方在發送完數據之后不需要斷開 TCP 連接,以備后用。HTTP/1.1 則規定所有連接都必須是持久的,除非顯式地在頭部加上?Connection: close。所以實際上,HTTP/1.1 中 Connection 這個頭部字段已經沒有 keep-alive 這個取值了,但由于歷史原因,很多 Web Server 和瀏覽器,還是保留著給 HTTP/1.1 長連接發送?Connection: keep-alive?的習慣。
瀏覽器重用已經打開的空閑持久連接,可以避開緩慢的三次握手,還可以避免遇上 TCP 慢啟動的擁塞適應階段,聽起來十分美妙。為了深入研究持久連接的特性,我決定用 Node 寫一個最簡單的 Web Server 用于測試,Node 提供了?http?模塊用于快速創建 HTTP Web Server,但我需要更多的控制,所以用?net?模塊創建了一個 TCP Server:
JSrequire('net').createServer(function(sock) {sock.on('data', function(data) {sock.write('HTTP/1.1 200 OK\r\n');sock.write('\r\n');sock.write('hello world!');sock.destroy();}); }).listen(9090, '127.0.0.1');啟動服務后,在瀏覽器里訪問 127.0.0.1:9090,正確輸出了指定內容,一切正常。去掉?sock.destroy()?這一行,讓它變成持久連接,重啟服務后再訪問一下。這次的結果就有點奇怪了:遲遲看不到輸出,通過 Network 查看請求狀態,一直是 pending。
這是因為,對于非持久連接,瀏覽器可以通過連接是否關閉來界定請求或響應實體的邊界;而對于持久連接,這種方法顯然不奏效。上例中,盡管我已經發送完所有數據,但瀏覽器并不知道這一點,它無法得知這個打開的連接上是否還會有新數據進來,只能傻傻地等了。
Content-Length
要解決上面這個問題,最容易想到的辦法就是計算實體長度,并通過頭部告訴對方。這就要用到?Content-Length?了,改造一下上面的例子:
JSrequire('net').createServer(function(sock) {sock.on('data', function(data) {sock.write('HTTP/1.1 200 OK\r\n');sock.write('Content-Length: 12\r\n');sock.write('\r\n');sock.write('hello world!');}); }).listen(9090, '127.0.0.1');可以看到,這次發送完數據并沒有關閉 TCP 連接,但瀏覽器能正常輸出內容并結束請求,因為瀏覽器可以通過?Content-Length?的長度信息,判斷出響應實體已結束。那如果 Content-Length 和實體實際長度不一致會怎樣?有興趣的同學可以自己試試,通常如果?Content-Length?比實際長度短,會造成內容被截斷;如果比實體內容長,會造成 pending。
由于?Content-Length?字段必須真實反映實體長度,但實際應用中,有些時候實體長度并沒那么好獲得,例如實體來自于網絡文件,或者由動態語言生成。這時候要想準確獲取長度,只能開一個足夠大的 buffer,等內容全部生成好再計算。但這樣做一方面需要更大的內存開銷,另一方面也會讓客戶端等更久。
我們在做 WEB 性能優化時,有一個重要的指標叫 TTFB(Time To First Byte),它代表的是從客戶端發出請求到收到響應的第一個字節所花費的時間。大部分瀏覽器自帶的 Network 面板都可以看到這個指標,越短的 TTFB 意味著用戶可以越早看到頁面內容,體驗越好。可想而知,服務端為了計算響應實體長度而緩存所有內容,跟更短的 TTFB 理念背道而馳。但在 HTTP 報文中,實體一定要在頭部之后,順序不能顛倒,為此我們需要一個新的機制:不依賴頭部的長度信息,也能知道實體的邊界。
Transfer-Encoding: chunked
本文主角終于再次出現了,Transfer-Encoding?正是用來解決上面這個問題的。歷史上?Transfer-Encoding?可以有多種取值,為此還引入了一個名為?TE?的頭部用來協商采用何種傳輸編碼。但是最新的 HTTP 規范里,只定義了一種傳輸編碼:分塊編碼(chunked)。
分塊編碼相當簡單,在頭部加入?Transfer-Encoding: chunked?之后,就代表這個報文采用了分塊編碼。這時,報文中的實體需要改為用一系列分塊來傳輸。每個分塊包含十六進制的長度值和數據,長度值獨占一行,長度不包括它結尾的 CRLF(\r\n),也不包括分塊數據結尾的 CRLF。最后一個分塊長度值必須為 0,對應的分塊數據沒有內容,表示實體結束。按照這個格式改造下之前的代碼:
JSrequire('net').createServer(function(sock) {sock.on('data', function(data) {sock.write('HTTP/1.1 200 OK\r\n');sock.write('Transfer-Encoding: chunked\r\n');sock.write('\r\n');sock.write('b\r\n');sock.write('01234567890\r\n');sock.write('5\r\n');sock.write('12345\r\n');sock.write('0\r\n');sock.write('\r\n');}); }).listen(9090, '127.0.0.1');上面這個例子中,我在響應頭中表明接下來的實體會采用分塊編碼,然后輸出了 11 字節的分塊,接著又輸出了 5 字節的分塊,最后用一個 0 長度的分塊表明數據已經傳完了。用瀏覽器訪問這個服務,可以得到正確結果。可以看到,通過這種簡單的分塊策略,很好的解決了前面提出的問題。
前面說過 Content-Encoding 和 Transfer-Encoding 二者經常會結合來用,其實就是針對進行了內容編碼(壓縮)的內容再進行傳輸編碼(分塊)。下面是我用 telnet 請求測試頁面得到的響應,可以看到對 gzip 內容進行的分塊:
BASH> telnet 106.187.88.156 80GET /test.php HTTP/1.1 Host: qgy18.qgy18.com Accept-Encoding: gzipHTTP/1.1 200 OK Server: nginx Date: Sun, 03 May 2015 17:25:23 GMT Content-Type: text/html Transfer-Encoding: chunked Connection: keep-alive Content-Encoding: gzip1f �H���W(�/�I�J0用 HTTP 抓包神器?Fiddler?也可以看到類似結果,有興趣的同學可以自己試一下。
本文鏈接:https://imququ.com/post/transfer-encoding-header-in-http.html,參與評論 ?
?
轉載于:https://my.oschina.net/u/2963099/blog/1592113
《新程序員》:云原生和全面數字化實踐50位技術專家共同創作,文字、視頻、音頻交互閱讀總結
以上是生活随笔為你收集整理的关于http中Transfer-Encoding的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 乾颐堂安德HCIE课程3-OSPF的精华
- 下一篇: 【Sphinx】MySQL+Sphinx