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)。
 
 

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

發表迴響