HTTP 實體與編碼

文章同步於 Github blog

天天都有數以億計的各類媒體對象經由 HTTP 傳送,如圖像、文本、影片以及軟件程序等。HTTP 要確保它所承載
的「貨物」滿 足如下條件:html

  • 能夠被正確地識別(經過Content-Type首部說明媒體格式,Content- Language 首部說明語言),以便瀏覽器和其餘客戶端能正確處理內容。
  • 能夠被正確地解包(經過Content-Length首部和Content-Encoding首部)。
  • 是最新的(經過實體驗證碼和緩存過時控制)
  • 符合用戶的須要(基於Accept系列的內容協商首部)
  • 在網絡上能夠快速有效地傳輸(經過範圍請求、差別編碼以及其餘數據壓縮方法)
  • 完整到達、未被篡改(經過傳輸編碼首部和Content-MD5校驗和首部)

HTTP 實體首部

HTTP 實體首部描述了 HTTP 報文的內容。HTTP/1.1 版定義瞭如下 10 個基本字體首部字段。git

  • Content-Type:實體中所承載對象的類型。
  • Content-Length: 所傳送實體主體的長度或大小。
  • Content-Language: 與所傳送對象最相配的人類語言。
  • Content-Encoding: 對象數據所作的任意變換(好比,壓縮)。
  • Content-Location: 一個備用位置,請求時可經過它得到對象。
  • Content-Range: 若是這是部分實體,這個首部說明它是總體的哪一個部分。
  • Content-MD5: 實體主體內容的校驗和。
  • Last-Modified: 所傳輸內容在服務器上建立或最後修改的日期時間。
  • Expires: 實體數據將要失效的日期時間。
  • Allow: 該資源所容許的各類請求方法,例如,GET 和 HEAD。
  • ETag: 這份文檔特定實例的惟一驗證碼。ETag 首部沒有正式定義爲實體首部,但它對許多涉及實體的操做來講,都是一個重要的首部。
  • Cache-Control: 指出應該如何緩存該文檔。和 ETag 首部相似,Cache-Control 首部也沒有正 式定義爲實體首部。

實體的大小(Content-Length)

除非使用了分塊編碼,不然 Content-Length 首部就是帶有實體主體的報文必須使用的。使用 Content-Length 首部是爲了可以檢測出服務器崩潰而致使的報文截尾,並對共享持久鏈接的多個報文進行正確分段。

Content-Length 首部指示出報文中實體主體的字節大小。這個大小是包含了全部內容編碼的。若是主體進行了內容編碼,Content-Length 首部說明的就是 編碼後(encoded) 的主體的字節長度,而不是未編碼的原始主體的長度。github

沒有 Content-Length 的話,客戶端沒法區分究竟是報文結束時正常的鏈接關閉,仍是報文傳輸中因爲服務器崩潰而致使的鏈接關閉。客戶端須要經過 Content-Length 來檢測 報文截尾算法

Content-Length 首部對於持久鏈接是必不可少的。若是響應經過持久鏈接傳送, 就可能有另外一條 HTTP 響應緊隨其後。客戶端經過 Content-Length 首部就能夠知道報文在何處結束,下一條報文從何處開始。由於鏈接是持久的,客戶端沒法依賴 鏈接關閉來判別報文的結束。瀏覽器

有一種狀況下,使用持久鏈接時能夠沒有 Content-Length 首部,即採用 分塊編碼(chunked encoding)時。在分塊編碼的狀況下,數據是分爲一系列的塊來發送的,每塊都有大小說明。哪怕服務器在生成首部的時候不知道整個實體的大小(一般是由於實體是動態生成的),仍然可使用分塊編碼傳輸若干已知大小的塊。

實體摘要(Content-MD5)

服務器使用 Content-MD5 首部發送對實體主體運行 MD5 算法的結果。只有產生響應的原始服務器能夠計算併發送 Content-MD5 首部。若是一份文檔使用 gzip 算法進 行壓縮,而後用分塊編碼發送,那麼就對整個經 gzip 壓縮的主體進行 MD5 計算。緩存

媒體類型(MIME Type)和字符集

Content-Type 首部字段說明了實體主體的 MIME 類型。6MIME 類型是標準化的 名字,用以說明做爲貨物運載實體的基本媒體類型(好比:HTML 文件、Microsoft Word 文檔或是 MPEG 視頻等)。客戶端應用程序使用 MIME 類型來解釋和處理其內容服務器

Content-Type 首部說明的是原始實體主體的媒體類型。例如, 若是實體通過內容編碼的話,Content-Type 首部說明的還是編碼以前的實體主體的類型。

Content-Type 首部還支持可選的參數來進一步說明內容的類型。charset(字符集)參數就是個例子,它說明把實體中的比特轉換爲文本文件中的字符的方法:網絡

Content-Type: text/html; charset=iso-8859-4

MIME 中的 multipart(多部分)電子郵件報文中包含多個報文,它們合在一塊兒做 爲單一的複雜報文發送。每一部分都是獨立的,有各自的描述其內容的集;不一樣的 部分之間用分界字符串鏈接在一塊兒。架構

HTTP 也支持多部分主體。不過,一般只用在下列兩種情形之一:提交填寫好的表格,或是做爲承載若干文檔片斷的範圍響應。併發

內容編碼(Content-Encoding)

內容編碼,是對報文的主體進行的可逆變換。內容編碼是和內容的具體格式細節緊密相關的。例如,你可能會用 gzip 壓縮文本文件,但不是 JPEG 文 件,由於 JPEG 這類東西用 gzip 壓縮的不夠好。

HTTP 定義了一些標準的內容編碼類型,並容許用擴展編碼的形式增添更多的編碼。 由 互聯網號碼分配機構(IANA)對各類編碼進行標準化,它給每一個內容編碼算法分配了惟一的代號。Content-Encoding 首部就用這些標準化的代號來講明編碼時使 用的算法。

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

Accept-Encoding首部

毫無疑問,咱們不但願服務器用客戶端沒法解碼的方式來對內容進行編碼。爲了不服務器使用客戶端不支持的編碼方式,客戶端就把本身支持的內容編碼方式列表放在請求的 Accept-Encoding 首部裏發出去。

若是 HTTP 請求中沒有包含 Accept-Encoding 首部,服務器就能夠假設客戶端可以接受任何編碼方式(等價 於發送 Accept-Encoding: *)。

客戶端能夠給每種編碼附帶 Q(質量)值參數來講明編碼的優先級。Q 值的範圍從0.0 到 1.0,0.0 說明客戶端不想接受所說明的編碼,1.0 則代表最但願使用的編碼。 「*」表示「任何其餘方法」。決定在響應中回送什麼內容給客戶端是個更通用的過程,而選擇使用何種內容編碼則是此過程的一部分。

compress;q=0.5, gzip;q=1.0 gzip;q=1.0, identity; q=0.5, *;q=0

傳輸編碼(Transfer Codings)

傳輸編碼

傳輸編碼也是做用在實體主體上的可逆變換,但使用它們是因爲架構方面的緣由,同內容的格式無關。使用傳輸編碼是爲了改變 報文中的數據在網絡上傳輸的方式。

Transfer-Encoding首部

HTTP 協議中只定義了下面兩個首部來描述和控制傳輸編碼。

  • Transfer-Encoding 告知接收方爲了可靠地傳輸報文,已經對其進行了何種編碼。
  • TE 用在請求首部中,告知服務器可使用哪些傳輸編碼擴展。

下面的例子中,請求使用了 TE 首部來告訴服務器它能夠接受分塊編碼(若是是 HTTP/1.1 應用程序的話,這就是必須的)而且願意接受附在分塊編碼的報文結尾上 的拖掛:

GET /new_products.html HTTP/1.1
Host: www.joes-hardware.com
User-Agent: Mozilla/4.61 [en] (WinNT; I) TE: trailers, chunked
...

對它的響應中包含 Transfer-Encoding 首部,用於告訴接收方已經用分塊編碼對
報文進行了傳輸編碼:

HTTP/1.1 200 OK Transfer-Encoding: chunked Server: Apache/3.0
...

在這個起始首部以後,報文的結構就將發生改變。

傳輸編碼的值都是大小寫無關的。HTTP/1.1 規定在 TE 首部和 Transfer-Encoding 首部中使用傳輸編碼值。最新的 HTTP 規範只定義了一種傳輸編碼,就是分塊編碼

分塊編碼

分塊編碼把報文分割爲若干個大小已知的塊。塊之間是緊挨着發送的,這樣就不須要在發送以前知道整個報文的大小了。

要注意的是,分塊編碼是一種傳輸編碼,所以是報文的屬性,而不是主體的屬性

分塊與持久鏈接

若客戶端和服務器之間不是持久鏈接,客戶端就不須要知道它正在讀取的主體的長度,而只須要讀到服務器關閉主體鏈接爲止。

當使用持久鏈接時,在服務器寫主體以前,必須知道它的大小並在 Content- Length 首部中發送。若是服務器動態建立內容,就可能在發送以前沒法知道主體的長度。

分塊編碼爲這種困難提供瞭解決方案,只要容許服務器把主體逐塊發送,說明每塊的大小就能夠了。由於主體是動態建立的,服務器能夠緩衝它的一部分,發送其大小和相應的塊,而後在主體發送完以前重複這個過程。服務器能夠用大小爲 0 的塊做爲主體結束的信號,這樣就能夠繼續保持鏈接,爲下一個響應作準備。

客戶端也能夠發送分塊的數據給服務器。由於客戶端事先不知道服務器是否接受分塊編碼(這是由於服務器不會在給客戶端的響應中發送 TE 首部),因此客戶端必須作好服務器用411 Length Required(須要Content-Length首部)響應來拒絕分塊請求的準備。

分塊報文的拖掛

若是客戶端的 TE 首部中說明它能夠接受拖掛的話,就能夠在分塊的報文最後加上拖掛。產生原始響應的服務器也能夠在分塊的報文最後加上拖掛。拖掛的內容是可選的元數據,客戶端不必定須要理解和使用(客戶端能夠忽略並丟棄拖掛中的內容)。

拖掛中能夠包含附帶的首部字段,它們的值在報文開始的時候多是沒法肯定的 (例如,必需要先生成主體的內容)。Content-MD5 首部就是一個能夠在拖掛中發送的首部,由於在文檔生成以前,很難算出它的 MD5。

除了 Transfer-Encoding、Trailer 以及 Content-Length 首部以外,其餘 HTTP 首部均可以做爲拖掛發送。

驗證碼和新鮮度

當文檔在客戶端「過時」以後(也就是說,客戶端再也不認爲該副本有效),客戶端必須從服務器請求一份新的副本。不過,若是該文檔在服務器上並未發生改變,客戶 端也就不須要再接收一次了——繼續使用緩存的副本便可。

這種特殊的請求,稱爲 條件請求(conditional request),要求客戶端使用 驗證碼 (validator)
來告知服務器它當前擁有的版本號,並僅當它的當前副本再也不有效時纔要求發送新的副本。

新鮮度

服務器應當告知客戶端可以將內容緩存多長時間,在這個時間以內就是新鮮的。 服務器能夠用這兩個首部之一來提供這種信息: Expires(過時)Cache- Control(緩存控制)

客戶端和服務器爲了能正確使用 Expires 首部,它們的時鐘必須同步。

條件請求(Conditional Requests)

HTTP 爲客戶端提供了一種方法,僅當資源改變時才請求副本, 這種特殊請求稱爲有條件的請求。有條件的請求是標準的 HTTP 請求報文,但僅當某個特定條件爲真時才執行。

例如,某個緩存服務器可能發送下面的有條件 GET 報文給服務器,僅當文件 /announce.html 從 2002 年 6 月 29 日(這是緩存的文檔最後 被做者修改的時間)以後發生改變的狀況下才發送它:

GET /announce.html HTTP/1.0
If-Modified-Since: Sat, 29 Jun 2002, 14:30:00 GMT

有條件的請求是經過以「If-」開頭的有條件的首部來實現的。

驗證碼

每一個有條件的請求都經過特定的驗證碼來發揮做用。驗證碼是文檔實例的一個特殊屬性,用它來測試條件是否爲真。從概念上說,你能夠把驗證碼看做文件的序列號、 版本號,或者最後發生改變的日期時間。

HTTP把驗證碼分爲兩類弱驗證碼(weak validators)強驗證碼(strong validators)。 弱驗證碼不必定能惟一標識資源的一個實例,而強驗證碼必須如此。

最後修改時間(If-Modified-Since)被看成弱驗證碼,由於儘管它說明了資源最後被修改的時間,但它的描述精度最大就是 1 秒

ETag 首部被看成強驗證碼,由於每當資源內容改變時,服務器均可以在 ETag 首部放置不一樣的值。

有時候,客戶端和服務器可能須要採用不那麼精確的實體標記驗證方法。例如,某服務器可能想對一個很大、被普遍緩存的文檔進行一些美化修飾,但不想在緩存服務器再驗證時產生很大的傳輸流量。在這種狀況下,該服務器能夠在標記前面加上「W/」前綴來廣播一個「弱」實體標記。

範圍請求(Range Requests)

關於客戶端如何要求服務器只在資源的客戶端副本再也不有效的狀況下才發送其副本, 咱們已經清楚地理解了。HTTP 還進一步錦上添花:它容許客戶端實際上只請求文 檔的一部分,或者說某個範圍

有了範圍請求,HTTP 客戶端能夠經過請求曾獲取失敗的實體的一個範圍(或者說一部分),來恢復下載該實體。固然這有一個前提,那就是從客戶端上一次請求該實 體到此次發出範圍請求的時段內,該對象沒有改變過。例如:

GET /bigfile.html HTTP/1.1
Host: www.joes-hardware.com
Range: bytes=4000-
User-Agent: Mozilla/4.61 [en] (WinNT; I) ...

在本例中,客戶端請求的是文檔開頭 4000 字節以後的部分(沒必要給出結尾字節數, 由於請求方可能不知道文檔的大小)。在客戶端收到了開頭的 4000 字節以後就失敗的狀況下,可使用這種形式的範圍請求。

並非全部服務器都接受範圍請求,但不少服務器能夠。服務器能夠經過在響應中包含 Accept-Ranges 首部的形式向客戶端說明能夠接受的範圍請求。這個首部的值是計算範圍的單位,一般是以字節計算的。例如:

HTTP/1.1 200 OK
Date: Fri, 05 Nov 1999 22:35:15 GMT Server: Apache/1.2.4
Accept-Ranges: bytes

涉及範圍請求的一系列 HTTP 事務的例子:
image

Range 首部在流行的 點對點(Peer-to-Peer,P2P) 文件共享客戶端軟件中獲得普遍應用,它們從不一樣的對等實體同時下載多媒體文件的不一樣部分。

差別編碼(Delta encoding)

若是客戶端有一個頁面的已過時副本,就要請求頁面的最新實例。若改變的地方比較少,與其發送完整的新頁面給客戶端,客戶端更願意服務器只發送頁面發生改變的部分,這樣就能夠更快地獲得最新的頁面。

差別編碼是 HTTP 協議的一個擴展,它經過交換對象改變的部分而不是完整的對象來優化傳輸性能。差別編碼也是一類實例操控,由於它依賴客戶端和服務器之間針對特定的對象實例來交換信息。RFC 3229 描述了差別編碼。

若是客戶端想告訴服務器它願意接受該頁面的差別,只要發送 A-IM 首部 就能夠了。A-IM 是 Accept-Instance-Manipulation(接受實例操控) 的縮寫。

在 A-IM 首部中,客戶端會說明它知道哪些算法能夠把差別應用於老版本而獲得最新版本。

服務端發送回下面這些內容:一個特殊的響應代碼—— 226 IM Used,告知客戶端它正在發送的是所請求對象的實例操控,而不是那個完整的對象自身;一個 IM(Instance-Manipulation 的縮寫) 首部,說明用於計算差別的算法;新的 ETag 首部Delta-Base 首部,說明用於計算差別的基線文檔的 ETag。

image

服務器側的「差別生成器」根據基線文檔和該文檔的最新實例,用客戶端在 A-IM 首部中指明的算法計算它們之間的差別。客戶端側的「差別應用器」 獲得差別,將其應用於基線文檔,獲得文檔的最新實例。例如,若是產生差別的算 法是 Unix 系統的 diff-e 命令,客戶端就能夠用 Unix 系統中的文本編輯器 ed 提供的 功能來應用差別。

差別編碼所用的首部

ETag

文檔每一個實例的惟一標識符。由服務器在響應中發送;客戶端在後繼請求的 If-Match 首部和 If-None-Match 首部中可使用它

If-None-Match

客戶端發送的請求首部,當且僅當客戶端的文檔版本與服務器不一樣時,才向服務器請求該文檔

A-IM

客戶端請求首部,說明能夠接受的實例操控類型

IM

服務器響應首部,說明做用在響應上的實例操控的類型。當響應代碼是226 IM Used 時,會發送這個首部

Delta-Base

服務器響應首部,說明用於計算差別的基線文檔的 ETag 值(應當與客戶端請求
中的 If-None-Match 首部裏的 ETag 相同)

參考

相關文章
相關標籤/搜索