http響應頭首部Content-Length

本文講述4個問題html

  1. gzip編碼與Content-Length的關係node

  2. 分塊編碼與Content-Length的關係nginx

  3. file文件已經在服務端進行gzip壓縮,那爲什麼在node中用request請求這張圖片時(請求的方法爲head/get)返回頭首部Content-Length仍是未壓縮前的圖片大小?chrome

  4. 響應頭必定會包含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

d

可是在頁面中用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

內容編碼gzip介紹

Accept-Encoding和Content-Encoding是HTTP中用來對「採用何種編碼格式傳輸正文」進行協定的一對頭部字段。

工做原理:

瀏覽器發送請求時,經過Accept-Encoding帶上本身支持的內容編碼格式列表,服務端從中挑選一種用來對內容編碼,編好碼的數據就放在實體主體中,再經過Content-Encoding響應頭指明選定的格式,瀏覽器拿到相應正文後再依據Content-Encoding進行解壓。

具體過程:

  1. 網站服務器生成原始響應報文,其中有原始的Content-Type和Content-Length首部。

  2. 內容編碼服務器建立編碼後的報文,編碼後的報文有一樣的Content-Type和不一樣的Content-Length,同時增長了Content-Encoding首部。

  3. 接收程序獲得編碼後的報文,進行解碼,得到原始報文。

這就有了一系列問題:

  1. file文件已經在服務端進行gzip壓縮,那爲什麼在node中用request請求這張圖片時(請求的方法爲head)返回頭首部Content-Length仍是未壓縮前的圖片大小?

  2. 響應頭必定會包含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 實體的大小

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();

原創文章 歡迎轉載。

參考:

  1. https://imququ.com/post/conte...

  2. https://imququ.com/post/trans...

  3. HTTP權威指南

相關文章
相關標籤/搜索