Http權威指南筆記(十二)——實體與編碼

本章會對HTTP實體和編碼進行學習。這裏的實體是指HTTP中真正需要傳輸的實體內容(比如一張圖片,一份文檔)。這裏的編碼主要是指內容編碼和傳輸編碼。

1 報文與實體

如果將HTTP對內容的傳輸比喻成實際生活中一些貨物的運輸的化。那HTTP報文就相當於是用於運輸貨物的“箱子”,而實體內容則是我們真正需要運輸的“貨物”。所以實體也就是被封裝在了報文當中。

現實貨物運輸中,一般箱子上也會有一些描述信息,用於對運輸的貨物進行描述說明。HTTP報文中也是一樣,也會有相應的實體首部,對實體內容進行描述說明,以便我們的接收方能正確處理實體內容。在HTTP/1.1中常用的實體首部如下所示:

  • Content-Type:實體內容的MIME類型
  • Content-Length:實體內容的長度或大小
  • Content-Language:傳輸的實體內容最匹配的語言類型
  • Content-Encoding:對實體數據所做的變換(如:壓縮)
  • Content-Location:實體數據的一個備用位置
  • Content-Range:將實體分爲幾個部分進行傳輸的時候,說明該部分實體是屬於哪個部分
  • Content-MD5:內容數據的校驗和
  • Last-Modified:所傳輸內容最近一次創建或者修改時間
  • Expires:數據的失效時間
  • Allow:該資源所允許的請求方法(如:GET, HEAD等)
  • ETag:該份實體數據的唯一驗證碼
  • Cache-Control:實體內容緩存有關的控制

上述的首部內容,大部分在之前的文章中已經介紹過了,這裏就不再詳細介紹了。這裏我們會對部分之前沒有詳細介紹的首部進行一個介紹,說明其中的一些注意事項。

2 實體大小(長度)控制

Content-Length是用於支持實體內容的大小或者長度的首部。此處的大小是指內容編碼之後的大小。比如我們傳輸一份文檔,在傳輸之前我們進行了gzip壓縮,最終傳輸的是這份壓縮後的文檔,此時,這裏的Content-Length的大小是指gzip壓縮後的大小,而不是原始文檔大小。

2.1 一些注意事項

  1. 正確攜帶Content-Length首部
    同時爲了接收端能正確處理實體內容,除非是使用分塊編碼,其他情況都應該帶上Content-Length的值。有了這個值,通過對收到的內容實際長度和該首部的值進行對比,接收端就能正確判斷報文是正常結束,還是因爲連接斷開等其他非正常結束。
  2. 要避免給出錯誤的Content-Length的值
    Content-Length的值錯誤比缺少Content-Length首部帶來的影響還要打。缺少該首部是在某些情況會影響到對內容的處理,但是如果值錯誤,這個時候接收端始終會認爲收到的內容有誤,無法正確處理內容。
  3. 持久連接必須攜帶Content-Length
    持久連接是必須有Content-Length首部。因爲持久連接一直處於連接狀態,接收端是無法從關閉連接等其他方式得知報文結束的,就必須通過Content-Length的值來判斷報文再哪裏結束,下一條報文從哪裏開始。

2.2 確定實體(大小)長度的規則

既然Content-Length首部對於正確處理實體內容如此重要,正常情況也需要給出該首部正確的值,但是並不是所有的服務器或者客戶端都嚴格遵循這一規定。實際處理當中,爲了正確確定實體內容的長度,我們一般遵循如下幾條規則:

  1. 如果是某些特定不允許攜帶有實體內容的報文類型,直接忽略Content-Length首部(比如HEAD響應和GET一樣,也應該有Content-Length首部,但是其是不會攜帶實體內容的,這個時候我們直接忽略該首部即可)
  2. 如果報文中含有Transfer-Encoding首部(不採用HTTP默認的恆等編碼),這個時候判斷報文結束是根據一個稱爲“零字節塊”(zero-byte chunk)的特殊模式,或者是因爲連接斷開關閉。也就意味着這個時候如果即使有Content-Length首部,也應該被忽略。
  3. 如果報文類型允許攜帶實體內容,同時也沒有非恆等的Transfer-Encoding首部,這個時候,就應該以Content-Length首部的值就是報文實體的長度。
  4. 如果報文使用了multipart/byteranges(多部分 / 字節範圍)媒體類型,允許報文可以不攜帶Content-Length首部,這個時候每個分塊就必須說明自己的大小,接收端可以通過這個數據判斷出整個實體內容的大小。但是使用該種方式有個前提,是發送端要知道接收端是能識別和處理這種類型的報文,否則就不能使用這種類型。
  5. 如果對於以上幾種規則都不匹配,這個時候默認就以連接關閉爲報文結束。但是這種方式一般只有服務器對客戶端使用。因爲客戶端如果對服務器使用這種方式,服務端的響應就沒法送回客戶端了。如果客戶端非要使用這種方式,可以通過半關閉的方式(即先關閉發送端,接收端保持連接),具體信息可以查閱Http權威指南筆記(四)——連接管理

3 實體摘要

現在我們的請求很多時候都會經過一些代理。如果我們中間的代理處於有誤,或者轉碼出現不兼容等問題的時候,就可能會造成我們收到的報文實體是不正確的。這個時候爲了接收端能夠自己進行驗證收到實體是否正確,發送端可以生成一個實體數據的校驗和給接收端用於檢測報文的完整性。該校驗和正常的中間代理是不會對此進行修改的。不過這種方式只能防止非惡意的修改,如果是惡意修改,除了修改報文實體也會同時修改校驗和,這個時候接收端是無法判斷的。

一般校驗和會放入Content-MD5首部進行傳輸。應該注意的是該校驗對是在內容編碼之後(如果存在內容編碼),傳輸編碼之前(如果存在傳輸編碼)的數據進行計算生成的。所以接收端在收到報文後,需要先進行傳輸編碼的解碼,然後對解碼後的內容進行驗證。

4 媒體類型和字符集

Content-Type首部用於說明實體主體的MIME類型。關於MIME類型,使用的是在IANA中註冊標準MIME類型,一般格式爲“主類型/子類型”,如:text/html, img/jpeg等。這裏就不詳細說明了,感興趣的可以自己搜索下相關資料。這裏需要注意一點是,該首部說明的是內容編碼之前的,也就是原始實體內容的類型。

該首部除了說明實體的MIME類型外,還可以說明實體所使用的字符編碼信息,如:
Content-Type: text/html; charset=utf-8
字符的信息,一般是跟在MIME類型後面,用charset進行指定。字符編碼的詳細信息我們將在下一篇文章進行介紹。

指定的MIMI類型中,有個稍微特殊的類型就是多部分媒體類型。一般我們在提交表格或者將文檔內容作爲多個片段進行傳世的時候會用到。

HTTP 使用 Content-Type:multipart/form-data 或 Content-Type:multipart/mixed 這樣的首部以及多部分主體來發送這種請求,舉例如下:
Content-Type: multipart/form-data; boundary=[abcdefghijklmnopqrstuvwxyz]
其中boundary用於說明用於分隔各個部分的字符串,如下面的傳輸內容是一段提交表單內容的示例:

Content-Type: multipart/form-data; boundary=AaB03x
--AaB03x
Content-Disposition: form-data; name="submit-name"
Sally
--AaB03x
Content-Disposition: form-data; name="files"; filename="essayfile.txt"
Content-Type: text/plain
...contents of essayfile.txt...
--AaB03x--

同時這種方式還可以進行嵌套的,比如我們在上述傳輸表單普通文本內容的基礎上,增加一個附件的功能(傳輸一個text文件和一張gif圖片),報文示例如下:

Content-Type: multipart/form-data; boundary=AaB03x
--AaB03x
Content-Disposition: form-data; name="submit-name"
Sally
--AaB03x
Content-Disposition: form-data; name="files"
Content-Type: multipart/mixed; boundary=BbC04y
--BbC04y
Content-Disposition: file; filename="essayfile.txt"
Content-Type: text/plain
...contents of essayfile.txt...
--BbC04y”
Content-Disposition: file; filename="imagefile.gif"
Content-Type: image/gif
Content-Transfer-Encoding: binary
...contents of imagefile.gif...
--BbC04y--
--AaB03x--

可以看到,整個表單的內容使用多部分媒體傳輸的時候使用--AaB03x--進行分割,在涉及到附件的時候,我們再次使用多部分媒體類型進行傳輸,這個時候使用--BbC04y--進行內容分割。

除了請求,我們的響應也是可以使用多部分媒體類型的,如果下面的示例,展示了一個對文檔不同範圍請求產生的響應:

HTTP/1.0 206 Partial content
Server: Microsoft-IIS/5.0
Date: Sun, 10 Dec 2000 19:11:20 GMT
Content-Location: http://www.joes-hardware.com/gettysburg.txt
Content-Type: multipart/x-byteranges; boundary=--[abcdefghijklmnopqrstu
vwxyz]--
Last-Modified: Sat, 09 Dec 2000 00:38:47 GMT

--[abcdefghijklmnopqrstuvwxyz]--
Content-Type: text/plain
Content-Range: bytes 0-174/1441
Fourscore and seven years ago our fathers brought forth on this
continent a new nation, conceived in liberty and dedicated to the
proposition that all men are created equal.
--[abcdefghijklmnopqrstuvwxyz]--
Content-Type: text/plain
Content-Range: bytes 552-761/1441

But in a larger sense, we can not dedicate, we can not consecrate,
we can not hallow this ground. The brave men, living and dead who
struggled here have consecrated it far above our poor power to add
or detract.
--[abcdefghijklmnopqrstuvwxyz]--
Content-Type: text/plain
Content-Range: bytes 1344-1441/1441
and that government of the people, by the people, for the people shall
not perish from the earth.

--[abcdefghijklmnopqrstuvwxyz]--

5 內容編碼

很多時候我們在傳輸內容實體之前,會對實體內容進行編碼處理。比如進行gzip壓縮,或者是加密等。一般我們稱之這個過程爲內容編碼。

5.1 內容編碼的過程

內容編碼的主要過程有如下3步:

  1. 生成原始報文,一般原始報文中含有Content-Type和Content-Length首部。
  2. 編碼服務器(可能是原始發送端本身,也有可能是發送端的下行代理)對報文實體進行編碼。編碼後一般也會攜帶Content-Type和Content-Length。Content-Type和原始報文一致,但是Content-Length可能會有變化。比如我們進行gzip壓縮後,這裏的Content-Length就是壓縮後的大小。
  3. 接收端收到報文後,先對報文進行解碼,獲得原始報文後再做處理

5.2 內容編碼的類型

HTTP定義了一些標準的內容編碼類型,當然也允許我們自行擴展一些編碼類型。常用的編碼類型如下表所示:

Content-Encoding 值 描  述
gzip 表明實體採用 GNU zip 編碼a
compress 表明實體採用 Unix 的文件壓縮程序
deflate 表明實體是用 zlib 的格式壓縮的b
identity 表明沒有對實體進行編碼。當沒有 Content-Encoding 首部時,就默認爲這種情況

這裏面gzip是使用比較平凡的一種壓縮編碼方式。

內容編碼一般都是服務器進行編碼,客戶端收到報文後進行解碼。這樣,可能存在一種情況是服務器用了一種客戶端無法解碼的類型進行編碼,這個時候就會出現錯誤。在HTTP中爲了解決這種錯誤,引入了Accept-Encoding首部。客戶端在請求的時候,可以使用該首部告知服務器端客戶端可以處理的編碼類型。同樣,如果可以接收處理多個編碼類型,我們可以使用“,”隔開每種編碼,同時可以爲每種編碼指定一個優先級,告知服務器,優先選擇哪種編碼,示例如下:

Accept-Encoding: compress, gzip
Accept-Encoding: *
Accept-Encoding: compress;q=0.5, gzip;q=1.0”

其中"*"代表可以接收任務編碼類型,如果客戶端請求的時候沒有使用Accept-Encoding首部,也即默認可以處理所有編碼類型。

6. 傳輸編碼和分塊編碼

6.1 傳輸編碼

雖然這裏的傳輸編碼和前面介紹的內容編碼都是用於對實體操作。但是兩種編碼的目的不太一樣,內容編碼一般是爲了對內容進行壓縮和轉換等,而傳輸編碼是爲了其他一些架構的因素考慮進行使用。同樣兩種編碼對某些首部的影響也不一樣,比如實體摘要Content-MD5,一般是用作於內容編碼之後,傳輸編碼之前。同樣,如果一般代用傳輸編碼後,Content-Length首部的值也通常會做忽略處理。

實際使用當中,一般傳輸編碼主要用於解決報文未知大小和安全性的問題。不過由於安全性現在通常用HTTPS就能很好處理了。所以大多數時候傳輸編碼就是用於解決報文未知大小的問題。比如一些動態生成的報文,如果不能生成完成我們就沒法得知報文實體的大小,但是有時候我們又希望在完全生成完成之前就開始傳輸數據,提高效率。

HTTP協議中提供了Transfer-Encoding和TE兩個首部用於控制傳輸編碼。其中Transfer-Encoding用於發送方告知接收方用了何種傳輸編碼。而TE和Accept-Encoding類似,是接收端告訴發送端可以處理哪些傳輸編碼。

6.2 分塊編碼

目前HTTP規範中,僅僅定義了一種傳輸編碼——分塊編碼。分塊編碼把報文分割爲若干個大小已知的塊。塊之間是緊挨着發送的,這樣就不需要在發送之前知道整個報文的大小了,也就解決了我們上面提到的未知報文大小的問題。

在正常情況,如果不是使用的持久連接。那麼客戶端直接可以一直讀取到連接關閉就意味着報文結束了。但是如果是在持久連接中,如果事先不知道報文實體的大小。這個時候就可以使用分塊編碼。分塊編碼允許服務器把主體逐塊發送,說明每塊的大小就可以了。因爲主體是動態創建的,服務器可以緩衝它的一部分,發送其大小和相應的塊,然後在主體發送完之前重複這個過程。服務器可以用大小爲 0 的塊作爲主體結束的信號,這樣就可以繼續保持連接,爲下一個響應做準備。

如果客戶端的 TE 首部中說明它可以接受拖掛的話,服務器就可以在分塊的報文最後加上拖掛。不過該選項爲可選選項,所以就看你是否需要了。比如我們可以在最後拖掛一個報文整個實體內容的校驗和。客戶端在接收完全部分塊內容後,也可以計算出報文實體的校驗和,然後進行對比,以驗證報文內容的完整性。

我們在使用傳輸編碼的時候,一定要注意遵守如下規則:

  • 傳輸編碼中必須包含“分塊”,除非使用連接關閉來結束報文
  • 當使用分塊編碼的時候,必須是最後一個作用域主題的編碼
  • 分塊編碼不能多次使用在同一主體上

7 範圍請求

有些時候,我們並不需要請求一個完整的文檔內容,可能只需要文檔中的某個片段。比如在多線程下載的時候,可能每個線程都只需請求資源的部分內容即可。這個時候就可以利用範圍請求來實現。HTTP中,通過Range首部實現範圍請求。如下:

GET /bigfile.html HTTP/1.1
Host: www.joes-hardware.com
Range: bytes=4000-
……

該示例中,客戶端通過Range首部,告訴服務器我只要資源第4000字節以後的內容。這裏我們不知道文檔總的打下,所以可以通過不指定結尾字節數,告知服務器返回從4000到結尾的部分。如果你知道具體需要的範圍,也可以指定具體的區間。

8 差異編碼

服務器上的文檔並不是一層不變的,如果我們服務器上的文檔發生了變化。客戶端緩存了變化之前的文檔內容,這個時候客戶端發起請求的時候。如果把整個文檔全部返回給客戶端,那是很浪費資源的,特別是有時候改動很小的時候。這個時候如果我們可以只返回有改動(差異)的部分,客戶端拿到這部分內容之後,將本地緩存更新後展示給用戶即可。這樣既節約資源,也能提高傳輸的速度。這種情況就可以利用差異編碼實現。差異編碼的過程如下圖所示:
差異編碼
上圖展示了差異編碼的結構,包括請求、生成、接收和裝配文檔的全過程。客戶端必須告訴服務器它有頁面的哪個版本,它願意接受頁面最新版的差異(delta),它懂得哪些將差異應用於現有版本的算法。服務器必須檢查它是否有這個頁面的客戶端現有版本,計算客戶端現有版本與最新版之間的差異(有若干算法可以計算兩個對象之間的差異)。然後服務器必須計算差異,發送給客戶端,告知客戶端所發送的是差異,並說明最新版頁面的新標識(ETag),因爲客戶端將差異應用於其老版本之後就會得到這個版本。
A-IM 是 Accept-Instance-Manipulation(接受實例操控)的縮寫,用於告訴服務器可以接受差異的類型(算法)。服務器在響應中用IM首部告訴客戶端,本次返回的差異數據是用的何種操作類型(算法)。
另外這裏總結一下,目前在IANA中註冊實例操作類型(不僅僅是差異操作類型,還包括前面的一些內容編碼的操作)如下表所示:

類 型 說  明
vcdiff 用 vcdiff 算法計算差異a
diffe 用 Unix 系統的diff-e 命令計算差異
gdiff 用 gdiff 算法計算差異b
gzip 用 gzip 算法壓縮
deflate 用 deflate 算法壓縮
range 用在服務器的響應中,說明響應是針對範圍選擇得到的部分內容
identity 用在客戶端請求中的 A-IM 首部中,說明客戶端願意接受恆等實例操控

上面的每個操作類型具體介紹,這裏就不詳述了,有興趣的朋友可以自己搜索下相關的資料。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章