資料酬載 (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),
同樣是對 酬載主體 進行編碼,而非表頭,
且一樣能夠使用 gzip、deflate、br (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., GET、HEAD、DELETE、CONNECT、TRACE):
酬載 (payload) 是 未定義的語意 (無意義的),
若硬要送出,「可能」使 Server 拒絕此請求。
事實上,排除 TRACE 方法 外,HTTP 並無 嚴格限制 「請求方法 不准攜帶 酬載」,
換句話說,只要 Server 支援,一樣能在 GET 訊息 附加酬載送出。
(儘管這是不建議的做法)
因此,當使用這些請求方法時 (酬載無意義),
使用者代理 (User Agent) 不應 生成 Content-Length 欄位。
反之,若請求方法預期含有酬載主體 (e.g., POST、PUT、PATCH、OPTIONS),
都 應該 生成 Content-Length 欄位。
儘管長度可能為零
[註]:
是的,OPTIONS 方法並非無意義,然而規範尚未定義其用途,
僅預計未來的擴展可能使用 OPTIONS 主體,來更詳細地查詢目標資源。
簡單來說:
訊息主體 (Message Body) 在 請求訊息中 是否存在,
由 Content-Length 或 Transfer-Encoding 欄位作為信號;
而與 方法語意 無關,即使該方法 未定義 訊息主體的任何用途。
許多人誤將 請求方法 視為訊息主體是否存在酬載的依據。
回應訊息
除了稍後會提及的一些情形,若發送完整表頭前,酬載大小已知,
源伺服器 (Origin Server) 應發送一個 Content-Length 欄位。
這將允許 下游 (downstream) 接收方測量傳輸進度,以知訊息何時完整接收,
並能夠 重新使用連線 來獲取其他請求。
儘管長度可能為零
[註]:
所謂術語「上/下 游」:
一個原則:
『所有』 訊息,都由 上游 『流』到 下游,
與 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 則留言
問題:酬載主體是否包含以下數據: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””。
貌似不會顯示評論,我發重複了幾次,十分抱歉。如果你能抽空解答我的疑惑,那就再好不過了 ^_^
厲害!您的問題相當細微,這也是我當初探討的一大重點。
先從結論講起:「payload body 是 『封裝』的資源的表示」是對的,您並沒有理解錯誤 😄,以下我將從 message body 與 payload body 的異同著手,逐步向您解釋 payload body 在 HTTP 中所代表的含義:
首先,RFC7230 2.1節 中指出,payload body 會「包含於」 message body 中,這給了我們「內」與「外」的初步訊息。然而,在 RFC7230 3.3節 又說明:「若未使用 傳輸編碼 (transfer coding),則 message body 與 payload 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 的朋友了 👍很榮幸能跟您討論交流 😄
你的理解十分深刻,对我的帮助很大,谢谢!
看了你寫的幾篇文章,斷斷續續看了 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. 沒錯
2. Content-Type 較為特殊,是用於描述 decoded representation
3. Content-Range 是用於描述 酬載主體 (payload body)
4. 正確
希望我的 Response 能讓您滿意 😇
不好意思,插件許久未更新
留言系統容易產生誤判 😅
造成您的不便 敬請見諒
您好,
這篇對我最近的研究有很大幫助,謝謝你的分享。
期待在看到您更多的好文章,謝謝
很高興能幫上忙!我會繼續努力創作的 😭
你好,我是一個WEB新手,最近在研究HTTP的傳輸格式,看到你的文章,收穫頗豐,
不過,也有些問題,