http的核心部分是http傳輸的報文內容
http的報文結構
http協議是一個“純文本”的協議,所以頭數據都是 ASCII 碼的文本。
ASCII 碼, 一個字節表示一個字母,所以 HTTP 報文會比較大;而那些二進制協議,可以利用一個字節的 8 個比特表示更多信息,這些信息通常是協議規定,所以需要對應程序進行解析。相比較而言,可以很容易地用肉眼閱讀,不用借助程序解析也能夠看懂。4
HTTP 協議的請求報文和響應報文的結構基本相同,由三大部分組成:
- 起始行(start line):描述請求或響應的基本信息;
- 頭部字段集合(header):使用 key-value 形式更詳細地說明報文;
- 消息正文(entity):實際傳輸的數據,它不一定是純文本,可以是圖片、視頻等二進制數據。
這其中前兩部分起始行和頭部字段經常又合稱為“請求頭”或“響應頭”,消息正文又稱為“實體”,但與“header”對應,很多時候就直接稱為“body”。
HTTP 協議規定報文必須有 header,但可以沒有 body,而且在 header 之后必須要有一個“空行”,也就是“CRLF”,十六進制的“0D0A”。
所以,一個完整的 HTTP 報文就像是下圖的這個樣子,注意在 header 和 body 之間有一個“空行”。
請求報文的起始行-請求行
請求行(request line),它簡要地描述了客戶端想要如何操作服務器端的資源。
請求行由三部分構成:
- 請求方法:是一個動詞,如 GET/POST,表示對資源的操作;
- 請求目標:通常是一個 URI,標記了請求方法要操作的資源;
- 版本號:表示報文使用的 HTTP 協議版本。
這三個部分通常使用空格(space)來分隔,最后要用 CRLF 換行表示結束。
例如:
GET / HTTP/1.1
在這個請求行里,“GET”是請求方法,“/”是請求目標,“HTTP/1.1”是版本號,把這三部分連起來,意思就是“服務器你好,我想獲取網站根目錄下的默認文件,我用的協議版本號是 1.1,請不要用 1.0 或者 2.0 回復我。”
響應報文的起始行-狀態行
“狀態行”(status line),意思是服務器響應的狀態。
狀態行由三部分構成:
版本號:表示報文使用的 HTTP 協議版本;
狀態碼:一個三位數,用代碼的形式表示處理的結果,比如 200 是成功,500 是服務器錯誤;
原因:作為數字狀態碼補充,是更詳細的解釋文字,幫助人理解原因。
例如
HTTP/1.1 200 OK
意思就是:“瀏覽器你好,我已經處理完了你的請求,這個報文使用的協議版本號是 1.1,狀態碼是 200,一切 OK。”
頭部字段
請求行或狀態行再加上頭部字段集合就構成了 HTTP 報文里完整的請求頭或響應頭,如圖:
請求頭和響應頭的結構是基本一樣的,唯一的區別是起始行,所以把請求頭和響應頭里的字段放在一起介紹。
頭部字段是 key-value 的形式,key 和 value 之間用“:”分隔,最后用 CRLF 換行表示字段結束。
比如在“Host: 127.0.0.1”這一行里 key 就是“Host”,value 就是“127.0.0.1”。
HTTP 頭字段非常靈活,不僅可以使用標准里的 Host、Connection 等已有頭,也可以任意添加自定義頭,這就給 HTTP 協議帶來了無限的擴展可能。
不過使用頭字段需要注意下面幾點:
- 字段名不區分大小寫,例如“Host”也可以寫成“host”,但首字母大寫的可讀性更好;
- 字段名里不允許出現空格,可以使用連字符“-”,但不能使用下划線“_”。例如,“test-name”是合法的字段名,而“test name”“test_name”是不正確的字段名;
- 字段名后面必須緊接着“:”,不能有空格,而“:”后的字段值前可以有多個空格;
- 字段的順序是沒有意義的,可以任意排列不影響語義;
- 字段原則上不能重復,除非這個字段本身的語義允許,例如 Set-Cookie。
常用頭字段
HTTP 協議規定了非常多的頭部字段,實現各種各樣的功能,但基本上可以分為四大類:
- 通用字段:在請求頭和響應頭里都可以出現;
- 請求字段:僅能出現在請求頭里,進一步說明請求信息或者額外的附加條件;
- 響應字段:僅能出現在響應頭里,補充說明響應報文的信息;
- 實體字段:它實際上屬於通用字段,但專門描述 body 的額外信息。
對 HTTP 報文的解析和處理實際上主要就是對頭字段的處理,理解了頭字段也就理解了 HTTP 報文。
- Host
屬於請求字段,只能出現在請求頭里,它同時也是唯一一個 HTTP/1.1 規范里要求必須出現的字段,也就是說,如果請求頭里沒有 Host,那這就是一個錯誤的報文。
Host 字段告訴服務器這個請求應該由哪個主機來處理,當一台計算機上托管了多個虛擬主機的時候,服務器端就需要用 Host 字段來選擇,有點像是一個簡單的“路由重定向”。
- User-Agent
請求字段,只出現在請求頭里。它使用一個字符串來描述發起 HTTP 請求的客戶端,服務器可以依據它來返回最合適此瀏覽器顯示的頁面。
- Date
是一個通用字段,但通常出現在響應頭里,表示 HTTP 報文創建的時間,客戶端可以使用這個時間再搭配其他字段決定緩存策略。
- Server
響應字段,只能出現在響應頭里。它告訴客戶端當前正在提供 Web 服務的軟件名稱和版本號。
- Content-Length
它表示報文里 body 的長度,也就是請求頭或響應頭空行后面數據的長度。服務器看到這個字段,就知道了后續有多少數據,可以直接接收。如果沒有這個字段,那么 body 就是不定長的,需要使用 chunked 方式分段傳輸。
小結
- HTTP 報文結構就像是“大頭兒子”,由“起始行 + 頭部 + 空行 + 實體”組成,簡單地說就是“header+body”;
- HTTP 報文可以沒有 body,但必須要有 header,而且 header 后也必須要有空行,形象地說就是“大頭”必須要帶着“脖子”;
- 請求頭由“請求行 + 頭部字段”構成,響應頭由“狀態行 + 頭部字段”構成;
- 請求行有三部分:請求方法,請求目標和版本號;
- 狀態行也有三部分:版本號,狀態碼和原因字符串;
- 頭部字段是 key-value 的形式,用“:”分隔,不區分大小寫,順序任意,除了規定的標准頭,也可以任意添加自定義字段,實現功能擴展;
- HTTP/1.1 里唯一要求必須提供的頭字段是 Host,它必須出現在請求頭里,標記虛擬主機名。
問答
1、如果拼 HTTP 報文的時候,在頭字段后多加了一個 CRLF,導致出現了一個空行,會發生什么?
答:在header 下面第一個空行以后都會被當作body 體
2、講頭字段時說“:”后的空格可以有多個,那為什么絕大多數情況下都只使用一個空格呢?
答:頭部多一個空格就會多一個傳輸的字節,去掉無用的信息,保證傳輸的頭部字節數盡量小