本文講述4個問題html
gzip編碼與Content-Length的關係node
分塊編碼與Content-Length的關係nginx
file文件已經在服務端進行gzip壓縮,那爲什麼在node中用request請求這張圖片時(請求的方法爲head/get)返回頭首部Content-Length仍是未壓縮前的圖片大小?chrome
響應頭必定會包含Content-Length首部嗎?瀏覽器
在圖片性能監控腳本中對站內某頁面的全部圖片進行請求(採用request模塊),以獲取圖片的具體大小。(經過response.headers的Content-Length)服務器
// 部分代碼 request.head('http://file.showjoy.com/images/46/4695c19423cd40469fb89836373c1d45.jpg', function (error, response, body) { console.log(response.headers['content-cength']) // 'content-length': '812401' })
對超過閾值的圖片會郵件進行提醒:post
可是在頁面中用chrome查看這張圖片時,卻發現size只有227kb。性能
因爲file
開頭的資源是文件服務器上的資源,將file改爲cdn1再作嘗試。可見圖片尺寸大小仍是812kb。網站
cdn1上的資源是由文件服務器上的資源同步上去的,這也意味着相同的資源size不一樣。形成這樣的問題頗有多是服務器對資源作了壓縮處理。編碼
cdn http響應頭 Accept-Ranges:bytes Access-Control-Allow-Origin:* Access-Control-Expose-Headers:X-Log, X-Reqid Access-Control-Max-Age:2592000 Cache-Control:public, max-age=86400 Connection:keep-alive Content-Disposition:inline; filename="4695c19423cd40469fb89836373c1d45.jpg" Content-Length:812401 Content-Transfer-Encoding:binary Content-Type:image/jpeg Date:Thu, 04 Aug 2016 10:24:19 GMT ETag:"FqxG8os4S6llXHllO2ublrIKagfY" Last-Modified:Mon, 11 Apr 2016 09:05:23 GMT Server:nginx X-Log:mc.g;IO:1 X-Qiniu-Zone:0 X-Reqid:6VUAAKhrk3UAkmcU X-Via:1.1 zhenjiang173:4 (Cdn Cache Server V2.0), 1.1 yzh229:1 (Cdn Cache Server V2.0)
file文件服務器 http響應頭 HTTP/1.1 200 OK Server: Tengine Date: Thu, 04 Aug 2016 10:23:45 GMT Content-Type: image/jpeg Last-Modified: Mon, 11 Apr 2016 09:01:56 GMT Transfer-Encoding: chunked Connection: keep-alive Vary: Accept-Encoding Expires: Thu, 31 Dec 2037 23:55:55 GMT Cache-Control: max-age=315360000 Content-Encoding: gzip
能夠看出文件服務器開啓了Content-Encoding: gzip
Accept-Encoding和Content-Encoding是HTTP中用來對「採用何種編碼格式傳輸正文」進行協定的一對頭部字段。
工做原理:
瀏覽器發送請求時,經過Accept-Encoding帶上本身支持的內容編碼格式列表,服務端從中挑選一種用來對內容編碼,編好碼的數據就放在實體主體中,再經過Content-Encoding響應頭指明選定的格式,瀏覽器拿到相應正文後再依據Content-Encoding進行解壓。
具體過程:
網站服務器生成原始響應報文,其中有原始的Content-Type和Content-Length首部。
內容編碼服務器建立編碼後的報文,編碼後的報文有一樣的Content-Type和不一樣的Content-Length,同時增長了Content-Encoding首部。
接收程序獲得編碼後的報文,進行解碼,得到原始報文。
這就有了一系列問題:
file文件已經在服務端進行gzip壓縮,那爲什麼在node中用request請求這張圖片時(請求的方法爲head)返回頭首部Content-Length仍是未壓縮前的圖片大小?
響應頭必定會包含Content-Length首部嗎?
<!--響應頭--> <!--首行--> HTTP/1.1 200 OK <!--首部--> Server: Tengine Date: Fri, 05 Aug 2016 08:08:05 GMT Connection: keep-alive Vary: Accept-Encoding Transfer-Encoding: chunked <!--實體首部--> Content-Type: image/jpeg Cache-Control: max-age=315360000 Content-Encoding: gzip Expires: 0 Cache-Control: no-cache <!--實體主體--> 內容...
HTTP實體首部描述了HTTP報文的內容。在這裏能夠將實體首部和實體主體想象結合成一個箱子,實體首部是箱子上部的描述信息,實體主體則是箱子內貨真價實的物品。
響應頭的實體首部列表:
Content-Type
Content-Length
Content-Language
Content-Encoding
Content-Location
Content-Range
Content-MD5
Last-Modified
Expires
Allow
ETag
Cache-Control
http的head方法與get方法返回的首行和首部徹底相同,不一樣的是get方法的響應頭中會有主體,而head方法在響應中只返回首部,不會返回主體部分,這就容許客戶端在未獲取實際資源的狀況下,對資源的首部進行檢查。
因此在編寫爬蟲的時候使用了request.head
方法,快捷地獲取所須要的Content-Length
。可是爲什麼獲取的Content-Length是gzip壓縮前的大小呢?
Content-Length首部指示出報文實體主體的字節大小,這個大小是包含了全部內容編碼的。好比對文本文件進行了gzip壓縮的話,Content-Length首部就是壓縮後的大小,而不是原始大小。
另外Content-Length首部對於長鏈接是必不可少的,長鏈接表明在鏈接期間會有多個http請求響應在排隊,而服務器不可以關閉鏈接,客戶端只能經過Content-Length知道一條報文在哪裏結束,下一條報文在哪裏開始。
除非使用了分塊編碼Transfer-Encoding: chunked
,不然響應頭首部必須使用Content-Length首部。 [摘自http權威指南]
分塊編碼把「報文」分割成若干個大小已知的塊,塊之間是緊挨着發送的,這樣就不須要在發送以前知道整個報文的大小了。(也意味着不須要寫回Content-Length首部了)
當使用持久鏈接時,在服務器寫主體以前,必須知道它的大小並在Content-Length首部中發送。若是服務器動態建立內容,可能在發送以前沒法知道主體大小,分塊編碼就是爲了解決這種狀況。服務器把主體逐塊發送,說明每一塊的大小。服務器再用大小爲0的塊做爲結束塊。,爲下一個響應作準備。
再回過頭看請求file文件服務器的圖片時響應頭的首部信息發現了這個首部:Transfer-Encoding: chunked
這也說明了這個圖片請求的響應是採用分塊編碼的傳輸方式,採用這種傳輸方式進行響應時,不必帶上Content-Length
這個首部信息。由於即便帶上了也是不許確的。再回過頭看上述file圖片的響應頭中確實沒有Content-Length首部。
可是在node中請求http://file.showjoy.com/images/46/4695c19423cd40469fb89836373c1d45.jpg
這張圖片,是能取得Content-Length的,這是什麼緣由?
request.head('http://file.showjoy.com/images/46/4695c19423cd40469fb89836373c1d45.jpg', function (error, response, body) { console.log(response.headers) }) ===> { server: 'Tengine', date: 'Sun, 07 Aug 2016 04:13:08 GMT', 'content-type': 'image/jpeg', 'content-length': '812401', 'last-modified': 'Mon, 11 Apr 2016 09:01:56 GMT', connection: 'close', vary: 'Accept-Encoding', etag: '"570b6804-c6571"', expires: 'Thu, 31 Dec 2037 23:55:55 GMT', 'cache-control': 'max-age=315360000', 'accept-ranges': 'bytes' }
上述緣由在於在node中請求須要加上Accept-Encoding': 'gzip'
首部信息,讓服務器知道這個請求支持gzip壓縮。不加這個首部,服務器就不會採起gzip壓縮,同時我司服務器設定也不進行分塊編碼。因此返回響應頭的Content-Length
首部是必須的,可是這個值的大小確定是沒有進行過壓縮的。
request( { method: 'get' , uri: 'http://file.showjoy.com/images/46/4695c19423cd40469fb89836373c1d45.jpg' , gzip: true } , function (error, response, body) { // body is the decompressed response body console.log(response.headers) } ) ===> { server: 'Tengine', date: 'Sun, 07 Aug 2016 04:16:10 GMT', 'content-type': 'image/jpeg', 'last-modified': 'Mon, 11 Apr 2016 09:01:56 GMT', 'transfer-encoding': 'chunked', connection: 'close', vary: 'Accept-Encoding', expires: 'Thu, 31 Dec 2037 23:55:55 GMT', 'cache-control': 'max-age=315360000', 'content-encoding': 'gzip' }
同理上面請求示例開啓了gzip,文件服務器根據這個標識同時開啓了分塊編碼,導致Content-Length
沒有返回。
那麼如何獲取開啓了分塊編碼的響應的文件大小呢?如下是node http原生寫法,供你們參考。
var options = { hostname: 'file.showjoy.com', port: 80, path: '/images/7a/7add9f81cd9b41a6aba6271c69719f71.jpg', method: 'GET', headers: { 'Accept-Encoding': 'gzip' } }; var req = http.request(options, function(res) { console.log('STATUS: ' + res.statusCode); console.log('HEADERS: ' + JSON.stringify(res.headers)); var chunks = []; res.on('data', function (chunk) { chunks.push(chunk); }) res.on('end', function () { console.log('SIZE: ' + chunks.toString().length) // file size }) }) req.write('data\n'); req.end();
原創文章 歡迎轉載。
參考: