Http Header的Transfer-Encoding

Transfer-Encoding,是一個 HTTP 頭部字段,字面意思是「傳輸編碼」。實際上,HTTP 協議中還有另一個頭部與編碼有關:Content-Encoding(內容編碼)。Content-Encoding 一般用於對實體內容進行壓縮編碼,目的是優化傳輸,例如用 gzip 壓縮文本文件,能大幅減少體積。內容編碼一般是選擇性的,例如 jpg / png 這類文件通常不開啓,由於圖片格式已是高度壓縮過的,再壓一遍沒什麼效果不說還浪費 CPU。php

而 Transfer-Encoding 則是用來改變報文格式,它不但不會減小實體內容傳輸大小,甚至還會使傳輸變大,那它的做用是什麼呢?本文接下來主要就是講這個。咱們先記住一點,Content-Encoding 和 Transfer-Encoding 兩者是相輔相成的,對於一個 HTTP 報文,極可能同時進行了內容編碼和傳輸編碼。html

Persistent Connection

暫時把 Transfer-Encoding 放一邊,咱們來看 HTTP 協議中另一個重要概念:Persistent Connection(持久鏈接,通俗說法長鏈接)。咱們知道 HTTP 運行在 TCP 鏈接之上,天然也有着跟 TCP 同樣的三次握手、慢啓動等特性,爲了儘量的提升 HTTP 性能,使用持久鏈接就顯得尤其重要了。爲此,HTTP 協議引入了相應的機制。nginx

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:緩存

1 JSrequire('net').createServer(function(sock) {
2     sock.on('data', function(data) {
3         sock.write('HTTP/1.1 200 OK\r\n');
4         sock.write('\r\n');
5         sock.write('hello world!');
6         sock.destroy();
7     });
8 }).listen(9090, '127.0.0.1');

啓動服務後,在瀏覽器裏訪問 127.0.0.1:9090,正確輸出了指定內容,一切正常。去掉 sock.destroy() 這一行,讓它變成持久鏈接,重啓服務後再訪問一下。此次的結果就有點奇怪了:遲遲看不到輸出,經過 Network 查看請求狀態,一直是 pending。性能優化

這是由於,對於非持久鏈接,瀏覽器能夠經過鏈接是否關閉來界定請求或響應實體的邊界;而對於持久鏈接,這種方法顯然不奏效。上例中,儘管我已經發送完全部數據,但瀏覽器並不知道這一點,它沒法得知這個打開的鏈接上是否還會有新數據進來,只能傻傻地等了。網絡

Content-Length

要解決上面這個問題,最容易想到的辦法就是計算實體長度,並經過頭部告訴對方。這就要用到 Content-Length 了,改造一下上面的例子:性能

1 JSrequire('net').createServer(function(sock) {
2     sock.on('data', function(data) {
3         sock.write('HTTP/1.1 200 OK\r\n');
4         sock.write('Content-Length: 12\r\n');
5         sock.write('\r\n');
6         sock.write('hello world!');
7     });
8 }).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,對應的分塊數據沒有內容,表示實體結束。按照這個格式改造下以前的代碼:

 1 JSrequire('net').createServer(function(sock) {
 2     sock.on('data', function(data) {
 3         sock.write('HTTP/1.1 200 OK\r\n');
 4         sock.write('Transfer-Encoding: chunked\r\n');
 5         sock.write('\r\n');
 6 
 7         sock.write('b\r\n');
 8         sock.write('01234567890\r\n');
 9 
10         sock.write('5\r\n');
11         sock.write('12345\r\n');
12 
13         sock.write('0\r\n');
14         sock.write('\r\n');
15     });
16 }).listen(9090, '127.0.0.1');

上面這個例子中,我在響應頭中代表接下來的實體會採用分塊編碼,而後輸出了 11 字節的分塊,接着又輸出了 5 字節的分塊,最後用一個 0 長度的分塊代表數據已經傳完了。用瀏覽器訪問這個服務,能夠獲得正確結果。能夠看到,經過這種簡單的分塊策略,很好的解決了前面提出的問題。

前面說過 Content-Encoding 和 Transfer-Encoding 兩者常常會結合來用,其實就是針對進行了內容編碼(壓縮)的內容再進行傳輸編碼(分塊)。下面是我用 telnet 請求測試頁面獲得的響應,能夠看到對 gzip 內容進行的分塊:

 1 BASH> telnet 106.187.88.156 80
 2 
 3 GET /test.php HTTP/1.1
 4 Host: qgy18.qgy18.com
 5 Accept-Encoding: gzip
 6 
 7 HTTP/1.1 200 OK
 8 Server: nginx
 9 Date: Sun, 03 May 2015 17:25:23 GMT
10 Content-Type: text/html
11 Transfer-Encoding: chunked
12 Connection: keep-alive
13 Content-Encoding: gzip
14 
15 1f
16 �H���W(�/�I�J
17 
18 0
相關文章
相關標籤/搜索