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