HTTP初探
1. HTTP版本
自 HTTP 協議發明到現在,經過了幾次版本修改,分別是HTTP/0.9,HTTP/1.0,HTTP/1.1以及HTTP/2。現在市面上主要還是?HTTP/1.1,我們本文也主要介紹的是該版本。
2. TCP/IP協議
在學習 HTTP 協議之前我們先來了解下 TCP/IP 協議。它是 HTTP 的基礎,地基打穩了房子才能結實!
大家在工作過程中可能經常聽到 OSI 七層網絡結構以及 TCP/IP 四層網絡結構等,但是這些都是什么呢?你平時工作中是否被搞暈過呢?
左邊的是大名鼎鼎的國際標準化組織 ISO 指定的網絡結構模型。但是實際上大家使用的都是右邊的?TCP/IP 四層網絡結構。
ISO指定網絡標準的時候,TCP/IP已經成為了事實上的標準,所以造成了一種奇葩現象。國際協會指定了標準,但是大家都不用~~
下面我們簡單的聊一下 TCP/IP 四層結構中每一層的作用。
應用層:?這一層就是上層應用使用的協議。比如HTTP協議,FTP協議,SMTP協議等。
傳輸層:?從物理層到傳輸層這三層都是負責建立網絡連接,發送數據。他們并不關心應用層使用什么協議。傳輸層使用的就是TCP和UDP協議。
網絡層:?這一層負責選擇路由路徑。條條大路通羅馬嘛,所以從相同的出發地到相同的目的地?也會有很多種路徑,網絡層就負責選擇一條數據通行的路徑。
物理層:?這一層負責數據的發送。這是最底層的網卡負責的。網卡把數據轉換成高/低電平,然后通過網線發送出去。
下面我們以一個實際的例子來說明這個過程:
上圖是 主機A 向 主機B 發送 hello world 的過程。用戶發出一個請求之后,從應用層開始,一直到物理層,每一層都會被加上蓋層所屬的附加信息;在接收端,每經過一層都會去掉該層的附加信息,然后交給上層處理。
是不是很像洋蔥,一層又一層……
應用層把待發送的信息 hello world 使用自己的協議進行封裝,然后調用傳輸層的接口,以此類推,最終數據傳遞給了物理層。每一層都會加上自己特有的一些標志信息。物理層最后把要發送的數據(包含應用層真正想發送的hello world 以及每個層自己增加的標識信息)轉換為高低電平發送出去,接收端收到之后進行一個逆向的解析過程,最后主機B收到了 hello world。
上面是一個簡單的描述,但是整體的原理就是這樣的~ 大家不要被每一層的概念所迷惑。這些層是人為劃分出來的概念。就是為了寫代碼實現的時候比較方便。只需要定義每個層的接口,上一層調用下一層的接口就行了,不必關心具體實現。這也是軟件設計中的一種理念。
3. HTTP協議
上面簡單的說了一下網絡的結構,這些東西是?HTTP 運行的基礎。?下面我們聊一下本文的重點:HTTP 協議相關的內容。
3.1 HTTP協議總覽
我們先看一下 HTTP 協議的總覽圖,如下:
3.2 HTTP協議結構
圖中可以看到,HTTP協議分為三個部分,其中報文頭部和報文主體之間使用空行進行分隔。
HTTP 是一個?Request-Response?的協議。
客戶端發出 HTTP 請求( request );
服務器接收請求,處理請求,然后返回響應結果( response );
客戶端接收響應結果,進行其它處理。
下面我們針對HTTP的請求和響應分別學習他們的報文結構。
3.3 HTTP 請求報文
我們可以看到,HTTP請求報文分為三部分:
請求行
請求頭
請求體 ?
3.3.1 請求行
請求行包含了三部分,分別是請求方法,請求的?URL?以及請求體。
3.3.2 請求方法
HTTP/1.1支持多種請求方法 (?method?),如下圖:
圖中的數字表示的是支持該方法的最低 HTTP 協議版本,例如我們從圖中可以看到HTTP/0.9只支持GET方法。在工作中我們最常用到的是?GET?和?POST?方法。下面我們簡單來聊一下這兩種方法的區別。
3.4 GET 方法和 POST 方法
3.4.1 GET : 獲取資源
一般來說,GET 請求只用于獲取數據。比如我們獲取指定頁面的信息,并返回響應。
上圖是我們訪問慕課網的首頁,我們可以發現使用的就是GET方法。
3.4.2 POST : 請求資源
POST 請求主要是向指定資源提交數據,服務器接收到這些數據進行處理。比如我們在注冊頁面中填寫了一些個人信息,當我們提交這些信息的時候會向服務器發送一個?POST?請求,將信息放在請求體中,服務器收到之后進行相應的處理。
雖然 HTTP 協議中的請求方法很多,但是在大部分的時候 GET 方法和 POST 方法已經滿足我們的需求了,我在工作中最常用的也是這兩種方法,別的方法基本沒用過……
3.4.3 ?GET 和 POST 的區別
說了這么多,那么 GET 方法和 POST 方法到底有什么區別呢?這是一個繞不開的話題,而且這個問題在面試的時候也經常會被問到。也許我們被問到這個問題的時候都能扯上一兩句,比如:
GET ?請求的參數是通過 URL 傳遞的, POST ?請求參數是通過 Request Body ?傳遞的;
GET 允許傳遞的參數沒有 POST ?的長度長。 ?
還有很多類似答案,但是事實上是這樣的嗎?我不得不告訴你一個殘酷的事實:GET和POST其實沒有任何區別 ~
HTTP 是一個應用層的協議,它底層使用的是?TCP/IP,所以?GET?和?POST?二者的底層是相同的,二者能做的事情是一模一樣的。我們一一駁斥上面的幾個理由。
如果你愿意, GET ?請求也可以有 Request body ,你可以請求參數放到 Request body ?中是完全可以的。同樣地,你也可以在 POST ?請求的 URL ?中加入請求參數。本人剛加入微博的時候,發現很多 POST ?請求的 URL ?中都包含了很多參數,對此深表疑問,后來查了很多資料才發現,原來是自己一直理解錯誤了,尷尬~
GET ?和 POST ?參數長度問題。這些長度并不是 HTTP 規定的,而是瀏覽器和服務器自己的規定,和 HTTP 協議沒有一毛錢的關系。 ?
瀏覽器和服務器為了節省內存,所以都會限制我們參數的大小。在?Nginx?中可以通過large_client_header_buffers來限制請求頭的長度~
那說來說去,二者是一樣的,那為啥要搞兩種方法呢?
來,我們劃重點了:有一些客戶端,比如?CURL?命令,當?POST?的數據大于?1024?字節的時候,會先分為兩個步驟:
向服務器發送一個包含 Expect: 100-continue ?請求,詢問服務器是否愿意接收數據。
服務器返回 100 continue , curl ?把真正的 POST ?數據發送給服務器。
當然了,并不是所有的服務器都會返回?100-continue,有的會返回?417 Expectation Failed,這時候就不能繼續?POST?了。
到這里之后,大家是否明白了?GET?和?POST?的區別了呢?
3.5 請求URL
這部分就是我們請求的資源的地址,它配合請求頭部的?Host?屬性共同工作。
3.6 協議版本
這部分就是當前請求使用的?HTTP?協議的版本信息。上面我們提到過,不同的?HTTP?版本的功能是不相同的,所以我們要明確的說明當前請求使用的是哪個版本。
3.7 請求頭
請求頭是個什么東西呢?我們不妨以一個例子說明:
放學的時候,王老師對小明說,“小明,明天早上八點,你去學校西門口迎接一位新同學小李,他穿白色T恤,身高180……”
在這里面,接小李就是HTTP的報文,而明天早上八點, 學校西門口,白色T恤,身高180這些附加信息就相當于HTTP 的請求頭,這些信息是為了完成工作所添加的額外信息。
HTTP 的請求和響應都包含報文頭,報文頭分為如下幾種:
通用首部字段
請求首部字段
響應首部字段
實體首部字段 ?
每一種類型的頭部都有很多不同的選項,我們先介紹前兩種報文頭,后面在學習響應報文時介紹剩下的兩種報文格式。
3.8 通用首部
通用首部既可以用在Request中,也可以用在Response中。常用的有以下幾個:
?
首部字段 | 作用 |
---|---|
Date | 報文的日期相關 |
Cache-Control | 控制緩存 |
Connection | 管理連接 |
?
我們這里主要看一下?Connection?選項,這個選項是用于管理?HTTP?連接的,格式如下:
Connection:?keep-alive
Connection:?close
3.9 keep-alive
HTTP 是基于?Resquest-Response?形式的,客戶端每次發送完一個請求之后,等待服務器響應,最后和服務器斷開連接,本次請求結束。由于 HTTP 是基于?TCP/IP?傳輸的,當?HTTP?發送請求的時候要和服務器經過三次握手建立連接,斷開的時候要經過四次揮手進行斷開。如果有大量的 HTTP 請求的話,每次都要進過三次握手和四次揮手,就會非常的耗時。
為了解決這個問題,HTTP引入了?keep-alive?機制。所謂的keep-alive就是客戶端和服務器三次握手之后,可以發送多個請求,最后再進行四次揮手。
這樣我們就可以節省很多次握手和揮手的步驟,提升了服務器的性能。
HTTP 頭部中的?connection?就是完成這個功能的,當我們設置?Connection: keep-alive?的時候,就會保持該鏈接,直到某個請求的?Connection: close?為止。這樣可以在很大程度上提高 HTTP 的性能。
3.10 請求首部字段
請求首部字段是發送 HTTP 請求時使用的首部字段,用于補充請求的額外信息,便于服務器理解請求的內容。請求首部字段有很多內容,我們學習幾個常用的字段:
?
字段名 | 作用 |
---|---|
Host | 請求資源所在的服務器 |
If-Match | 標志位,用于判斷資源是否符合要求 |
If-Modified-Since | 當資源比該字段內容更新時,發送資源 |
If-Unmodified-Since | 和If-Modified-Since作用相反 |
Referer | 當前請求來源方的地址 |
User-Agent | 客戶端的類型,比如Chrome瀏覽器,IE瀏覽器,?CURL程序等 |
Cookie | 發送請求時給服務端的一些信息 |
?
3.10.1 Host字段
這個字段說明了當前請求的服務器的域名。比如我們訪問慕課網首頁的時候,我們可以看到瀏覽器發送的請求中,Host字段就被設置為了?www.imooc.com。
Host:?www.imooc.com
3.10.2 If系列
我們這里列出來了三種?If?系列首部字段。HTTP 協議還有其它幾種?If?字段,大體功能都類似。從名字中我們可以看出來,這些首部都帶有判斷性質,如果滿足了某種條件才會做什么。這三個選項都是為了讓 HTTP 更高效,節省網絡帶寬。
當服務器發送一個響應的時候,可能會帶有一個?ETag?標志,客戶端在第二次獲取該資源的時候,可以帶上?If-Match?字段,它的值就是服務器返回的?ETag,當服務器發現對應的資源發生改變的時候,會將新資源返回,并生成新的?ETag?返回給客戶端。如果資源未發生改變,那么服務端會返回?304 Not Modified,這樣就節省了帶寬。
If-Modified-Since和?If-Unmodified-Since?也是類似的作用,只不過它們比較的是返回內容的時間戳。
3.10.3 Referer字段
這個字段是為了說明當前是從哪里來的。比如我們從慕課網首頁跳轉到免費課程的時候,就可以發現請求的?Referer?如下:
Referer:?https://www.imooc.com/
說明我們是從慕課網的首頁跳轉而來的,這個字段對于圖片防盜鏈非常有效。
3.10.4 Cookie
Cookie 相關的問題會經常在面試中被問到,那么什么是?Cookie?呢?在說明這個東西之前,我們先來了解一個概念:HTTP 協議是一個無狀態的協議,啥叫無狀態協議呢?來看下圖:
傳說,魚只有七秒的記憶,所以它可以每天非常歡樂的在水里游啊游,游啊游~
如果把魚比作 HTTP 協議的話,那么它應該是世界上最快樂的協議了,因為 HTTP 連一點記憶都沒有~~ 。HTTP 的無狀態指的是它不會保存任何狀態信息,每個請求都是獨立的,和其它請求沒有任何關系。
比如,我們在第一個頁面登陸了慕課網,當我們打開第二個慕課網頁面的時候,HTTP并不會記住我們已經登錄了慕課網。
為了解決這個問題,HTTP 引入了?cookie?和?session?機制。每次發出 HTTP 請求的時候,都會把?cookie?帶上,那么第二次請求通過狀態信息就可以知道當前用戶是誰了。
但是由于cookie是保存在客戶端的信息,所以很容易被篡改,因此 HTTP 引入了?session?機制。session?是保存在服務端的信息,起到的作用和?cookie?類似,都是用于保存一些狀態信息。
到這里之后,我們再把?Cookie?的概念告訴你,是不是很容易理解了呢?
An HTTP cookie (web cookie, browser cookie) is a small piece of data that a server sends to the user’s web browser. The browser may store it and send it back with the next request to the same server. Typically, it’s used to tell if two requests came from the same browser — keeping a user logged-in, for example. It remembers stateful information for the stateless HTTP protocol.
—以上是來自 MDN 對于 Cookie 的解釋
在這里我用我那撇腳的英語水平給大家翻譯一下:
Cookie 是服務端發送給客戶端的一段數據。客戶端可以保存該數據,并在后續請求中帶上該數據。通常來說,Cookie 用于用戶登錄等操作。Cookie 使得無狀態的 HTTP 變得有狀態了。
3.10.5 請求體
這里面就是我們真正要做的事情了,這里面的內容就是每個請求自定義的~~。比如我們在注冊的時候,通常在點擊注冊按鈕時,會發送一個POST請求,請求體里面就包含了我們填寫的姓名、密碼、郵箱等個人信息。
3.11 HTTP 響應報文
可以看到,響應報文同樣分為三部分:
狀態行
響應頭
相應內容 ?
3.11.2 狀態行
狀態行分為三部分,分別是HTTP版本,狀態碼以及原因短語。
3.11.3 HTTP版本
當前使用的 HTTP 版本號,比如?HTTP/1.1
3.11.4 狀態碼和原因短語
狀態碼是一個數字,是為計算機設計的,而原因短語是當前狀態碼的一種可讀文本,是為人設計的。HTTP協議中的狀態碼分為五大部分,大概60多種,下面我們列舉幾個會經常用到的:
200 : 這個是我們經常遇到的,表示當前的請求成功了;
301 : 表示當前請求的資源已經被永久的移動了了另外一個位置,響應的 Location 頭部應該包含資源的新地址,客戶端應該去另一個地址獲取這個資源;
302 :表示當前請求的資源被臨時的移動到了另外一個位置,響應的 Location 頭部應該包含資源的新地址;
304 :如果請求頭里面包含類似 If-Modified-Since 這樣的選項,若服務器發現當前請求的資源不符合 If-Modified-Since 要求,那么就會返回 304 ,表示當前的資源沒有發生改變,不用重新請求;
404 :這個應該是大家最熟悉的了,表示當前的資源不存在;
413 :當 POST 的數據太大的時候, Nginx 就會返回這個狀態碼,響應的原因短語是 Request Entity Too Large ;
500 :這個錯誤表示當前的服務器發生了錯誤,比如我們的代碼有 bug 等。 ?
3.11.5 響應頭
我們在請求頭部分說了通用首部字段和請求首部字段,下面我們看一下剩余的兩種首部字段。
3.11.6 響應首部字段
響應首部字段是服務器向客戶端返回的報文中使用的字段,該字段也有很多,我們講幾個比較常用的。
?
字段名 | 作用 |
---|---|
Accept-Ranges | 服務器用來表示自身支持的范圍請求 |
Location | 配合301和302狀態碼使用,表示資源位置發生了改變 |
Etag | 資源標識 |
?
2.3.11.6.1 Accept-Ranges
該字段表示服務器自身是否支持范圍請求,它的值用于表示范圍請求的單位。
格式:
1)?Accept-Ranges:?bytes
2)?Accept-Ranges:?none
none 不支持任何范圍請求單位,由于其等同于沒有返回此頭部,因此很少使用。不過一些瀏覽器,比如IE9,會依據該頭部去禁用或者移除下載管理器的暫停按鈕。 ? bytes 范圍請求的單位是 bytes (字節)。
3.11.6.2 Etag
服務器對返回的內容會計算一個數值,比如返回文件的MD5,這個值標識了當前返回的內容。客戶端根據這個值配合If-Match請求頭使用,可以有效的降低網絡帶寬。
3.11.7 實體首部字段
實體首部字段可以用于請求報文,也可以用于響應報文,用于表示實體的一些相關特性。
?
字段名 | 作用 |
---|---|
Content-Length | 表示實體的大小,單位是字節 |
Content-Range | 用于范圍請求 |
Content-Type | 實體的類型 |
Last-Modified | 最后修改時間 |
?
3.11.7.1 Content-Type
表示實體的類型,這個類型非常非常的多
但是我們常用的也就那么幾個
application/x-www-form-urlencoded 默認的 GET 和 POST 編碼方式,所有的數據都會變成鍵值對的形式,如 key1=value1&key2=value2 。
multipart/form-data 如果我們上傳資源的時候,那么必須使用這種格式。 ?
3.11.7.2 Content-Length
這個字段的值代表的是實體的長度,以字節表示。
3.11.7.3 Content-Range
這個字段是配合?Range?請求使用的,適用于斷點續傳,下載等功能。比如在下載的時候,可以使用多個進程,分別下載文件的一部分,到最后合并成一個文件,加快下載速度。
這個實體是非常費解的,英文叫做Entity,我的個人理解它就是請求或返回的body數據
3.11.8 響應body
這部分就是響應的主體部分哈~~
編輯:黃飛
?
評論
查看更多