HTTP

HTTP/1.1 — 訊息格式 (Message Format)

HTTP 透過 統一介面 (uniform interface) 使:
Client 送出 請求 (Request)
Server 送出 回應 (Response)
 
 

那「請求」與「回應」到底是傳輸什麼?

 

訊息 (Message) !

 


 

訊息格式 (Message Format)

訊息 (Message),又譯為 報文、信息、消息,
是 HTTP/1.1 的最小傳輸單元。
 
[註]:
HTTP/2 的最小傳輸單元為 訊息框 (Frame)
多個訊息框 組合 為一個訊息
 
所有訊息都是由:

  • 起始行 (start-line) 開始
  • 0 或多個 表頭欄位 (header-field) + CRLF
    [合稱為 表頭 (headers) 或是 表頭部分 (header section)]
  • 再加上一個 CRLF
  • 最後是 可選的 (optional) 訊息主體 (message-body)

 

HTTP-message = start-line *( header-field CRLF ) CRLF [ message-body ]

 
 
請求訊息 (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"} ] }

 
 
回應訊息 (response message) 範例 :
(顏色對應上方格式)

HTTP/1.1 200 OK

Content-Type: text/html; charset=utf-8 Date: Sat, 18 Feb 2017 00:01:57 GMT Server: nginx/1.11.8 transfer-encoding: chunked Connection: Close

 

<!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <title>echo</title> ....略

 
 
請求/回應 訊息,語法上格式相同,
而 內容 與 訊息主體 (message body) 的規則不同。
 


 

CRLF
(carriage return followed by line feed)

 

HTTP-message = start-line *( header-field CRLF ) CRLF <-- -- 在這! [ message-body ]

 
這裡先插個隊 ! 因為接下來會一直用到它 😄。
 
CRLF (carriage return followed by line feed),
是網際網路 (Internet) 嚴謹的 換行 (newline) 標準 ,
也就是鼎鼎大名的:

\r\n

 
CR = 回車 (Carriage Return),又稱 歸位,
源於傳統的電傳印表機 (TTY),用於回到一行字的起頭,而非換行。
許多語言表示為: \r
在 Unicode 中為 0x0D (十進制的 13)。
(顯然跟我們鍵盤上的 Enter (return) 意義已不相同)
 
LF = 換行 (Line Feed),
許多語言表示為: \n
在 Unicode 中為 0x0A (十進制的 10)。
 

雖然多數語言 \n 就能做到換行,
規範上,仍以 \r\n 為主。

 
對應訊息範例的:

 

 
CRLF,是接續在 表頭 (Header) 之後的 必要 部分。
然而,許多粗心的程設設計師,在沒有 訊息主體 (message body) 部分時,
時常忽略了 CRLF,是實作上需注意的地方。
 


 

起始行 (start-line)

HTTP-message = start-line <-- -- 在這! *( header-field CRLF ) CRLF [ message-body ]

 
起始行 (start-line),做為訊息的開始,
請求訊息回應訊息 內容上最大的差異,
因應 請求、回應,分別稱為:

請求行 request-line
狀態行 status-line

 
 

請求行 (request-line)

請求行 (request-line) 依序包含:

方法 (method)、空白 (space, SP)、
請求目標 (request-target)、再一個 空白 (space, SP)、
HTTP 版本 (HTTP-version)、最後是已介紹過的 CRLF (carriage return followed by line feed)

 
對照上方的請求訊息 (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"} ] }

 
即此部分 :

POST /task?id=1 HTTP/1.1

 
 

1. 方法 (method)

方法 (method),對應請求範例的 POST
全稱 請求方法 (request method)。
 
(2-2) 資源、表示、URI 提及:
URI 對映的資源 是 『名詞』,
而操作這些資源的『動詞』: GET、POST、PUT、DELETE… 就稱為 方法 (method)。
 
常聽到的 GET 請求、POST 請求…,就是由此欄位所決定 !
因此可知,它們本質上是相同的東西 (i.e., 訊息),只是規範與使用方式不同。
 
Ex:

https://example.org/apple

這個 URI 有蘋果 🍎 這個資源,
使用 『GET』 方法,就是 『給』我蘋果🍎 啦!
(4-2) GET vs. POST 有詳盡的說明 😉
 
需注意:

『 方法 』是 區分大小寫的 ( case-sensitive ),需使用大寫英文。

 
 

2. 空白 (space, SP)


Photo by Apple Inc.
 
 

3. 請求目標 (request-target)

對應請求範例的 /task?id=1
 
請求目標 (request-target),總共有四種形式,
採用何種形式,取決於使用的 請求方法 (Request Method) 與 代理 (proxy) 方式。
 
礙於篇幅,這裡只介紹 原始形式 (origin-form)
可以在 (5-1) 訊息路由 (Message Routing) 查看更多 請求目標 (request-target)
 
原始形式 (origin-form),是最常見的形式:

origin-form = absolute-path [ "?" query ]

 
(2-1) 統一資源識別符 (URI) :

http(s) URI 格式 =
"http(s):" "//" authority path-abempty [ "?" query ] [ "#" fragment ]

 
原始形式 origin-form 類似 path-abempty + [ "?" query ]
 
不同之處在於:

不能為 空路徑,必須以 " / " 開始 !

 
例如訪問:

https://echo.paw.cloud/?id=1

 
原始形式 (origin-form) 為:

/?id=1

 
也就是範例 /task?id=1 的由來啦 !
 
 
如果把 query 拿掉呢?

https://echo.paw.cloud

 
原始形式 (origin-form)為:

/

 

記得要有 『 / 』!!

 
當 Client 直接 將訊息發送給 Origin Server
(且不是使用 CONNECT 請求 或 伺服器整體的 OPTIONS 請求)
必須使用 原始形式 origin-form 作為 請求目標 (request-target)
 
 

4. 空白 (space, SP)


Photo by Apple Inc.
 
 

5. HTTP 版本 (HTTP-version)

對應請求範例的 HTTP/1.1
 
格式為:

HTTP-name "/" DIGIT "." DIGIT

 
Ex:
HTTP/1.1、HTTP/1.0
 
注意:

『 HTTP 』需為大寫

 
 

6. CRLF

介紹過啦 ! 以一個『空行』代表請求行的結束。
 
 

狀態行 (status-line)

狀態行 (status-line) 依序包含:

HTTP 版本 (HTTP-version)、空白 (space, SP)、
狀態碼 (status-code)、再一個 空白 (space, SP)、
原因短語 (reason-phrase)、以及 CRLF (carriage return followed by line feed)。

 
對照上方的回應訊息 (response message) 範例,

HTTP/1.1 200 OK

Content-Type: text/html; charset=utf-8 Date: Sat, 18 Feb 2017 00:01:57 GMT Server: nginx/1.11.8 transfer-encoding: chunked Connection: Close

 

<!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <title>echo</title> ....略

 
即此部分 :

HTTP/1.1 200 OK

 
 
HTTP 版本 (HTTP-version)、SP、CRLF,不再累述。
 
 

狀態碼 與 原因短語
(status-code & reason-phrase):

對應回應範例的 200OK
 
HTTP Response Code Cheatsheet
 
(4-6) 回應狀態碼 (Response Status Codes) 將有詳盡的介紹。
 


 

表頭 (header)

HTTP-message = start-line *( header-field CRLF ) <-- -- 在這! CRLF [ message-body ]

 
表頭 (header),又譯為 首部、檔頭、標頭,
是由 0 或多個 表頭欄位 (header-field) + CRLF 組合而成。
類似程式語言 函式/方法的 參數 (parameter)。
 
例如,你對一服務生指定 目標資源 — — 牛排,
而 要幾分熟、幾份餐具、點心什麼時候上…,
這種 附加的重要資訊,即為 表頭 (header)
 
加上 起始行 (start-line) 部分,
(1-3) 統一介面 所述之 介面約束 — — 自我描述的訊息 (self-descriptive messages)
 
對照上方的請求訊息 (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"} ] }

 
即此部分 :

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

 
 

表頭欄位 (header-field)

單個 表頭欄位 (header-field) 的組成 :

  • 一個 不區分大小寫的 欄位名稱 (field-name) — — 習慣上仍以首字大寫為主
  • 緊接一個 冒號 (colon ":")
  • 可選的空白 (optional trailing whitespace, OWS)
  • 欄位值 (field-value)
  • 可選的空白 (optional trailing whitespace, OWS)

 

header-field = field-name ":" OWS field-value OWS

 
欄位名稱 (field-name)冒號 (colon ":") 之間不可有空白!
 

多個欄位值之間,可用 空白分隔符號 分隔

 
分隔符號 (delimiter) :

" ( ) , / : ; < = > ? @ [ \ ] { }

 
也可以在表頭中,以 括號 補充說明 — — 註釋 (Comment) :

(Macintosh; Intel Mac OS X 10.12; rv:53.0)

 
一個訊息,沒有限制欄位的數量,
表頭名稱的引入,也沒有任何限制。
因此,有許多常見的 非標準表頭欄位
 
所有正式定義的訊息表頭,都會在 網際網路號碼分配局 (IANA) 註冊
 
 

ASCII

從上例不難看出,排除 分隔符號 以外,
表頭欄位 (header-field) 是基於拉丁字母的 ASCII 碼。
 
而 HTTP/2 則進一步,將表頭欄位壓縮為 二進制 的有序串列,
稱為 表頭區塊片段 (Header Block Fragment)
 

這也是 HTTP/1.1 和 HTTP/2 最大的差異 之處 !

 
隨著網頁已成長到需要數十到數百個請求,
HTTP/1.1 未經壓縮的表頭,
使訊息頻繁地出現冗餘的欄位,不必要地消耗頻寬並造成延遲。
 
HTTP/2 則以 表頭壓縮 (Header Compression)
大幅地增進效能與擴充性 !
 
 

重複欄位 (Duplicate Header)

發送方 (sender) 不得在訊息中,生成相同的表頭欄位名稱。
除非:

  • 整個個欄位值,以 逗號分隔 (comma-separated) 列表呈現
  • 表頭欄位是公認的例外 (i.e., Set-Cookie 表頭欄位)

 
此時,接收方 (recipient) 可以將多個相同欄位名稱的值,
照順序以 逗號分隔 (comma-separated) 組合在一起,而不改變訊息的語意。
 
因此:

接收 具有相同欄位 名稱的訊息時,順序 是很重要的,
代理 (proxy) 不得在轉發訊息時,更改這些欄位值的順序。

 
Set-Cookie 欄位,是一個公認的特殊情形
實際上,他時常在 回應訊息中 出現多次,
並非使用逗號分隔,也無法組合成單個表頭欄位,
接收方 (recipient) 必須將 Set-Cookie 做為特殊情形處理。
 
 

欄位順序 (Field Order)

除了上一節會提到的名稱重複問題,
表頭欄位的順序,對於接收方並不是太重要。

 
然而,優良的做法仍是以 『控制』欄位為首,
(e.g., 請求訊息 優先使用 Host 欄位,回應訊息 優先使用 Date 欄位。)
這樣的好處是,接收方可以儘早的決定,是否處理訊息
 
注意事項:

在確實接收完整的表頭欄位前,
Server 不應該將請求應用於目標資源上。

 
因為後續有可能出現條件請求、驗證憑證、故意誤導的重複欄位…,
這些影響請求處理的表頭欄位。
 


 

訊息主體 (Message Body)

HTTP-message = start-line *( header-field CRLF ) CRLF [ message-body ] <-- -- 在這!

 
訊息主體 (message body),又譯為 訊息體、報文主體,
就像包裹的箱子,是 訊息 (message) 中 乘載 資料的地方。
 

 
能乘載任意格式的資料 (位元組):

 
對照上方的請求訊息 (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"} ] }

 
 

酬載 (payload)

資料酬載 (payload) 或稱 酬載、有效載荷,
是傳輸時 實際預期的資料,可能是 完整 or 部分 的 表示 (representation)。
 
也就是範例中的 :

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

 
因此,我們稱 訊息主體 (Message Body) 包含了 (contain) 酬載主體/有效載體 (payload body)
 
與 表示 (representation) 不同的是,
酬載 (payload) 語意上 不考慮資料的內容是何種 格式、語言、編碼…,
強調的是傳輸的 資料 位元組 (octet) 本身,
更多差異,詳見 (3-4) 酬載 — Content-Length, Transfer-Encoding…
 
描述酬載的 元資料 (metadata) — — 酬載表頭欄位 (payload header fields):

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

 
 

作者: 鄭中勝

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

發表迴響