HTTP網(wǎng)絡(luò)協(xié)議必知必會(huì)大盤(pán)點(diǎn)
HTTP協(xié)議作為網(wǎng)絡(luò)傳輸?shù)幕緟f(xié)議,有著廣泛的應(yīng)用。HTTP協(xié)議的完整內(nèi)容很多,但是其核心知識(shí)卻又簡(jiǎn)單精煉。學(xué)習(xí)者應(yīng)該掌握其基本結(jié)構(gòu),并且能夠舉一反三。這篇文章所列的,就是在實(shí)際開(kāi)發(fā)中必須知道必須掌握的HTTP知識(shí)。
HTTP協(xié)議
HTTP協(xié)議:消息的分類
HTTP消息(有的文章稱之為報(bào)文)分為請(qǐng)求消息和響應(yīng)消息兩種基本分類。其中請(qǐng)求消息是客戶端發(fā)送給服務(wù)器的用于請(qǐng)求服務(wù)和資源的消息,響應(yīng)消息是服務(wù)器對(duì)請(qǐng)求消息的應(yīng)答。一般來(lái)說(shuō),一個(gè)響應(yīng)對(duì)應(yīng)一個(gè)請(qǐng)求,不多也不少。
HTTP協(xié)議:特點(diǎn)
HTTP協(xié)議被人總結(jié)為無(wú)連接、無(wú)狀態(tài)的特點(diǎn):
無(wú)連接:無(wú)連接的含義是限制每次連接只處理一個(gè)請(qǐng)求。服務(wù)器處理完客戶的請(qǐng)求,并收到客戶的應(yīng)答后,即斷開(kāi)連接。采用這種方式可以節(jié)省傳輸時(shí)間。
無(wú)狀態(tài):HTTP協(xié)議是無(wú)狀態(tài)協(xié)議。無(wú)狀態(tài)是指協(xié)議對(duì)于事務(wù)處理沒(méi)有記憶能力。缺少狀態(tài)意味著如果后續(xù)處理需要前面的信息,則它必須重傳,這樣可能導(dǎo)致每次連接傳送的數(shù)據(jù)量增大。另一方面,在服務(wù)器不需要先前信息時(shí)它的應(yīng)答就較快。
HTTP協(xié)議:消息的基本格式
HTTP協(xié)議的請(qǐng)求消息和響應(yīng)消息的格式及其相似。提煉出它們的共性,可以指出,HTTP消息分為三個(gè)部分:
首行
頭部(Header)
正文(Body)
其中,頭部用來(lái)指出HTTP消息的一些屬性,它們有固定的格式;正文部分是傳輸?shù)膶?shí)際內(nèi)容,它們的格式是任意的,通常用Content-Type頭來(lái)指定。首行在請(qǐng)求消息和響應(yīng)消息中具體格式略有區(qū)別,它們表示的按理說(shuō)應(yīng)該是HTTP消息最基本的部分。不論是HTTP請(qǐng)求還是HTTP響應(yīng),首行都是有的,否則會(huì)出現(xiàn)不可饒恕的解析錯(cuò)誤;然而頭部和正文是可選的,不過(guò)實(shí)際過(guò)程中,多多少少都要包含一些基本的頭。
HTTP消息主要是基于ASCII編碼的消息實(shí)體。主要的意思是指首行和頭部都是以ASCII編碼,而正文部分的編碼就顯得任意了。在實(shí)際的開(kāi)發(fā)中,發(fā)送的文本消息時(shí)常會(huì)碰到亂碼的問(wèn)題。一種解決辦法是,對(duì)于文本消息,約定以UTF-8格式進(jìn)行編碼和解碼。
知道的人也許知道,HTTP消息是基于TCP協(xié)議的上層應(yīng)用協(xié)議。TCP協(xié)議是網(wǎng)絡(luò)流協(xié)議的一種。抽象地講,就是從一臺(tái)主機(jī)一個(gè)字節(jié)一個(gè)字節(jié)有序地傳輸?shù)搅硪慌_(tái)主機(jī)。對(duì)于HTTP協(xié)議來(lái)說(shuō),自然保持了這種有序性,即按照首行、頭部、正文的順序進(jìn)行傳輸。首行和頭部都是ASCII文本流,正文部分是字節(jié)流。一個(gè)特殊的控制結(jié)構(gòu)CRLF用來(lái)控制每個(gè)部分的結(jié)束。
CRLF是回車符和換行符的意思,它們是兩個(gè)特殊的ASCII字符。CR是回車符(\r),在ASCII中的編碼是13;LF是換行符(\n),在ASCII中的編碼是10.
下面通過(guò)一個(gè)例子來(lái)解釋CRLF在HTTP消息中的控制。
GET /simple.html HTTP/1.1----- 首行 Accept: text/html--| Accept-Language: zh-cn| Accept-Encoding: gzip, deflate|-- 頭部 User-Agent: Mozilla/4.0| Host: localhost:8080| Connection: Keep-Alive--| ----- 空白行表示頭部的結(jié)束 ----- 接下來(lái)的內(nèi)容是正文部分
這是一個(gè)簡(jiǎn)單的HTTP請(qǐng)求消息。我在其中做了一些必要的刪減,以便每個(gè)頭足夠短都能在一行中顯示。記住首行和頭部是ASCII流,正文部分是字節(jié)流,它們?cè)谙?shí)體中是連續(xù)的片段,并不像代碼中所示那樣有換行的結(jié)構(gòu)。換句話說(shuō),原始的消息應(yīng)該是如下形式:
GET /simple.html HTTP/1.1Accept: text/html Accept-Language: zh-cn Accept-Encoding: gzip, deflate User-Agent: Mozilla/4.0 Host: localhost:8080 Connection: Keep-Alive
回到之前有換行符的代碼例子中去。將每個(gè)CRLF單獨(dú)列為一行是便于觀察組織。可以清楚地看到,第一行是首行,以CRLF標(biāo)志其結(jié)束;接下來(lái)是頭部,含有多個(gè)消息頭,每行定義一個(gè)消息頭,以CRLF標(biāo)志其結(jié)束;一個(gè)單獨(dú)的CRLF(緊接著上一個(gè)CRLF)表示整個(gè)頭部的結(jié)束,接下來(lái)是正文部分。在這個(gè)示例中,正文部分為空。
另外,可以看到每個(gè)消息頭的格式都是一致的,即Key:Value的形式。其中Key表示消息頭的鍵,Value表示消息頭的值。
HTTP請(qǐng)求
接下來(lái)具體講講HTTP的請(qǐng)求消息。誠(chéng)心而論,光是寫(xiě)上面這么點(diǎn)內(nèi)容就花費(fèi)了我好久。每每想到寫(xiě)博客耗費(fèi)的精力和時(shí)間,都會(huì)影響到我寫(xiě)博客的動(dòng)力。
之前已經(jīng)說(shuō)過(guò),HTTP請(qǐng)求消息也分為三個(gè)部分:
請(qǐng)求行
請(qǐng)求頭部
請(qǐng)求正文
其中請(qǐng)求頭部的格式我們已經(jīng)見(jiàn)過(guò)。請(qǐng)求行的基本格式為:
方法 路徑 版本
例如下面的例子:
GET /simple.html HTTP/1.1
就有對(duì)應(yīng)關(guān)系:
方法:GET
路徑:/simple.html
版本:HTTP/1.1
請(qǐng)求行是HTTP請(qǐng)求消息的最基本要素。版本是用來(lái)聲明HTTP消息的解析規(guī)則,不同的版本在某些地方的表現(xiàn)是不同的,這里不作過(guò)多拆解了?,F(xiàn)在實(shí)際應(yīng)用中最新的HTTP協(xié)議版本就是HTTP/1.1。路徑可以理解成該請(qǐng)求消息發(fā)往服務(wù)器的入口,一般來(lái)講,同一個(gè)路徑應(yīng)該代表同一個(gè)資源實(shí)體。方法表示對(duì)該資源實(shí)體進(jìn)行的操作,例如上述的GET方法,其含義就是請(qǐng)求獲取該資源的內(nèi)容。這些都是通常的解釋,但不是必然的要求。實(shí)際上,服務(wù)器會(huì)解析到方法和路徑,根據(jù)方法和路徑做出自己相應(yīng)的響應(yīng)。這種響應(yīng)的規(guī)則,可以遵循某些規(guī)范,也可以完全不考慮這些規(guī)范,是任意的。市面上已經(jīng)存在一些約定俗成的規(guī)范了,比如Restful。Restful是非常優(yōu)秀的基于HTTP協(xié)議的WEB API設(shè)計(jì)理念,很值得講,但在這里就不講了。
HTTP請(qǐng)求:方法
首先列出最常用的HTTP方法:
GET
POST
PUT
PATCH
DELETE
HEAD
OPTIONS
之前說(shuō)過(guò),服務(wù)器對(duì)于方法的處理,是沒(méi)有強(qiáng)制的規(guī)范的。這句話說(shuō)得并不全對(duì)。其實(shí)每個(gè)HTTP方法,都是有一些HTTP協(xié)議要求的。比如說(shuō)GET方法請(qǐng)求的資源,瀏覽器端一般都會(huì)有緩存,下次請(qǐng)求的時(shí)候可能從緩存中去取就夠了,服務(wù)器不用再重復(fù)發(fā)送相同的資源了;但是服務(wù)器如果將獲取資源的接口的方法定義為POST,那么瀏覽器端就不會(huì)再對(duì)資源進(jìn)行緩存了,即使每次取到的都是同樣地內(nèi)容,都會(huì)請(qǐng)求服務(wù)器重新發(fā)送一遍。所以說(shuō),將請(qǐng)求資源的接口的方法定義為POST而不是GET,就是一種不合理的設(shè)計(jì)。
再比如,GET方法的請(qǐng)求消息是不能定義消息體的,HEAD方法的請(qǐng)求其響應(yīng)消息是不包含消息體的,這些都是HTTP協(xié)議對(duì)于HTTP方法的約束。
HTTP請(qǐng)求:路徑
方法和路徑的組合構(gòu)成WEB API的入口,路徑也是很關(guān)鍵的。路徑的基本格式一般是:
basic-path[?query-string]
其中[]中的內(nèi)容表示可選的。在上例中,basic-path就是/simple.html,但不包含query-string的內(nèi)容。basic-path形式很像UNIX中絕對(duì)路徑的樣式,要以/打頭。單獨(dú)的/表示一種路徑,/a、/a/b、/a/b/c都是合理的路徑表示。不推薦使用/a/、/a/b/、/a/b/c/這樣/后面不跟任何其他內(nèi)容的形式(/除外)。優(yōu)秀的API設(shè)計(jì)者會(huì)利用不同的路徑層級(jí)來(lái)合理地組織資源。
問(wèn)號(hào)后面的部分就是query-string。它的格式是任意的,只要客戶端和服務(wù)器約定好一定的形式即可。這個(gè)部分一般是請(qǐng)求參數(shù)的附加。之前說(shuō)過(guò),GET方法是不包含請(qǐng)求體的,所以GET方法的HTTP請(qǐng)求想要附加參數(shù)只能使用這種方式。當(dāng)然其他方法也是可以使用這種方式附加參數(shù),只要服務(wù)器同意就可以了。query-string的格式任意,但在客戶端和服務(wù)器之間也有預(yù)先定好的約定,即鍵值對(duì)的形式。query-string可以表示成一系列鍵值對(duì)的集合,用以下方式表示:
k1=v1&k2=v2&k3=&k4
#p#
在這里,&分隔不同的鍵值對(duì),=表示鍵和值得關(guān)系??梢钥吹揭还灿兴膫€(gè)鍵值對(duì)關(guān)系,它們是:
k1: v1
k2: v2
k3: 空字符串
k4: 起碼該鍵被定義了
一般來(lái)說(shuō),鍵值對(duì)要寫(xiě)成k=v的形式,但是k=和僅僅一個(gè)k都是允許的,前者表示鍵k的值是空字符串,后者表示鍵k被定義了,但是其值是什么并不關(guān)心。
從上面的例子中發(fā)現(xiàn),在query-string中&和=被用于特殊的用途了,我們不能再在其中從容地使用這兩個(gè)符號(hào)了。如果我們要在值中包含這兩個(gè)符號(hào),那咋辦呢?方法就是,編碼。
在實(shí)際的HTTP請(qǐng)求中,對(duì)于如下的鍵值關(guān)系
k1: & k2: =
具體的query-string要寫(xiě)成:
k1=%26&k2=%3D
這是因?yàn)樵贏SCII編碼中,&的16進(jìn)制表示是26,=的16進(jìn)制表示是3D。對(duì)于需要的編碼,就要表示成其實(shí)際編碼的16進(jìn)制表示,每個(gè)字節(jié)都用一個(gè)%XX三個(gè)字符進(jìn)行表示。這樣,%本身也就要進(jìn)行編碼了,它的編碼是%25。除了這些控制字符的編碼,還可以進(jìn)行中文等非英語(yǔ)語(yǔ)言的編碼。
HTTP請(qǐng)求頭
HTTP請(qǐng)求頭格式與之前所說(shuō)的消息頭格式?jīng)]什么兩樣,就是以冒號(hào)分隔的鍵值對(duì)。HTTP請(qǐng)求頭中,既包含預(yù)定義的頭(如Content-Type、Content-Length等),也支持自定義頭。原本打算多列出幾個(gè)常見(jiàn)的請(qǐng)求頭的,但限于精力,不打算這樣做了。我只說(shuō)說(shuō)我最常用的Content-Type頭吧。
Content-Type頭,既可用于請(qǐng)求消息,也可用于響應(yīng)消息,是規(guī)定請(qǐng)求正文內(nèi)容格式的頭部。例如利用這個(gè)頭部,我們可以規(guī)定正文的格式為純文本格式、表單格式、XML格式、JSON格式、圖像格式等。例如Content-Type: application/json就表示JSON文本格式。
HTTP響應(yīng)
HTTP響應(yīng)消息的基本格式也是一樣的,包含三個(gè)部分:
響應(yīng)行
響應(yīng)頭部
響應(yīng)正文
響應(yīng)頭部和響應(yīng)正文我覺(jué)得不需要再多說(shuō)了。響應(yīng)行的基本格式是:
版本號(hào) 狀態(tài)碼 狀態(tài)文本
例如下面的響應(yīng)行:
HTTP/1.1 200 OK
其對(duì)應(yīng)關(guān)系為:
版本號(hào):HTTP/1.1
狀態(tài)碼:200
狀態(tài)文本:OK
HTTP狀態(tài)碼主要表示應(yīng)答的狀態(tài)。狀態(tài)碼是由3個(gè)數(shù)字表示,其中第一個(gè)數(shù)字表示一個(gè)大狀態(tài),后面兩個(gè)數(shù)字表示該大狀態(tài)的一個(gè)子狀態(tài)。200就表示操作成功,還有其他常見(jiàn)的如404表示對(duì)象未找到,500表示服務(wù)器錯(cuò)誤,403表示不能瀏覽目錄等等。
狀態(tài)碼一共分為五個(gè)大狀態(tài),它們是:
1xx
2xx:請(qǐng)求成功處理
3xx
4xx:客戶端出錯(cuò)
5xx:服務(wù)器出錯(cuò)
HTTP協(xié)議示例:
接下來(lái)的所有示例中,我們將代碼都寫(xiě)成前面的一行一行的模式,但略去. 這時(shí)只要記住每行的結(jié)尾都暗含一個(gè)CRLF控制就可以了。例如:
GET /simple.html?bg=white HTTP/1.1 Accept: text/html Accept-Language: zh-cn Accept-Encoding: gzip, deflate User-Agent: Mozilla/4.0 Host: localhost:8080 Connection: Keep-Alive
GET請(qǐng)求沒(méi)有請(qǐng)求正文,但可以包含query-string.
POST請(qǐng)求可以包含請(qǐng)求正文,例如下面帶JSON格式正文的POST請(qǐng)求:
POST /test/demo_form.asp HTTP/1.1 Host: w3schools.com Content-Type: application/json Content-Length: 38 {"name1": "value1", "name2": "value2"}
一個(gè)返回404錯(cuò)誤的響應(yīng)示例:
HTTP/1.1 404 Not Found Date: Mon, 06 Mar 2006 09:03:14 GMT Server: Apache/2.0.55 (Unix) PHP/5.0.5 Content-Length: 291 Keep-Alive: timeout=15, max=100 Connection: Keep-Alive Content-Type: text/html; charset=iso-8859-1 <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN" <html><head> <title>404 Not Found</title> </head><body> <h1>Not Found</h1> <p>The requested URL /notexist was not found on this server.</p> <hr> <address>Apache/2.0.55 (Unix) PHP/5.0.5 Server at localhost Port 8080</address> </body></html>