HTTP

HTTP 酬載 — Content-Length, Transfer-Encoding, Content-Range, Trailer

資料酬載 (payload) 或稱 酬載、有效載荷,
是傳輸時 實際預期的資料,可能是 完整 or 部分 的 表示 (representation)。
 
對照 (3-1) 訊息格式 (Message Format) 的請求訊息 (request message) 範例,

POST /?id=1 HTTP/1.1

Host: echo.paw.cloud Content-Type: application/json; charset=utf-8 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:53.0) Gecko/20100101 Firefox/53.0 Connection: close Content-Length: 136

 

{ "status": "ok", "extended": true, "results": [ {"value": 0, "type": "int64"}, {"value": 1.0e+3, "type": "decimal"} ] }

 
即此部分 :

{ "status": "ok", "extended": true, "results": [ {"value": 0, "type": "int64"}, {"value": 1.0e+3, "type": "decimal"} ] }

 
因此,我們稱 訊息主體 (Message Body) 包含了 (contain) 酬載主體/有效載體 (payload body)
 
與 表示 (representation) 不同的是,
酬載 (payload) 語意上 不考慮資料的內容是何種 格式、語言、編碼…,
強調的是傳輸的 資料 位元組 (octet) 本身。
 
酬載 (payload)元資料 (metadata),通常包含了:

  • 傳輸編碼 (Transfer-Encoding)
  • 內容長度 (Content-Length)
  • 內容範圍 (Content-Range)
  • 拖曳 (Trailer)

即所謂的 — — 酬載表頭欄位 (payload header fields)
 


 

Transfer-Encoding (傳輸編碼)

傳輸編碼 (Transfer-Encoding)內容編碼 (Content-Encoding)
同樣是對 酬載主體 進行編碼,而非表頭,
且一樣能夠使用 gzipdeflatebr (Brotli) 及 compress … 等編碼方式。
 
[註]:
HTTP/1.1 標準中 沒有 表頭壓縮 !
(雖然有非標準的 UA 實作)
HTTP/2 則引入了 HPACK 表頭壓縮。
 
不同的是 內容編碼 (Content-Encoding)表示 (representation) 的屬性,
強調 實體資料「本身」的生成特性。
 
傳輸編碼 (Transfer-Encoding)訊息 (message) 的屬性,
強調 傳輸 的層面,比 內容編碼 更外層 :

Transfer-Encoding( Content-Encoding( Content-Type( bits ) ) )

 
因此:

兩者並不衝突,且時常搭配使用。

 
例如:

HTTP/1.1 200 OK

Server: nginx/1.10.0 (Ubuntu) Content-Type: text/html; charset=utf-8 Content-Encoding: gzip Transfer-Encoding: chunked

 

...略...

 
 

分塊傳輸編碼 (Chunked Transfer Coding)

傳輸編碼 (Transfer-Encoding) 最主要的編碼方式為 : 分塊 (chunked)
總稱為 — — 分塊傳輸編碼 (Chunked Transfer Coding),
簡單說就是將資料分成一塊一塊 😂 (仍在同個訊息中)。
 
分塊傳輸編碼 範例 :

HTTP/1.1 200 OK

Content-Type: text/plain; charset=utf-8 Transfer-Encoding: chunked

 

1C 這是中勝 安安你好>_^ 5 apple 2 is 4 good 0

 
紅裡透 的是 十六進制 的 資料長度 (位元組),
例如,「這是中勝安安你好」共 8*3 = 24 bytes (utf-8),
而「空白」與啾咪「>_^」共 4 bytes,合計 28 bytes = 1C16
最後用長度 0 代表內容結束。
 
這樣的作法,有利於動態生成 及 未知大小的內容,
訊息發送者 不需等到所有資料完成,才計算總長。
 
分塊傳輸編碼內容編碼 時常搭配使用:

HTTP/1.1 200 OK

Content-Type: text/plain; charset=utf-8 Content-Encoding: gzip Transfer-Encoding: chunked

 

31 000111110001011... (二進制資料) 0

 
 

最終編碼 (final encoding)

需注意的是:

若訊息 使用 分塊 (chunked) 以外的其他傳輸編碼 (e.g., gzip),
則 發送方 需將 分塊 (chunked) 置於應用列表的 尾端,以確保訊息被正確地框架化。
例如: Transfer-Encoding: gzip, chunked

 
此規則對 回應 較為通融,允許 Server 不使用 chunked 為最終編碼,
這時 訊息長度 取決於 讀取連線,直到 Server 關閉連線
 
反之,若為 請求 則得 嚴格執行
否則 Server 必須使用 4xx (Error) 狀態代碼進行回應。
 
[註]:
因 HTTP/2 使用的是 資料框 (DATA frames) 串流傳輸,
分塊 (chunked) 傳輸編碼,禁止 用於 HTTP/2。
 
 

Transfer-Encoding vs. Content-Encoding

Q: 但是 內容編碼 不也有 gzip ? 既生瑜,何生亮 ?
 
A:
如上所述,內容編碼 (Content-Encoding)表示 (representation) 的屬性,
強調 實體資料「本身」的生成特性。
 
傳輸編碼 (Transfer-Encoding)訊息 (message) 的屬性,
強調 傳輸 的層面,比 內容編碼 更外層。
 
(尛,這不是就語意上的差別嗎 😑 ?
 

沒錯,以相同方式編碼時,主要差別僅在 語意 (sementic)
這正是 HTTP 精髓 (無聊) 的地方!

 
這代表: 解碼 傳輸編碼 後的,才是真正的 表示資料 (representation data) !

Transfer-Encoding( representation-data )

 
表示資料 可能使用 內容編碼 也可能沒有,
並且,使用 內容編碼 與 未使用 內容編碼 的 酬載,將視為不同個體!

representation-data := Content-Encoding( Content-Type( bits ) )

 
這將影響回應的覆用性 — 強驗證 快取 更新機制 (e.g., ETag) !
 
相較之下,無論是否應用 傳輸編碼,表示資料皆不變,
因此 不會導致不同的 實體標籤 (ETag)
 
並且,使用者配置的 代理伺服器 (Proxy Server)
允許修改/刪除訊息中的 傳輸編碼,內容編碼 則否。
 
事實上,這種 語意差異 您已遇見無數次,
詳見 (4-2) GET vs. POST
 


 

Content-Length (內容長度)

HTTP/1.1 的主架構師 — Dr. Roy Thomas Fielding :

早期,HTTP/1.0 藉由底層的傳輸協定 (i.e., TCP),
實作 回應訊息 結束的偵測方式– — 關閉 TCP 連線。
然而,這產生一個嚴重的問題:
「Client 無法有效區分 回應 到底是 已完成 還是 網路異常 而中斷」。

 
於是 HTTP/1.0 重新定義了 Content-Length (內容長度) 表頭欄位
用於指出 訊息主體 的 位元組 (octet) 長度,
並在 HTTP/1.1,引入上方介紹的 分塊傳輸編碼 (chunked transfer coding)
 
除了達成 傳輸的獨立性 (Transport Independence)
更為之後的 持久連線 (persistent connection) 奠定基礎
— — 以長度分隔訊息 (length-delimited message)
 
Content-Length (內容長度) 表頭欄位
其值為 (酬載主體) 預期的 十進制 位元組數 (不是字數 !),
對照 (3-1) 訊息格式 (Message Format) 的請求訊息 (request message) 範例,

POST /?id=1 HTTP/1.1

Host: echo.paw.cloud Content-Type: application/json; charset=utf-8 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:53.0) Gecko/20100101 Firefox/53.0 Connection: close Content-Length: 136

 

{ "status": "ok", "extended": true, "results": [ {"value": 0, "type": "int64"}, {"value": 1.0e+3, "type": "decimal"} ] }

 
即此部分 :

Content-Length: 136

 
接收者收到訊息的 Content-Length 與 實際傳輸量 不吻合
甚至 沒有 Content-Length 時,就該懷疑訊息的有效性。
 
 
[註]:
人生無趣的話,可以數數看上方範例 😂 ,
空行 是傳輸 0x0A (十進制的 10) — — 1 byte
空白 是傳輸 0x20 (十進制的 32) — — 1 byte
而絕大部分 中文字 使用 3 bytes (UTF-8) 。
 
 

請求訊息 與 無意義酬載

(4-1) 請求方法、安全性、冪等性:
對於許多 請求方法 (e.g., GETHEADDELETECONNECTTRACE):

酬載 (payload) 是 未定義的語意 (無意義的),
若硬要送出,「可能」使 Server 拒絕此請求。

 
事實上,排除 TRACE 方法 外,HTTP 並無 嚴格限制 「請求方法 不准攜帶 酬載」,
換句話說,只要 Server 支援,一樣能在 GET 訊息 附加酬載送出。
(儘管這是不建議的做法)
 
因此,當使用這些請求方法時 (酬載無意義),
使用者代理 (User Agent) 不應 生成 Content-Length 欄位
 
反之,若請求方法預期含有酬載主體 (e.g., POSTPUTPATCHOPTIONS),
應該 生成 Content-Length 欄位

儘管長度可能為零

 
[註]:
是的,OPTIONS 方法並非無意義,然而規範尚未定義其用途,
僅預計未來的擴展可能使用 OPTIONS 主體,來更詳細地查詢目標資源。
 
簡單來說:

訊息主體 (Message Body) 在 請求訊息中 是否存在,
Content-LengthTransfer-Encoding 欄位作為信號;
而與 方法語意 無關,即使該方法 未定義 訊息主體的任何用途。

 
許多人誤將 請求方法 視為訊息主體是否存在酬載的依據。
 
 

回應訊息

除了稍後會提及的一些情形,若發送完整表頭前,酬載大小已知,
源伺服器 (Origin Server) 應發送一個 Content-Length 欄位
 
這將允許 下游 (downstream) 接收方測量傳輸進度,以知訊息何時完整接收,
並能夠 重新使用連線 來獲取其他請求。
 

儘管長度可能為零

 
[註]:
所謂術語「上/下 游」:
 
message-flow-2
 
一個原則:

『所有』 訊息,都由 上游 『流』到 下游,
與 Client/Server 無關。

 
 

回應特例

訊息主體 (Message Body) 在 回應訊息中 是否存在,
取決於使用的 請求方法,以及 回應狀態碼

 
對所有 HEAD 方法 的 請求訊息,
以及 狀態碼 為 1xx (Informational) — 資訊、
204 (No Content) — 沒有內容 、 304 (Not Modified) — 未修改
回應不得包含 訊息主體 (Message Body)
因此,以上情形,源伺服器 不得發送 Content-Length 欄位 (X)
 
需注意的是:

HEAD 方法 的效果應等價於 GET 方法,
而 304 回應 儘管沒有酬載主體,其原先的快取回應則不一定。

 
因此,對 HEAD 方法 以及 304 (Not Modified)
Server 是極有可能發送 Content-Length 欄位 的 !
 
而對所有 CONNECT 方法2xx (Successful) 回應,
代表連線將切換為 隧道模式 (tunnel mode),已非 HTTP 通訊方。
 
 

Content-Length vs. Transfer-Encoding

Content-Length (內容長度)Transfer-Encoding (傳輸編碼)互斥 的 ! (有你就沒有我)
因此,當訊息存在 Transfer-Encoding 欄位,發送方 不得 生成 Content-Length。
 
如果 Transfer-Encoding 欄位 存在,並且「chunked」是 最終編碼:

透過讀取和解碼 分塊大小 (chunk-size) 來確定訊息長度,直到傳輸編碼指示數據完成為止。

 
(注意,Content-Length 為 十進制,chunk-size 為 十六進制)
 
如果訊息存在 Transfer-Encoding 欄位,而「chunked」不是 最終編碼:
 
對於請求:

不能可靠地確定訊息長度,Server 必須使用 4xx (Error) 狀態代碼進行回應,並關閉連線。

 
對於回應:

訊息長度取決於讀取連線,直到 Server 關閉連線。

 


 

Content-Range (內容範圍)

Content-Range 表頭欄位,在 單個部分 206 (Partial Content) 回應中被發送。
 
206 (Partial Content) — 部分內容

代表 Server 藉由請求中的 Range 表頭欄位
成功獲取 並傳輸 一或多個滿足該範圍的 目標資源 之 表示 (representation)。

 
Client 可以使用 Range 表頭欄位
將 GET 語義用於 範圍請求 (range request),使 Server 僅傳輸所選 表示 (representation) 的一部分。
(e.g., 大型文件 的單個頁面)
 
 

傳輸單個部分

生成 206 回應 的 Server,必須生成一個 Content-Range 表頭欄位,
描述 選擇表示所包含的範圍,與由 該範圍的組成的 酬載 (payload),例如:

HTTP/1.1 206 Partial Content Date: Wed, 15 Nov 1995 06:25:24 GMT Last-Modified: Wed, 15 Nov 1995 04:58:08 GMT

Content-Range: bytes 21010-47021/47022

Content-Length: 26012 Content-Type: image/gif ...26012 bytes of partial image data...

 
 

傳輸多個部分

生成 206 回應 的 Server,不得 生成 Content-Range 表頭欄位,
而是使用 「多部分」的 內容類型 (Content-Type),例如:

Content-Type: multipart/byteranges; boundary=THIS_STRING_SEPARATES

詳見 (4-2) GET vs. POST
 
訊息範例:

HTTP/1.1 206 Partial Content

Date: Wed, 15 Nov 1995 06:25:24 GMT Last-Modified: Wed, 15 Nov 1995 04:58:08 GMT Content-Length: 1741 Content-Type: multipart/byteranges; boundary=THIS_STRING_SEPARATES

 

--THIS_STRING_SEPARATES Content-Type: application/pdf Content-Range: bytes 500-999/8000 ...the first range... --THIS_STRING_SEPARATES Content-Type: application/pdf Content-Range: bytes 7000-7999/8000 ...the second range --THIS_STRING_SEPARATES--

 


 

Trailer (拖曳)

當訊息包括使用了 分塊傳輸編碼,且發送者希望在 訊息結尾處 補充其他欄位 時 — — Trailer (拖曳)
發送者應在表頭中生成一個 Trailer 表頭欄位,指示哪些欄位將以 Trailer (拖曳) 的方式補充在後方,
以利於動態生成的 元資料 (metadata),例如 訊息完整性檢查、數位簽章 或 後續處理狀態,
排除 被禁止使用的欄位,大部分皆可使用於 Trailer 中。
 
例如,在訊息的後方,提供快取的過期時間 (Expires) :

HTTP/1.1 200 OK

Content-Type: text/plain Transfer-Encoding: chunked Trailer: Expires

 

1C 這是中勝 安安你好>_^ 5 apple 2 is 4 good 0 Expires: Mon, 12 Jun 2017 10:51:21 GMT

 
[註]:
傳統 HTTP/1.1 可能使用 Content-MD5 表頭欄位
因語意一致性,已於 2014 年的 HTTP/1.1 修訂中 刪除
 
 

TE — 傳輸編碼協商

如同 Accept 對映於 Content-Type,指出接受/預期 的媒體類型,
Accept-Encoding 對映於 Content-Encoding,指出接受/預期 的內容編碼。
 
TE 表頭欄位 類似 「Accept-Transfer-Encoding」的概念 (無此欄位),
指出 接受/預期 的 傳輸編碼
 
不同的是:

它是指排除 chunked (分塊) 以外,還支援什麼 傳輸編碼,
以及 是否接受 trailer (拖曳) 欄位。

 
因為:

HTTP/1.1 將 chunked (分塊) 處理,
視為 使用者代理 (User Agent) 的基本能力。

 
因此 TE 欄位 中,不得 生成 chunked 欄位,
且不像 Accept-Encoding 擁有 “identity” 屬性 — — 指明不應用任何編碼 (no encoding)。
 
 

作者: 鄭中勝
喜愛音樂,但不知為何總在打程式 ? 期許能重新審視、整理自身所學,幫助有需要的人。

在《HTTP 酬載 — Content-Length, Transfer-Encoding, Content-Range, Trailer》中有 9 則留言

  1. 問題:酬載主體是否包含以下數據:message 結尾的 Trailer 部分(假設存在)?
    對 payload body 中的 body 的理解:
    在我的理解中,payload 是個相對的概念。比如我收到別人寄過來的一盒餅乾,對我來說,payload 是餅乾;對快遞公司來說,payload 是餅乾和盒子,快遞公司使用的紙盒叫開銷。回到 HTTP 中,請求和響應可能帶 message body,所以相對它們來說,payload 是封裝的資源的表示及描述表示的元數據;payload body 即 payload 的 payload,是封裝的資源的表示。
    但是 RFC 7231 Section 3.3 中定義了 payload header fields(Content-Length、Content-Range、Trailer、Transfer-Encoding),這些字段是描述 message body 的,與 payload body 不是對應關係。 (總感覺怪怪的,我理解錯了吧。T_T)
    原文為“Header fields that specifically describe the payload, rather than the associated representation, are referred to as “payload header fields””。
    貌似不會顯示評論,我發重複了幾次,十分抱歉。如果你能抽空解答我的疑惑,那就再好不過了 ^_^

    1. 厲害!您的問題相當細微,這也是我當初探討的一大重點。
      先從結論講起:「payload body 是 『封裝』的資源的表示」是對的,您並沒有理解錯誤 😄,以下我將從 message bodypayload body 的異同著手,逐步向您解釋 payload body 在 HTTP 中所代表的含義:

      首先,RFC7230 2.1節 中指出,payload body 會「包含於」 message body 中,這給了我們「」與「」的初步訊息。然而,在 RFC7230 3.3節 又說明:「若未使用 傳輸編碼 (transfer coding),則 message bodypayload body 兩者是相等的」。

      上述兩句話並不矛盾,根據集合論的反對稱性,兩個集合相等的充要條件便是「互相包含」。由「若p則q 等價於 若非q則非p」可以得知:「若 message body 不等於 payload body => 有使用傳輸編碼」。

      來到 3.3.1 節 中:「applied to the payload body in order to form the message body」,可以得知:未經過傳輸編碼的才是 payload body,經傳輸編碼後,實際送出的稱為 message body。此觀點可由 RFC7231 3.1.2.2節 進行驗證,大意就是:「Content 進行 (若有的話) Content-Encoding 後是 Representation ,payload body 進行(若有的話) Transfer-Encoding 則是 message body 」。

      因此,對於問題:「酬載主體是否包含以下數據:message 結尾的 Trailer 部分(假設存在)?」我的回答是:「否,這屬於 chunked transfer coding 的部分,message 最終將被 解碼 (decode) 並將 Trailer 所補充的表頭欄位置於正常 HTTP/1.1 訊息該有的位置,且解碼後的訊息也極有可能設置 Content-Length (視實作而定)」。
      演算法虛擬碼詳見 RFC7230 4.1.3 節

      最後非常感謝您耐心的看我文章,而且鑽研得如此細緻 😭!您可能是目前來這,首位跟我一樣苦讀 RFC 的朋友了 👍很榮幸能跟您討論交流 😄

  2. 看了你寫的幾篇文章,斷斷續續看了 RFC 7230 和 7231 的一些相關內容,感覺概念還是沒有理清,希望能得到你的幫助,謝謝。
    我的理解是:
    1. 資源 (Resource) 可以是任何事物,由 URI 標識。
    2. 用戶通過 URI 來訪問 Server 上的資源,Server 將資源的某種表示 (representation) 返回給用戶,並通過 Content-Type 和 Content-Encoding 等表頭欄位來描述表示。
    3. 資源的表示將封裝在酬載主體(payload body) 中,封裝的數量可能是(多)部分、整個或多個,這種數量的對應關係可能通過狀態碼(如206)、Content-Range和Content-Type(值為multipart 類型)表頭欄位來指示,但這兩個表頭欄位也是描述表示而不是描述酬載主體的。
    4. 酬載主體可以經過 Transfer-Encoding 編碼為 message body,並通過 Transfer-Encoding 或 Content-Length 等表頭欄位來描述 message body。
    如果理解有誤,煩請指正。
    One more time,希望我的不幂等操作不会给你带来不便。

    1. 大致觀念都相當正確唷! 一些小細節:
      1. 沒錯
      2. Content-Type 較為特殊,是用於描述 decoded representation
      3. Content-Range 是用於描述 酬載主體 (payload body)
      4. 正確

      希望我的 Response 能讓您滿意 😇

    2. 不好意思,插件許久未更新
      留言系統容易產生誤判 😅
      造成您的不便 敬請見諒

  3. 您好,
    這篇對我最近的研究有很大幫助,謝謝你的分享。
    期待在看到您更多的好文章,謝謝

發表迴響