??作者:一只大喵咪1201
??專欄:《網(wǎng)絡(luò)》
??格言:你只管努力,剩下的交給時間!
??認識HTTP協(xié)議
上篇文章中,本喵帶著大家對HTTP有了一個初步的認識,今天就來詳細講解一下這個應(yīng)用層協(xié)議。
?urlencode和urldecode
如上圖所示的url
(網(wǎng)址),里面包含有/
以及?
等字符。
- 像這樣的字符,已經(jīng)被url當做特殊意義理解了,因此這些字符不能隨意出現(xiàn)。
如果url
中要包含這些特殊意義的字符,就需要對其做轉(zhuǎn)義處理,就類似C語言中的轉(zhuǎn)義字符一樣。但是這里是網(wǎng)絡(luò),轉(zhuǎn)義的規(guī)則不和C語言一樣,它有自己的規(guī)則:取出字符的ASCII碼:
- 轉(zhuǎn)成16進制,然后前面加上百分號即可。
比如,+
號被轉(zhuǎn)義后成為%2B
,這個過程就叫做encode,而將%2B
轉(zhuǎn)回到+
號就叫做decode。
這個過程并不需要我們自己去做,有需要進行編碼和解碼的需求在網(wǎng)上直接查就可以,URL編碼/解碼。
如上圖,將字符C++
進行urlencode后的結(jié)果是C%2B%2B
,同樣也可以進行urldecode進行解碼。
上圖是本喵在百度上搜索C++后跳轉(zhuǎn)到的網(wǎng)頁的URL
(網(wǎng)址),可以看到紅色框中的wd=C%2B%2B
,其中wd
表示關(guān)鍵字,=
號后面的內(nèi)容就是encode
后的結(jié)果。
當服務(wù)器收到url
請求后,會自行對特殊字符%xx
進行decode
。包括漢字也需要進行encode和decode。
?HTTP協(xié)議宏觀格式
- HTTP是基于請求和響應(yīng)的應(yīng)用層協(xié)議,使用的是
TCP
套接字,客戶端向服務(wù)端發(fā)送request
請求,服務(wù)端收到請求后會作response
響應(yīng)返回給客戶端。
如上圖所示,是HTTP協(xié)議的宏觀格式,包括客戶端的request
和服務(wù)端的response
。
- 客戶端:
請求行: 如上圖紅色框中所示,包含GET
方法,url
,還有http協(xié)議的版本,如http/1.0
或者http/1. 1
,這三部分構(gòu)成請求行,必須使用\r\n
來結(jié)束這一行。
請求報頭: 如上圖綠色框中所示,包含的都是請求屬性,采用的是鍵值對的形式,name
表示屬性的名稱,如HOST
等,value
是屬性的內(nèi)容,如具體的一個網(wǎng)址。每一個屬性必須以\r\n
來結(jié)束。
空行: 如上圖紫色框中所示,這一行什么內(nèi)容都沒有,只有\r\n
用來表示空行。
請求正文: 如上圖最下面的黑色框中所示,包含請求的正文,如username
用戶名,passwd
密碼等,同樣每個正文后面必須以\r\n
來結(jié)束。
- 正文可以省略不寫,但是其他三部分必須有,以空行為界,空行后面的是請求正文,空行前面的是請求行和請求報頭。
- 每一部分都是以字符串形式放在報文中,如
GET url http/1.0\r\nname: value\r\n\r\n
,這一個報文中,包含請求行,一行屬性以及空行。
請求行和請求報頭兩部分可以看成我們自定義協(xié)議中的報頭,請求正文是有效載荷。
- 服務(wù)端:
狀態(tài)行: 如上圖紅色框中所示,包含http/1.1
HTTP協(xié)議版本,狀態(tài)碼以及狀態(tài)碼描述,最后是\r\n
。
響應(yīng)報頭: 如上圖綠色框中所示,包含響應(yīng)屬性,和request
的請求報頭類似。
空行: 如上圖紫色框中所示,也是只有一個\r\n
。
響應(yīng)正文: 如上圖黑色框中所示,包含服務(wù)器要給客戶端返回的數(shù)據(jù)內(nèi)容,如html/css/js
以及圖片,視頻,音頻等網(wǎng)絡(luò)資源。
- 狀態(tài)行和響應(yīng)報頭同樣類似我們自定義協(xié)議中的報頭,響應(yīng)正文是有效載荷。
-
服務(wù)端的
response
和客戶端的request
格式相同,只是每一塊的內(nèi)容有所差異。 - 每一部分中具體名詞的含義后面本喵會講解。
按照上面HTTP協(xié)議的宏觀格式,存在幾個細節(jié)問題:
- 應(yīng)用層怎么保證完整的讀取了一個請求或者響應(yīng)?
首先肯定可以完整的讀完一行,因為每一行都是以\r\n
結(jié)尾的,所以使用while(完整讀取一行)
的循環(huán)可以讀完請求行+請求報頭
,直到空行再停止。
按照我們自定義協(xié)議中的邏輯,此時報頭(請求行+請求報頭)完全讀完了,還剩下有效載荷(請求正文)。同樣地,在請求報頭中有一個屬性`Content-Length:XXX正文長度``,根據(jù)這個正文長度就可以將所有的請求正文讀取完畢。
此時一個完整的請求request
就讀取到了,同樣的方式也可以讀取到一個完整的響應(yīng)response
。
- 請求和響應(yīng)是怎么做到序列化和反序列化的?
序列化和反序列化是由HTTP自己實現(xiàn)的,因為協(xié)議中的內(nèi)容都是字符串,第一行(請求行/狀態(tài)行) + 請求/響應(yīng)報頭,只要按照\r\n
為判斷條件,一直循環(huán)下去,就可以拼接(序列化)或者拆分(反序列化)。
正文不用進行序列化和反序列化,因為它本身就是字符串,根據(jù)Content-Length:XXX正文長度
讀取相應(yīng)字節(jié)數(shù)就可以。
??驗證HTTP協(xié)議格式
上面都是本喵在說HTTP是這樣的格式,那么到底是不是呢?下面寫代碼來看一下。
服務(wù)器底層連接代碼仍然用之前TCP
套接字寫好的:
其他代碼本喵就不截圖了,在本喵文章TCP網(wǎng)絡(luò)通信有詳細講解,這里就直接拿來用了,如上圖所示,handlerEnter
是服務(wù)器處理任務(wù)的入口。
如上圖所示,不一樣的地方還有在增加了一個回調(diào)函數(shù),包裝器類型:using func_t= function<bool(const request& req, response& resp)>
。除此之外,類名也變成了httpServe
。
重點在于下面的代碼,服務(wù)器是處理任務(wù)的:
先在protocol.hpp
中定義請求request
和響應(yīng)response
,如上圖所示,此時請求和響應(yīng)中只有一個string
類型的成員變量。
當服務(wù)器在執(zhí)行handlerEnter
的時候,首先從網(wǎng)絡(luò)中讀取數(shù)據(jù),這里假設(shè)recv
一次就能完整讀取一個請求(后面再進行優(yōu)化),然后將讀取到的報文完整賦值到請求req
的字符串inbuffer
中,再通過_func
回調(diào)函數(shù)根據(jù)請求req
構(gòu)建響應(yīng)resp
,再將響應(yīng)發(fā)送到網(wǎng)絡(luò)中。
如上圖所示的DealReq
就是服務(wù)器調(diào)用的回調(diào)函數(shù),用來處理請求并構(gòu)建響應(yīng)的,這里本喵暫時僅打印請求req
的inbuffer
中的內(nèi)容。
- 該函數(shù)在構(gòu)造服務(wù)器
httpServe
對象的時候就被作為參數(shù)傳給了構(gòu)造函數(shù),服務(wù)器中的回調(diào)函數(shù)掉的就是該函數(shù)。
由于從網(wǎng)絡(luò)中讀取到的數(shù)據(jù),也就是客戶端的請求request
并沒有經(jīng)過反序列化,也沒有去除過報頭,所以此時req
的inbuffer
中的內(nèi)容就是一個完整的報文(字符串),包括報頭和有效載荷。
先將我們的服務(wù)端程序運行起來,如上圖序號1所示,然后在windows
端打開瀏覽器,在輸入網(wǎng)址的地方輸入http://服務(wù)端公網(wǎng)IP+端口號
,如上圖序號2所示,然后訪問這個url
。
此時windows
的瀏覽器就是客戶端,當訪問這個url
的時候,客戶端就向服務(wù)端發(fā)起了請求,使用的協(xié)議是http
協(xié)議。在服務(wù)端將收到的請求完整打印出來,結(jié)果如上圖所示。
-
打印出來的
http
請求的格式符合本喵前面介紹的宏觀格式,并且將每一塊中的具體信息都打印了出來。
下面本喵就來介紹一下,http
請求中每一行的具體內(nèi)容是什么。
?請求的格式
請求行:
請求行中的內(nèi)容是GET / HTTP/1.1
。
-
GET
:表示請求方法,后面詳細講解。 -
/
:表示url
,也就是要訪問服務(wù)器上文件所在的具體路徑。
這里本喵在windows
端輸入網(wǎng)址的時候,并沒有指定路徑,所以在客戶端(瀏覽器)的http
協(xié)議會將請求路徑默認構(gòu)建成\
表示根目錄。
在windows
瀏覽器中輸入網(wǎng)址的時候,加上請求地址/a/b/c
,如上圖紅色框中所示,在客戶端發(fā)起請求時,服務(wù)端收到的http
請求的請求行中也會有相應(yīng)的請求地址。
請求地址中的第一個/
就表示根目錄,和默認請求路徑中的根目錄是一個路徑,但是這個根目錄并不是Linux服務(wù)器上的根目錄,而是web
根目錄。
如上圖紅色框所示,存在一個目錄wwwroot
,這個目錄就是web
根目錄,它位于當前服務(wù)端進程所在的當前目錄,所以說,/
根目錄可以是任何一個目錄,因為wwwroot
可以創(chuàng)建在任何地方。
在windows
客戶端發(fā)起請求的時候,輸入網(wǎng)址中的路徑/a/b/c
訪問的就是上圖紅色框中所示的c.html
文件。
-
HTTP/1.1
表示http
協(xié)議的版本號是1.1
,常用的版本號還有1.0
。
之所以在請求行中要包含http
協(xié)議的版本號,是為了服務(wù)器能夠合理的給客戶端提供對應(yīng)的服務(wù)。由于客戶端的更新情況不一致,有的是1.0
版本的,有的是1.1
版本的,此時服務(wù)端根據(jù)請求行中的版本號就能夠給不同版本的客戶端提供不同的服務(wù)。
請求報頭:
請求報頭中包含許多請求屬性,每一條屬性為一行,使用\r\n
結(jié)尾。
-
Host:43.143.106.44:8080
,表示客戶端所請求服務(wù)端是套接字(IP地址 + 端口號)。 -
Connection: keep-alive
,表示長連接,后面詳細講解。 -
Upgrade-Insecure-Requests: 1
,表示瀏覽器(客戶端)支持自動將HTTP請求升級到HTTPS請求。在學習了https
協(xié)議后才能感受到這一屬性的作用。 -
User-Agent: 相關(guān)信息
: 用于表示客戶端的相關(guān)信息。
如上圖所示,User-Agent
后面的內(nèi)容包括客戶端的操作系統(tǒng)和使用的瀏覽器等信息。
-
Accept: 相關(guān)信息
:表示該請求要請求的資源類型。
由于本喵使用的是windows
端的瀏覽器向服務(wù)端發(fā)起的請求,并且沒有設(shè)置Accept
屬性,也就是沒有指定要請求的資源類型,所以瀏覽器默認將http
能請求的所有資源類型都加在了Accept
屬性中。
-
Accept-Encoding: gzip, deflate
:表示客戶端支持兩種encode
格式。
服務(wù)器可以根據(jù)客戶端的支持情況采用不同的壓縮算法進行內(nèi)容壓縮。常見的壓縮算法有g(shù)zip和deflate。
-
Accept-Language: zh-CN,zh;q=0.9
:表示客戶端支持的語言格式。
請求報頭中的所有屬性都是采用name: val
的鍵值對形式,并且是一個字符串。
之后就是一個空行,只有一個\r\n
。
?響應(yīng)的格式
在處理請求構(gòu)建響應(yīng)的函數(shù)DealReq
中,先打印出請求的內(nèi)容,和之前一樣。之后開始構(gòu)建響應(yīng),按照HTTP
的宏觀格式構(gòu)建響應(yīng):
- 構(gòu)建狀態(tài)行,包括
HTTP
版本,狀態(tài)碼,狀態(tài)碼描述,最后以\r\n
結(jié)束。 - 構(gòu)建響應(yīng)報頭,也就是響應(yīng)屬性,這里本喵暫時只寫一個屬性
Content-Type: text/html\r\n
,暫時不用管它是什么意思。 - 構(gòu)建空行,只有
\r\n
但是這個空行必須有。 - 構(gòu)建響應(yīng)正文:本喵暫時不寫任何響應(yīng)正文。
構(gòu)建好了響應(yīng)response
之后就是將其序列化,也就是將這些不同類型的字符串拼接在一起,如上圖綠色框中所示,最后由服務(wù)器發(fā)送到網(wǎng)絡(luò)中。
如何看到服務(wù)器構(gòu)建的響應(yīng)呢?使用一個telnet
工具,如果你的Linux服務(wù)器上沒有的話,使用yum
下載一個即可。
- 使用
telnet
工具,通過本地環(huán)回的IP地址和端口號向服務(wù)器發(fā)起請求。 - 鍵盤上按
ctrl + 回車
,出現(xiàn)telnet>
,再按一下回車跳到下一行。 - 輸入
GET / HTTP/1.1
,手動輸入請求行,然后按回車。
此時就向服務(wù)端發(fā)起了請求,會收到服務(wù)端的響應(yīng),如上圖綠色框中所示,這部分內(nèi)容就是我們上面代碼中構(gòu)建的響應(yīng)內(nèi)容,可以看到有狀態(tài)行,響應(yīng)報頭,還有空行。
此時我們已經(jīng)驗證了http
的請求和響應(yīng)和本喵前面介紹的宏觀合適是相一致的。
??理解HTTP協(xié)議
上面本喵已經(jīng)通過代碼讓大家看到了HTTP的請求和響應(yīng),從而也驗證了HTTP宏觀格式,接下來就對HTTP協(xié)議做一個更深入的理解。
在請求中增加幾個成員變量,包括請求訪問method
,請求的url
,請求的http版本httpversion
,以及一個請求路徑。
再通過成員函數(shù)parse
對請求進行反序列化,首先使用getOneLine
讀取請求中的請求行,然后將請求行中的三個字段分離出來。
創(chuàng)建一個工具類Util
,將一些公用的工具類函數(shù)放在里面,如上圖所示,獲取請求一行內(nèi)容的函數(shù)getOneLine
就放在里面。
再在服務(wù)器的處理入口handlerEnter
中調(diào)用請求的成員函數(shù)parse
進行反序列化。
在處理請求,構(gòu)建響應(yīng)的函數(shù)DealReq
中,將請求行中的三個字段打印出來,由于通過parse
已經(jīng)反序列化了,所以直接打印req
中的字段成員變量即可。
在構(gòu)建響應(yīng)正文的時候,添加一段html
的代碼,如上圖所示,并且將代碼以字符串的形式拼接到響應(yīng)上。
使用telnet
工具向服務(wù)端發(fā)起請求,此時就會得到服務(wù)端的響應(yīng),如上圖所示,包括響應(yīng)正文(html
的代碼)。
在服務(wù)端就可以看到telnet
在發(fā)送請求是輸入的請求行中的三個字段,如上圖紅色框中所示。
-
telnet
發(fā)起請求后,得到的響應(yīng)正文中的html
代碼到底是什么意思呢?
用windows
上的瀏覽器來訪問服務(wù)器,可以得到如上圖所示的一個網(wǎng)頁,網(wǎng)頁中有一個字符串Hello HTTP
,而這個字符串在響應(yīng)正文的html
代碼中也出現(xiàn)過。
- 響應(yīng)正文中的
html
代碼就是一個網(wǎng)頁,是服務(wù)端響應(yīng)給客戶端的一個網(wǎng)頁。- Linux中使用
telnet
得到響應(yīng)正文并沒有被解釋,所以是完整的html
代碼,使用Windows的瀏覽器得到響應(yīng)正文會被瀏覽器做解釋,解釋后得到的就是一個網(wǎng)頁。
關(guān)于html
的知識,用到的去查一下即可,本喵這里就直接用了。
?服務(wù)器和網(wǎng)頁分離
上面代碼中,服務(wù)器構(gòu)建響應(yīng)正文時,直接將html
代碼放在了string resp_body
中,這種將前端代碼嵌入到后端代碼中的方式是不妥的,所以要將前端的html
代碼和后端分離開來。
如上圖所示,在wwwroot
根目錄下創(chuàng)建幾個文件,其中404.html
和index.html
直接位于根目錄中,a.html
和b.hmtl
位于wwwroot/test
目錄下。
這四個html
的文件內(nèi)容不同,表示的網(wǎng)頁也不同,作用也不同。
404.html:
如上圖代碼所示,當客戶端發(fā)起的請求中url
錯誤或者無效時,會將該文件中的內(nèi)容作為響應(yīng)正文返回給客戶端,告知客戶端訪問資源不存在,并且顯示404錯誤。
index.html:
如上圖代碼所示,當客戶端發(fā)起的請求中url
為\
時,例如http://127.0.0.1:8080/
,此時會將該文件中的內(nèi)容作為響應(yīng)正文給客戶端返回。
因為此時客戶端訪問的就是web
根目錄,也就是./wwwroot
目錄,所以當用戶訪問根目錄的時候,本喵將該目錄下的index.html
作為響應(yīng)返回。
a.htmlh和b.html:
如上圖代碼所示,當客戶端發(fā)起的請求中url
為/test/a.html
和/test/b.html
的時候,服務(wù)器就會將這兩個文件的沒人作為響應(yīng)正文返回給客戶端,客戶端就會得到兩個網(wǎng)頁。
上面4個文件中的代碼都是html
代碼,如果將文件內(nèi)容響應(yīng)給Windows的瀏覽器,就會以網(wǎng)頁的形式展現(xiàn),這點本喵在前面展示過。
雖然是網(wǎng)頁,但是歸根到底,仍然是存在于Linux服務(wù)器磁盤上的四個文件,只是后綴是.html
而已。
所以服務(wù)端要想將文件中的內(nèi)容作為響應(yīng)正文返回給客戶端,就需要將文件中的內(nèi)容讀出來,并且以字符串的形式拼接到響應(yīng)中。
在Util.hpp
中再增加一個工具函數(shù),如上圖所示的readFile
,該函數(shù)專門用來讀取文件中的內(nèi)容,并且以二進制的方式讀取到緩沖區(qū)buffer
中。
- 必須以二進制的方式,如果以文本的形式讀取圖片就會出現(xiàn)bug。
如上圖代碼所示,在DealReq
中構(gòu)建響應(yīng)時,使用readFile
工具函數(shù)讀取請求中用戶所指定路徑中html
文件中的內(nèi)容。
如果讀取失敗,說明沒有用戶指定的文件,則讀取404.html
文件中的內(nèi)容作為響應(yīng)正文返回給客戶端。
在調(diào)用readFile
的時候,會傳入req.path
客戶端指定的路徑,以及resp_body
,還有req.size
,那么path
和size
是怎么來的呢?
可以看到,這兩個參數(shù)都是req
的成員變量,所以要在httprequest
中處理:
獲取客戶端指定路徑:
首先將web
根目錄定義出來,如上圖代碼所示的default_root
就表示web
根目錄。
在parse
函數(shù)中,先給path
賦值為default_root
,path
的值為./wwwroot
。再拼接請求中的url
,例如請求是http://127.0.0.1:8080/test/a.html
,將/test/a.html
拼接到path
中以后就成為./wwwroot/test/a.html
。
如果請求中沒有寫url
也就是采用默認的/
,那么拼接以后path
的值就是./wwwroot/
表示訪問web
根目錄,此時讓其訪問home_page
首頁。
獲取html文件大?。?/strong>
Linux中存在一個系統(tǒng)調(diào)用stat
,就是用來獲取文件大小的,如上圖所示。
參數(shù):
- path:目標文件的路徑。
- buf:是一個struct stat類型的自定義變量,它的成員變量就有文件的大小(以字節(jié)為單位)。
- 返回值:調(diào)用成功返回0,調(diào)用失敗返回-1。
上圖所示就是struct stat
的定義,成員包含很多文件的屬性,如文件的inode
,硬鏈接數(shù)等等,而我們需要的就是st_size
文件大小。
所以在httpRequest
中通過系統(tǒng)調(diào)用stat
來獲取文件的大小,如上圖代碼中紫色框所示。
- 如果文件不存在,就獲取
404.html
文件的大小。
此時我們前端的網(wǎng)頁以及后端的服務(wù)器都寫好了,接下來就可以運行了。
在Windows上的瀏覽器訪問根目錄,就會出現(xiàn)上圖大喵咪網(wǎng)址首頁
,而在服務(wù)端可以看到這次請求,其中url
是/
,path./wwwroot/index.html
。
用瀏覽器訪問根目錄下的test/a.html
,如上圖所示,會得到網(wǎng)頁我是a網(wǎng)頁
。同樣可以在服務(wù)端看到url
是/test/a.html
,path是./wwwroot/test/a.html
。
訪問b.html
和上面一樣,只是最后的文件名不一樣而已。
當訪問一個不存在的網(wǎng)絡(luò)資源時,就會得到404
錯誤的網(wǎng)頁,表示訪問資源不存在。
此時就做到了服務(wù)器和網(wǎng)頁的分離。
?請求方法
請求方法非常多,但是常用的就兩種,分別是GET
和POST
方法。
在Windows上的瀏覽器,百度的搜索框?qū)?yīng)的html
代碼如上圖所示。我們之所以能夠搜索東西,是因為這個搜索框本質(zhì)上是一個form
表單,如上圖紅色框所示。
我們搜索的關(guān)鍵字就填入這個表單中,然后再將表單的內(nèi)容一起通過HTTP
協(xié)議發(fā)送請求到服務(wù)端,服務(wù)端再做出相應(yīng)的響應(yīng)。
- 我們進行數(shù)據(jù)提交的時候,本質(zhì)是前端要通過
form
表單提交,瀏覽器會自動將form
表單中的內(nèi)容轉(zhuǎn)化成為GET/POST
方法請求。
GET方法:
在index.html
中增加form
表單,如上圖代碼所示,其中action
是客戶端提交信息后要服務(wù)端執(zhí)行的動作,本喵這里是讓服務(wù)端執(zhí)行/test/a
路徑下的python
程序。
method
是請求方法,這里本喵設(shè)置為GET
方法。
提交的form
表單中,第一行是姓名,第二行是密碼,第三行是登錄按鈕。
在瀏覽器訪問根目錄,也就是在訪問服務(wù)器的index.html
文件,如上圖所示。此時是客戶端第一次發(fā)起請求,所以服務(wù)端會將index.html
的內(nèi)容返回給客戶端,此時返回的網(wǎng)頁中包含form
表單。
如上圖所示,服務(wù)端收到的第一次請求是請求./wwwroot/index.html
路徑下的文件。
- 第一次默認采用
GET
方法。
填好姓名和密碼后登陸就會彈出如上圖所示的網(wǎng)頁,此時是客戶端向服務(wù)端發(fā)送的第二次請求。
- 提交后的
action
是讓服務(wù)器執(zhí)行/test/a/c.py
下的文件,由于不存在,所以返回資源不存在。 -
form
表單中的姓名和密碼以xname=damiaomi&ypwd=123456
的形式拼接在了url
中,如上圖網(wǎng)址中的綠色框。
如上圖是服務(wù)器在客戶端提交form
表單后收到的請求,可以看到,表單中的內(nèi)容被拼接到了url
中。
POST方法:
如上圖代碼所示,將index.html
中的method
改成POST
方法,其他內(nèi)容不變。進行前面那樣的操作:
第一次發(fā)起請求時同樣訪問服務(wù)端的根目錄,上圖所示的網(wǎng)頁是服務(wù)端給客戶端的第一次響應(yīng),也是包含一個form
表單。
從服務(wù)器中看到,此次請求的方法仍然是GET
方法,因為這是第一次請求,我們指定的POST
方法是在提交表單時使用的,也就是在第二次請求中使用的。
輸入姓名密碼后點擊登錄,輸入的內(nèi)容和前面一樣,同樣會出現(xiàn)資源不存在的網(wǎng)頁,原因也是action
的文件不存在。
-
此時
form
表單中的內(nèi)容沒有拼接到url
中,如上圖紅色框中所示。
如上圖所示是服務(wù)器收到的第二次請求內(nèi)容,可以看到,此時使用的是POST
請求方法,url
以及path
都是截止到action
指定的路徑處,如上圖紅色框中所示。
-
form
表單中的內(nèi)容以請求正文的形式提交給了服務(wù)器,如上圖橘色框中所示。
通過上面對比我們發(fā)現(xiàn),GET/POST
請求方法的區(qū)別在于:
-
GET
方法通過url
提交參數(shù)。 -
POST
方法通過HTTP請求正文提交參數(shù)。
站在客戶端的角度,如果使用GET
方法,在提交form
表單的時候,內(nèi)容會拼接到url
中,在瀏覽器的網(wǎng)址欄中可以看到,如果是賬號密碼的話其他人就能夠直接看到。
如果使用POST
方法,在提交form
表單的時候,內(nèi)容是通過請求正文提交的,在瀏覽器的網(wǎng)址欄中看不到。
所以,如果提交的數(shù)據(jù)是比較私密的,如賬號密碼等,就使用POST
方法,如果無所謂的數(shù)據(jù)就使用POST/GET
哪個都行。
- 本喵并不是說
POST
方法比GET
方法安全,僅僅是POST
方法無法直觀的看到提交的數(shù)據(jù)。POST/GET
兩種方法都是不安全的。
?常見報頭屬性
在使用POST
方法發(fā)起HTTP請求后,多了幾個GET
方法中沒有的屬性,如上圖所示。
Content-Type: xxx\r\n
表示連接類型,請求和響應(yīng)中都有。
上圖所示的是請求中的Content-Type
,由于會用表單提交數(shù)據(jù)所以它的值如上圖紅色框中所示。這是瀏覽器在告訴服務(wù)器,它提交的請求類型是form
,服務(wù)器需要按照表單的處理方式來處理。
上圖所示是響應(yīng)中的Content-Type
,本喵在處理請求構(gòu)建響應(yīng)時,響應(yīng)報頭中只有一個屬性Content-Type: text/html\r\n
。
由于服務(wù)器響應(yīng)給客戶端瀏覽器的是一個html
文件的內(nèi)容,查閱一下Content-Type
對照表:
如上圖紅色框中所示,html
對應(yīng)的Content-Type
的值必須是text/html
,這是在告訴瀏覽器,服務(wù)器給它的響應(yīng)是html
類型的數(shù)據(jù),瀏覽器需要按照html
的處理方式來解釋。
Content-Length: XXX\r\n
使用POST
方法發(fā)起請求時,服務(wù)器收到的請求中就有Content-Length
這一屬性,用來表示請求正文的長度,以字節(jié)為單位。
同樣的,在構(gòu)建響應(yīng)的時候,也可以將這屬性作為響應(yīng)報頭加進去,表示響應(yīng)正文的長度,瀏覽器可以自動識別這一屬性。
在DealReq
函數(shù)中構(gòu)建響應(yīng)的時候,添加Content-Length: xxx
屬性,如上圖紅色框中所示,該屬性的值就是服務(wù)器返回給客戶端的文件大小,存放在req
中。
然后將響應(yīng)正文長度屬性拼接到相應(yīng)報頭中,一起發(fā)出去。
此時使用telnet
工具向服務(wù)器發(fā)起請求時,得到響應(yīng)中包含Content-Length
,如上圖紅色框中所示。在使用瀏覽器發(fā)起請求時,瀏覽器能自動識別Content-Length
這一響應(yīng)屬性,并作相應(yīng)處理。
?Connection: keep-alive
此時index.html
中有內(nèi)容,有跳轉(zhuǎn)網(wǎng)頁,有圖片,還有表單等等內(nèi)容,如上圖所示。
瀏覽器訪問根目錄時,得到的網(wǎng)頁中有這么多內(nèi)容,如上圖所示。
在瀏覽器上本喵只輸入了一次網(wǎng)址,然后回車,但是服務(wù)器上卻收到了多個請求,如上圖所示(本喵僅截圖幾個)。不同請求的url
和path
不同。
- 一個網(wǎng)頁中有多種類型的資源,而一次請求只能獲取一種類型資源。
- 雖然我們在瀏覽器中只訪問一次,但是瀏覽器會通過多個線程發(fā)起多次請求。
- 多次請求的多個響應(yīng)共同組成了一個網(wǎng)頁。
我們知道HTTP協(xié)議使用的是TCP
網(wǎng)絡(luò)通信那一套,所以客戶端的每一個請求,服務(wù)器都會創(chuàng)建一個套接字。
像上面這種請求,僅訪問一個網(wǎng)頁就會創(chuàng)建多個套接字,會導致套接字資源緊張,所以就出現(xiàn)了長連接的解決方案。
長連接:
- 一個客戶端對應(yīng)一個套接字,客戶端的一個請求響應(yīng)完后,套接字不關(guān)閉,只有客戶端退出了,或者指定關(guān)閉時,套接字才關(guān)閉。
- 一個客戶端無論有多少個請求,都通過一個套接字和服務(wù)器進行網(wǎng)絡(luò)通信。
這里本喵僅能從概念上來給大家講解,無法做出具體的實驗現(xiàn)象。
?會話保持(Cookie和Session)
Cookie技術(shù):
假設(shè)訪問CSDN使用的是HTTP協(xié)議。
CSDN在第一次登錄后,之后打開CSDN就不用再進行登陸了。根據(jù)前面學習我們知道,登錄時輸入的信息其實就是form
表單,然后將數(shù)據(jù)提交給服務(wù)器,讓服務(wù)器進行鑒權(quán),如果權(quán)限符合就會返回對應(yīng)的響應(yīng)。
- HTTP實際上是一種無狀態(tài)協(xié)議,每次請求并不會記錄它曾經(jīng)請求了什么。
所以,在第一次登錄CSDN后,在站內(nèi)進行網(wǎng)頁跳轉(zhuǎn)(從一篇文章到另一篇文章)時,理論上需要再次輸入賬號密碼進行登錄,讓服務(wù)器進行鑒權(quán),因為HTTP的每次請求/響應(yīng)之間是沒有任何關(guān)系的。
- 但我們在使用瀏覽器訪問CSDN的時候發(fā)現(xiàn)并不是這樣的,只需要登錄一次即可。
這是因為瀏覽器在我們第一次登錄CSDN的時候,將我們的賬號密碼等登錄信息保存了下來。
當我們進行網(wǎng)頁跳轉(zhuǎn)或者再次打開CSDN的時候,瀏覽器自動將保存的用戶登錄信息添加到了請求報頭中,并通過HTTP協(xié)議發(fā)送給了服務(wù)器,服務(wù)器進行鑒權(quán)并返回對應(yīng)的響應(yīng)。
- 登錄還是需要的,只是瀏覽器幫我們做了這個事。
- 這種技術(shù)就叫做Cookie技術(shù)。
如上圖所示,點擊網(wǎng)址前面的小鎖,可以查看當前瀏覽器正在使用的Cookie
。
當我們將上圖中和CSDN有關(guān)的Cookie
數(shù)據(jù)刪除后就需重新輸入賬號密碼來登錄了。
- 用戶在第一次輸入賬號和密碼時,瀏覽器會進行保存(Cookie),近期再次訪問同一個網(wǎng)站(發(fā)送http請求),瀏覽器會自動將用戶信息添加到報頭中推送給服務(wù)器。
- 這樣只要用戶首次輸入密碼,一段時間內(nèi)將不用再做登錄操作了。
Cookie
又分為內(nèi)存級和文件級:
- 內(nèi)存級
Cookie
:將信息保存在瀏覽器的緩沖區(qū)中,當瀏覽器被關(guān)閉時,意味著進程結(jié)束,保存的信息也就沒有了,重新打開瀏覽器后還需要重新登錄。 - 文件級
Cookie
:將信息保存在文件中,文件是放在磁盤上的,無論瀏覽器怎么打開關(guān)閉,文件中的信息都不會刪除,在之后發(fā)送HTTP請求時,瀏覽器從該文件中讀取信息并加到請求報頭中。
根據(jù)日常使用瀏覽器的情況,我們可以知道,大部分情況下的Cookie
都是文件級別的,因為關(guān)閉了瀏覽器下次打開不用再重新登錄。
Cookie
文件也是存在我們電腦上的,具體路徑有興趣的小伙伴可以去找一下。
Session:
如果我們電腦上的Cookie
文件被不法份子盜取,那么它就能以我們的身份去登錄我們的CSDN,并且進行一些非法操作。
- 為了保證信息安全,新的做法是將用戶的賬號密碼信息以及瀏覽痕跡等信息保存在服務(wù)器上。
- 每個用戶對應(yīng)一個文件,這個文件被叫做
Session
文件,由于存在很多的Session
文件,所以給每個文件一個名字,叫做Session id
。- 服務(wù)器將
Session id
作為響應(yīng)返回給用戶,此時用戶的Cookie
中保存的就是這個id
值。
如上圖所示,當?shù)谝淮蔚卿洉r,瀏覽器的form
表單中的用戶信息提交到了服務(wù)器,服務(wù)器創(chuàng)建Session
文件并保存用戶信息,然后再生成一個id
返回給瀏覽器。
此時瀏覽器的Cookie
保存的就是這個id
值。當進行站內(nèi)頁面跳轉(zhuǎn)或者再次打開CSDN的時候,瀏覽器自動將Cookie
中Session id
加到請求報頭中提交給服務(wù)端。
服務(wù)端收到請求后,拿到請求報頭中的Session id
找到對應(yīng)的Session
文件進行鑒權(quán),看看身份是否符合。
鑒權(quán)完畢后做出相應(yīng)的響應(yīng)返回給瀏覽器。
- 服務(wù)端存儲用戶信息的技術(shù)就叫做
Session
技術(shù)。
為什么新的會話保持(Cookie和Session
)技術(shù)能夠提高用戶信息的安全性呢?
- 服務(wù)端是由專業(yè)的人員維護的,服務(wù)器中存在病毒以及流氓軟件的可能想更小,所以用戶信息在服務(wù)端會更安全。
- 如果客戶端的
Cookie
中的Session id
被盜用,不法分子使用該id
向服務(wù)端發(fā)起請求時,會因為常用IP地址不一樣而被服務(wù)端強制下線,此時只有手里真正有賬號密碼的人才能夠再次登錄。
保證Session
安全的策略非常多,有興趣的小伙伴可以自行了解。
寫入Cookie
信息:
我們知道,瀏覽器的Cookie
信息是服務(wù)端響應(yīng)返回的,所以在我們構(gòu)建響應(yīng)的時候也可以構(gòu)建Cookie
信息讓瀏覽器去保存。
在DealReq
函數(shù)中構(gòu)建響應(yīng)時,設(shè)置Cookie
信息,內(nèi)容是name=123456abc
,有效時間是三分鐘,然后加到響應(yīng)報頭中返回給客戶端,如上圖所示。
使用瀏覽器訪問根目錄的時候,如上圖所示,會得index.html
文件表示的網(wǎng)頁,查看該網(wǎng)頁的Cookie
信息,可以看到name
是123456abc
,有效時間是3分鐘,和我們在服務(wù)端構(gòu)建響應(yīng)時寫的內(nèi)容一模一樣。
-
瀏覽器將我們在響應(yīng)中設(shè)置的
Cookie
內(nèi)容當作了Session id
。
真正生成Session id
是有一套復雜的算法的,它能夠保證每一個Session
文件的id
都是獨一無二的。
Cookie
和Session
兩種技術(shù)共同組成了HTTP的會話保持。
?HTTP狀態(tài)碼
本喵在DealReq
中構(gòu)建響應(yīng)的時候,狀態(tài)行中的狀態(tài)碼直接寫的200
,狀態(tài)碼描述是OK
。那么狀態(tài)碼到底有哪些呢?它們代表的意義是什么?
狀態(tài)碼有五種類型,分別以1~5開頭:
狀態(tài)碼 | 類別 | 原因短語 |
---|---|---|
1XX | informa(信息性狀態(tài)碼) | 接收的請求正在處理 |
2XX | Success(成功狀態(tài)碼) | 請求正常且處理完畢 |
3XX | Redirection(重定向狀態(tài)碼) | 需要進行附加操作以完成請求 |
4XX | Client Error(客戶端錯誤狀態(tài)碼) | 服務(wù)器無法處理請求 |
5XX | Server Error(服務(wù)器錯誤狀態(tài)碼) | 服務(wù)器處理請求出錯 |
最常見的一些狀態(tài)碼,如200(OK),404(Not Found),403(Forbidden請求權(quán)限不夠),302(Redirect),504(Bad Gateway)。
重定向狀態(tài)碼(3XX):
這些狀態(tài)碼沒啥好說的,重點說一下重定向。
- 重定向就是將網(wǎng)絡(luò)請求重新定個方向轉(zhuǎn)到其它位置(跳轉(zhuǎn)網(wǎng)站),此時這個服務(wù)器相當于提供了一個引路的服務(wù)。
相信都有過這樣的經(jīng)歷,打開一個網(wǎng)址以后,自動就彈出一些廣告網(wǎng)頁,這就是一種重定向。
-
瀏覽器發(fā)送請求給服務(wù)端,服務(wù)端返回一個新的
url
,并且狀態(tài)碼是3XX
,瀏覽器會自動用這個新的url
向新地址的服務(wù)端發(fā)起請求。
所以說,重定向是由客戶端完成的,當客戶端瀏覽器收到的響應(yīng)中狀態(tài)碼是3XX
后,它就會自動從響應(yīng)中尋找返回的新的url
并發(fā)起請求。
重定向又有兩種:
- 永久重定向:狀態(tài)碼為
301
。 - 臨時重定向:狀態(tài)碼為
302
和307
。
臨時重定向和永久重定向本質(zhì)是影響客戶端的標簽,決定客戶端是否需要更新目標地址。
如果某個網(wǎng)站是永久重定向,那么第一次訪問該網(wǎng)站時由瀏覽器幫你進行重定向,但后續(xù)再訪問該網(wǎng)站時就不需要瀏覽器再進行重定向了,此時直接訪問的就是重定向后的網(wǎng)站。
而如果某個網(wǎng)站是臨時重定向,那么每次訪問該網(wǎng)站時都需要瀏覽器來幫我們完成重定向跳轉(zhuǎn)到目標網(wǎng)站。
如上圖代碼所示,在DealReq
函數(shù)中構(gòu)建響應(yīng)時,狀態(tài)行中的狀態(tài)碼設(shè)為307
,狀態(tài)碼描述為Temporary Redirect
,表示臨時重定向。
設(shè)置屬性Location: XXX
,其值是重定向后的地址,這里是本喵CSDN的首頁地址。
然后將屬性Location
拼接到響應(yīng)報頭中,最后再發(fā)送給客戶端。
在瀏覽器中訪問index.html
,但是發(fā)起請求后,返回的并不是index.html
中的內(nèi)容,而且屬性Location
的值所指向的網(wǎng)頁,也就是本喵CSDN的首頁。
當瀏覽器發(fā)起請求訪問index.html
的時候,收到的響應(yīng)中,狀態(tài)碼是307
,所以瀏覽器不解釋index.html
,而是根據(jù)響應(yīng)報頭中的Location
屬性,得到新的url
,再向新的url
發(fā)起請求,得到新的響應(yīng),也就是本喵的CSDN首頁。文章來源:http://www.zghlxwxcb.cn/news/detail-618657.html
- 永久重定向無法演示出來,效果和臨時重定向一樣。
??總結(jié)
這篇文章的思路是,先宏觀介紹HTTP協(xié)議的格式,然后通過代碼去驗證這個格式,然后再把驗證結(jié)果中的具體屬性進行講解。一些重點屬性會單獨舉例進行講解。文章來源地址http://www.zghlxwxcb.cn/news/detail-618657.html
到了這里,關(guān)于【網(wǎng)絡(luò)】應(yīng)用層——HTTP協(xié)議的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!