搞定 HTTP 協議(三):如何嚴謹地描述一個 HTTP 報文?

在 HTTP 協議中,最爲核心的部分就是客戶端和服務器之間通訊時傳輸的報文了。HTTP 報文是由多行數據構成的字符串文本。一般狀況下,一個 HTTP 報文由如下 4 部分構成:html

  • 起始行(start line)git

  • 頭部字段(header fields)github

  • 一個空行(CRLF)安全

  • 報文主體(message body)bash

在以上四部分中,起始行與頭部字段,又常常被稱做「請求頭」或「響應頭」,而報文主體常常被稱爲「實體(entity)」,二者由最初出現的空行(CRLF)來劃分。而最後的報文主體是一個可選項,並不必定存在。服務器

一般狀況下,咱們都採用上面這種口語化的形式來描述 HTTP 報文的格式,可是這種口語化的表達並不十分嚴謹。好比,咱們一般會這樣描述一個請求頭:請求頭中包含了請求方法、請求 URI、HTTP 版本號,可是它們之間是否要加空格呢?加兩個空格能夠麼?再好比上圖中的 Host 頭部字段,冒號後面必須加一個空格麼?加兩個空格能夠麼?不加空格,而是用 Tab 製表符能夠麼?冒號前面能夠加空格麼?spa

若是咱們用口語化的表達來描述 HTTP 報文,就很難說清楚上面這些問題。所以,RFC 7230 文檔採用了 ABNF 範式來嚴謹的描述 HTTP 報文。code

ABNF 範式

ABNF 範式大致上分爲操做符核心規則兩大方面,這裏咱們不作區分,統一介紹一下與 HTTP 協議相關的描述。cdn

  • 選擇:使用反斜槓"/",表示多個規則可選其一
規則1 / 規則2
    
start-line = request-line / status-line // 起始行能夠是一個請求行,也能夠是一個狀態行
複製代碼
  • 可變重複
m * n
    
* 表示零個或多個元素:*(header field CRLF) // 能夠有零或多個,以 CRLF 結尾的頭部字段
1* // 表示一個或多個元素
複製代碼
  • 序列組合:使用小括號"()",將規則放在括號內組合起來,視做一個總體。
(規則1 規則2)
    
*(SP / HTAB) // 零個或多個(空格或者橫向製表符)
複製代碼
  • 可選序列:用中括號表示"[]"
[]
    
[ message-body ]: 報文主體是一個可選參數
複製代碼
  • 空白符:使用"SP"來表示,用來分隔定義的元素
SP %x20 空格
    
request-line = method SP request-target SP HTTP-Version CRLF
複製代碼
  • 水平 tab:用"HTAB"來表示
HTAB %x09 水平tab
    
header-field = field-name ":" *(SP / HTAB) field-value *(SP / HTAB)
// 頭部字段由字段名稱和字段值組成,中間以冒號分隔,冒號後面能夠有零個或多個空格或者橫向製表符
複製代碼
  • CRLF:互聯網標準換行,由 CR(回車)和 LF(換行)組成
CRLF

start-line
header-filed
CRLF
body-message
// 頭部字段和報文主體之間必須有一個 CRLF
複製代碼

瞭解上面這些 ABNF 範式中的操做符和核心規則後,咱們就能夠用 ABNF 範式來嚴謹的定義 HTTP 報文了:htm

// HTTP 報文構成:一個起始行;零個或多個頭部字段;一個空行;一個可選的報文主體

HTTP-message = start-line
              *( header-field CRLF )
              CRLF
              [ message-body ]
    
    
// 起始行構成:請求行或者狀態行
start-line = request-line / status-line
// 請求行構成:請求方法;空格;請求目標;空格;協議版本;換行
request-line = method SP request-target SP HTTP-version CRLF
// 狀態行構成:協議版本;空格;狀態碼;空格;緣由短語;換行
status-line = HTTP-version SP status-code SP reason-phrase CRLF
    
    
// 頭部字段構成:
// 一個不區分大小寫的字段名稱;一個英文冒號;零個或多個空格或橫向製表符;字段值;零個或多個空格或橫向製表符
header-field = field-name ":" OWS field-value OWS
    field-name = token
    OWS = *(SP / HTAB)
    field-value = *(field-content / obs-fold)
    
    
// 報文主體構成:用於攜帶請求或響應的有效載荷體
message-body = *OCTET
複製代碼

起始行

一個 HTTP 報文能夠是從客戶端到服務器的請求報文,也能夠是從服務器到客戶端的響應報文。一般狀況下,對於請求報文來講,咱們稱它的起始行爲請求行;而對於響應報文來講,咱們稱它的起始行爲狀態行

請求行

請求行描述了客戶端想要如何操做服務器上的資源。它一般包括:

  • 請求方法(method):但願如何操做資源
  • 請求目標(request-target):一般是一個 URI,表示資源的位置
  • 協議版本(HTTP-version):使用的 HTTP 版本

以實際的例子來講:

GET /index.html HTTP/1.1
複製代碼

"GET" 是請求方法, "/index.html" 是請求目標,"HTTP/1.1"是協議版本。利用這一行請求行,就能夠明確的告訴服務器:我想獲取根目錄下的 index.html 文件,個人 HTTP 版本號是 1.1。

狀態行

狀態行描述了服務器的響應狀態。它一般包括:

  • 協議版本(HTTP-version):使用 HTTP 的版本

  • 狀態碼(status-code):狀態碼其實也有對應的 ABNF 描述 3DIGIT, 表示一個三位整數,好比常見的 200

  • 描述狀態碼的緣由短語(reason-phrase):用來解釋狀態碼的具體緣由

仍是以實際的狀態行來講:

HTTP/1.1 200 ok
複製代碼

"HTTP/1.1" 是協議版本,"200" 是狀態碼,"ok" 是緣由短語。意思就是告訴客戶端:找到了相應資源,我已經處理好了你的請求。

頭部字段

從上面的圖中能夠知道:每一個頭部字段是一個典型的 key-value 格式,最後以 CRLF 表示結束,而且在整個頭部字段的最後,必須由一個 CRLF 表示頭部字段的結束。

Host: 127.0.0.1:9090
Content-Type: text/html
...
複製代碼

對於頭部字段來講,有一些特色須要咱們注意:

  • 頭部字段是徹底可擴展的,使用新字段名稱是沒有限制的
  • 字段名稱大小寫都可,但一般狀況下首字母須要大寫
  • 不一樣字段名稱的字段順序不重要,可是先發送包含控制數據的頭部字段是一個良好的實踐。例如請求中的 Host 和響應中的 Date,這樣當實現不處理一個報文的時候,能夠儘量早的作出判斷
  • 字段名稱和冒號之間不容許出現空白,由於可能會致使安全漏洞。冒號後能夠有一個或多個空格(也能夠是橫向製表符)。字段值後也能夠跟一個或多個空格(也能夠是橫向製表符),但最後要有一個 CRLF。一般狀況,在冒號後面加一個空格是良好的習慣

頭部字段一般分爲如下四種:

  • 通用頭部:既能夠出如今請求頭中,也能夠出如今響應頭中,如 Date 字段;
  • 請求頭部:只能出如今請求頭中,用於解釋說明請求信息,如 Host 字段;
  • 響應頭部:只能出如今響應頭中,用於解釋說明響應信息,如 Server 字段;
  • 實體頭部:用於表述報文主體,如 Content-Length 字段,表示報文主體的長度。

HTTP 報文是 HTTP 協議的核心,而頭部字段就是 HTTP 報文的核心。充分理解了常見的頭部字段,HTTP 協議就不在話下了,後面的文章會重點介紹常見的重要頭部字段。

報文主體

HTTP 協議中不要求報文主體必須存在,若是存在的話,報文主體用於攜帶請求或響應的有效載荷體。

一般狀況下,首部字段中的 Content-LengthTransfer-Encoding 是請求中報文主體存在的信號。而響應中報文主體的存在取決於響應的請求方法狀態碼。如 HEAD 請求方法的響應從不包括報文主體,而全部的 1xx,204 以及 304 的響應也不包含報文主體。

小結

本文詳細介紹了 HTTP 的報文結構,除了常見的口語化表達外,還引入了 RFC 7230 文檔中用於描述 HTTP 報文的 ABNF 範式,進行嚴謹的描述。

  1. HTTP 報文主要由起始行、零個或多個頭部字段、CRLF 以及可選的報文主體構成
  2. 起始行與頭部字段常常被稱爲請求頭或響應頭
  3. 請求中的起始行叫作請求行,由請求方法、請求目標、協議版本組成
  4. 響應中的起始行叫作狀態行,由協議版本、狀態碼、緣由短語組成
  5. 頭部字段的字段名通常大寫第一個字母,後面緊跟冒號,不容許有空格;冒號與字段值直接能夠有零個或多個空格或橫向製表符
  6. 頭部字段一般分爲四種:通用頭部、請求頭部、響應頭部以及實體頭部
  7. 頭部字段與報文主體之間,必須以一個 CRLF 區分
  8. 報文主體能夠不存在

最後的話

你的點贊會給我一天好心情,若是能順手 來個 star,再順便關注下公衆號(零幺小館)就更完美了。

參考資料

  1. RFC 7230 文檔
  2. 極客時間 - 《透視 HTTP 協議》
  3. 極客時間 - 《Web 協議詳解與抓包實戰》
  4. 《圖解 HTTP》
相關文章
相關標籤/搜索