簡介
網(wǎng)絡(luò)上已經(jīng)有不少介紹 HTTP 的好文章,對HTTP的一些細(xì)節(jié)介紹的比較好,所以本篇文章不會對 HTTP 的細(xì)節(jié)進(jìn)行深究,而是從夠高和更結(jié)構(gòu)化的角度將 HTTP 協(xié)議的元素進(jìn)行分類講解。
HTTP的定義和歷史
在一個網(wǎng)絡(luò)中。傳輸數(shù)據(jù)需要面臨三個問題:
1.客戶端如何知道所求內(nèi)容的位置?
2.當(dāng)客戶端知道所求內(nèi)容的位置后,如何獲取所求內(nèi)容?
3.所求內(nèi)容以何種形式組織以便被客戶端所識別?
對于WEB來說,回答上面三種問題分別采用三種不同的技術(shù),分別為:統(tǒng)一資源定位符(URIs),超文本傳輸協(xié)議(HTTP)和超文本標(biāo)記語言(HTML)。對于大多數(shù)WEB開發(fā)人員來說URI和HTML都是非常的熟悉。而HTTP協(xié)議在很多WEB技術(shù)中都被封裝的過多使得HTTP反而最不被熟悉。
HTTP作為一種傳輸協(xié)議,也是像HTML一樣隨著時間不斷演進(jìn)的,目前流行的HTTP1.1是HTTP協(xié)議的第三個版本。
HTTP 0.9
HTTP 0.9作為HTTP協(xié)議的第一個版本。是非常弱的。請求(Request)只有一行,比如:
GET www.cnblogs.com
1
GET www.cnblogs.com
從如此簡單的請求體,沒有POST方法,沒有HTTP 頭可以看出,那個時代的HTTP客戶端只能接收一種類型:純文本。并且,如果得不到所求的信息,也沒有404 500等錯誤出現(xiàn)。
雖然HTTP 0.9看起來如此弱,但已經(jīng)能滿足那個時代的需求了。
HTTP 1.0
隨著1996年后,WEB程序的需求,HTTP 0.9已經(jīng)不能滿足需求。HTTP1.0最大的改變是引入了POST方法,使得客戶端通過HTML表單向服務(wù)器發(fā)送數(shù)據(jù)成為可能,這也是WEB應(yīng)用程序的一個基礎(chǔ)。另一個巨大的改變是引入了HTTP頭,使得HTTP不僅能返回錯誤代碼,并且HTTP協(xié)議所傳輸?shù)膬?nèi)容不僅限于純文本,還可以是圖片,動畫等一系列格式。
除此之外,還允許保持連接,既一次TCP連接后,可以多次通信,雖然HTTP1.0 默認(rèn)是傳輸一次數(shù)據(jù)后就關(guān)閉。
HTTP 1.1
2000年5月,HTTP1.1確立。HTTP1.1并不像HTTP1.0對于HTTP0.9那樣的革命性。但是也有很多增強(qiáng)。
首先,增加了Host頭,比如訪問我的博客:
GET /Careyson HTTP/1.1
Host: www.cnblogs.com
1
2
GET /Careyson HTTP/1.1
Host: www.cnblogs.com
Get后面僅僅需要相對路徑即可。這看起來雖然僅僅類似語法糖的感覺,但實際上,這個提升使得在Web上的一臺主機(jī)可以存在多個域。否則多個域名指向同一個IP會產(chǎn)生混淆。
此外,還引入了Range頭,使得客戶端通過HTTP下載時只下載內(nèi)容的一部分,這使得多線程下載也成為可能。
還有值得一提的是HTTP1.1 默認(rèn)連接是一直保持的,這個概念我會在下文中具體闡述。
HTTP的網(wǎng)絡(luò)層次
在Internet中所有的傳輸都是通過TCP/IP進(jìn)行的。HTTP協(xié)議作為TCP/IP模型中應(yīng)用層的協(xié)議也不例外。HTTP在網(wǎng)絡(luò)中的層次如圖1所示。
圖1.HTTP在TCP/IP中的層次
可以看出,HTTP是基于傳輸層的TCP協(xié)議,而TCP是一個端到端的面向連接的協(xié)議。所謂的端到端可以理解為進(jìn)程到進(jìn)程之間的通信。所以HTTP在開始傳輸之前,首先需要建立TCP連接,而TCP連接的過程需要所謂的“三次握手”。概念如圖2所示。
圖2.TCP連接的三次握手
在TCP三次握手之后,建立了TCP連接,此時HTTP就可以進(jìn)行傳輸了。一個重要的概念是面向連接,既HTTP在傳輸完成之間并不斷開TCP連接。在HTTP1.1中(通過Connection頭設(shè)置)這是默認(rèn)行為。所謂的HTTP傳輸完成我們通過一個具體的例子來看。
比如訪問我的博客,使用Fiddler來截取對應(yīng)的請求和響應(yīng)。如圖3所示。
圖3.用fiddler抓取請求和相應(yīng)
可以看出,雖然僅僅訪問了我的博客,但鎖獲取的不僅僅是一個HTML而已,而是瀏覽器對HTML解析的過程中,如果發(fā)現(xiàn)需要獲取的內(nèi)容,會再次發(fā)起HTTP請求去服務(wù)器獲取,比如圖2中的那個common2.css。這上面19個HTTP請求,只依靠一個TCP連接就夠了,這就是所謂的持久連接。也是所謂的一次HTTP請求完成。
HTTP請求(HTTP Request)
所謂的HTTP請求,也就是Web客戶端向Web服務(wù)器發(fā)送信息,這個信息由如下三部分組成:
1.請求行
2.HTTP頭
3.內(nèi)容
一個典型的請求行比如:
GET www.cnblogs.com HTTP/1.1
1
GET www.cnblogs.com HTTP/1.1
請求行寫法是固定的,由三部分組成,第一部分是請求方法,第二部分是請求網(wǎng)址,第三部分是HTTP版本。
第二部分HTTP頭在HTTP請求可以是3種HTTP頭:1.請求頭(request header) 2.普通頭(general header) 3.實體頭(entity header)
通常來說,由于Get請求往往不包含內(nèi)容實體,因此也不會有實體頭。
第三部分內(nèi)容只在POST請求中存在,因為GET請求并不包含任何實體。
我們截取一個具體的Post請求來看這三部分,我在一個普通的aspx頁面放一個BUTTON,當(dāng)提交后會產(chǎn)生一個Post請求,如圖4所示。
圖4.HTTP請求由三部分組成
HTTP請求方法
雖然我們所常見的只有Get和Post方法,但實際上HTTP請求方法還有很多,比如: PUT方法,DELETE方法,HEAD方法,CONNECT方法,TRACE方法。這里我就不細(xì)說了,自行Bing。
這里重點說一下Get和Post方法,網(wǎng)上關(guān)于Get和Post的區(qū)別滿天飛。但很多沒有說到點子上。Get和Post最大的區(qū)別就是Post有上面所說的第三部分:內(nèi)容。而Get不存在這個內(nèi)容。因此就像Get和Post其名稱所示那樣,Get用于從服務(wù)器上取內(nèi)容,雖然可以通過QueryString向服務(wù)器發(fā)信息,但這違背了Get的本意,QueryString中的信息在HTTP看來僅僅是獲取所取得內(nèi)容的一個參數(shù)而已。而Post是由客戶端向服務(wù)器端發(fā)送內(nèi)容的方式。因此具有請求的第三部分:內(nèi)容。
HTTP響應(yīng)(HTTP Response)
當(dāng)Web服務(wù)器收到HTTP請求后,會根據(jù)請求的信息做某些處理(這些處理可能僅僅是靜態(tài)的返回頁,或是包含Asp.net,PHP,Jsp等語言進(jìn)行處理后返回),相應(yīng)的返回一個HTTP響應(yīng)。HTTP響應(yīng)在結(jié)構(gòu)上很類似于HTTP請求,也是由三部分組成,分別為:
1.狀態(tài)行
2.HTTP頭
3.返回內(nèi)容
首先來看狀態(tài)行,一個典型的HTTP狀態(tài)如下:
HTTP/1.1 200 OK
1
HTTP/1.1 200 OK
第一部分是HTTP版本,第二部分是響應(yīng)狀態(tài)碼,第三部分是狀態(tài)碼的描述,因此也可以把第二和第三部分看成一個部分。
對于HTTP版本沒有什么好說的,而狀態(tài)碼值得說一下,網(wǎng)上對于每個具體的HTTP狀態(tài)碼所代表的含義都有解釋,這里我說一下分類。
信息類 (100-199)
響應(yīng)成功 (200-299)
重定向類 (300-399)
客戶端錯誤類 (400-499)
服務(wù)端錯誤類 (500-599)
HTTP響應(yīng)中包含的頭包括1.響應(yīng)頭(response header) 2.普通頭(general header) 3.實體頭(entity header)。
第三部分HTTP響應(yīng)內(nèi)容就是HTTP請求所請求的信息。這個信息可以是一個HTML,也可以是一個圖片。比如我訪問百度,HTTP Response如圖5所示。
圖5.一個典型的HTTP響應(yīng)
圖5中的響應(yīng)是一個HTML,當(dāng)然還可以是其它類型,比如圖片,如圖6所示。
圖6.HTTP響應(yīng)內(nèi)容是圖片
這里會有一個疑問,既然HTTP響應(yīng)的內(nèi)容不僅僅是HTML,還可以是其它類型,那么瀏覽器如何正確對接收到的信息進(jìn)行處理?
這是通過媒體類型確定的(Media Type),具體來說對應(yīng)Content-Type這個HTTP頭,比如圖5中是text/html,圖6是image/jpeg。
媒體類型的格式為:大類/小類 比如圖5中的html是小類,而text是大類。
IANA(The Internet Assigned Numbers Authority,互聯(lián)網(wǎng)數(shù)字分配機(jī)構(gòu))定義了8個大類的媒體類型,分別是:
application— (比如: application/vnd.ms-excel.)
audio (比如: audio/mpeg.)
image (比如: image/png.)
message (比如,:message/http.)
model(比如:model/vrml.)
multipart (比如:multipart/form-data.)
text(比如:text/html.)
video(比如:video/quicktime.)
HTTP頭
HTTP頭僅僅是一個標(biāo)簽而已,比如我在Aspx中加入代碼:
Response.AddHeader(“測試頭”,”測試值”);
對應(yīng)的我們可以在fiddler抓到的信息如圖7所示。
圖7.HTTP頭
不難看出,HTTP頭并不是嚴(yán)格要求的,僅僅是一個標(biāo)簽,如果瀏覽器可以解析就會按照某些標(biāo)準(zhǔn)(比如瀏覽器自身標(biāo)準(zhǔn),W3C的標(biāo)準(zhǔn))去解釋這個頭,否則不識別的頭就會被瀏覽器無視。對服務(wù)器也是同理。假如你編寫一個瀏覽器,你可以將上面的頭解釋成任何你想要的效果微笑
下面我們說的HTTP頭都是W3C標(biāo)準(zhǔn)的頭,我不會對每個頭的作用進(jìn)行詳細(xì)說明,關(guān)于HTTP頭作用的文章在網(wǎng)上已經(jīng)很多了,請自行Bing。HTTP頭按照其不同的作用,可以分為四大類。
通用頭(General header)
通用頭即可以包含在HTTP請求中,也可以包含在HTTP響應(yīng)中。通用頭的作用是描述HTTP協(xié)議本身。比如描述HTTP是否持久連接的Connection頭,HTTP發(fā)送日期的Date頭,描述HTTP所在TCP連接時間的Keep-Alive頭,用于緩存控制的Cache-Control頭等。
實體頭(Entity header)
實體頭是那些描述HTTP信息的頭。既可以出現(xiàn)在HTTP POST方法的請求中,也可以出現(xiàn)在HTTP響應(yīng)中。比如圖5和圖6中的Content-Type和Content-length都是描述實體的類型和大小的頭都屬于實體頭。其它還有用于描述實體的Content-Language,Content-MD5,Content-Encoding以及控制實體緩存的Expires和Last-Modifies頭等。
請求頭(HTTP Request Header)
請求頭是那些由客戶端發(fā)往服務(wù)端以便幫助服務(wù)端更好的滿足客戶端請求的頭。請求頭只能出現(xiàn)在HTTP請求中。比如告訴服務(wù)器只接收某種響應(yīng)內(nèi)容的Accept頭,發(fā)送Cookies的Cookie頭,顯示請求主機(jī)域的HOST頭,用于緩存的If-Match,If-Match-Since,If-None-Match頭,用于只取HTTP響應(yīng)信息中部分信息的Range頭,用于附屬HTML相關(guān)請求引用的Referer頭等。
響應(yīng)頭(HTTP Response Header)
HTTP響應(yīng)頭是那些描述HTTP響應(yīng)本身的頭,這里面并不包含描述HTTP響應(yīng)中第三部分也就是HTTP信息的頭(這部分由實體頭負(fù)責(zé))。比如說定時刷新的Refresh頭,當(dāng)遇到503錯誤時自動重試的Retry-After頭,顯示服務(wù)器信息的Server頭,設(shè)置COOKIE的Set-Cookie頭,告訴客戶端可以部分請求的Accept-Ranges頭等。
狀態(tài)保持
還有一點值得注意的是,HTTP協(xié)議是無狀態(tài)的,這意味著對于接收HTTP請求的服務(wù)器來說,并不知道每一次請求來自同一個客戶端還是不同客戶端,每一次請求對于服務(wù)器來說都是一樣的。因此需要一些額外的手段來使得服務(wù)器在接收某個請求時知道這個請求來自于某個客戶端。如圖8所示。
圖8.服務(wù)器并不知道請求1和請求2來自同一個客戶端
通過Cookies保持狀態(tài)
為了解決這個問題,HTTP協(xié)議通過Cookies來保持狀態(tài),對于圖8中的請求,如果使用Cookies進(jìn)行狀態(tài)控制,則變成了如圖9所示。
圖9.通過Cookies,服務(wù)器就可以清楚的知道請求2和請求1來自同一個客戶端
通過表單變量保持狀態(tài)
除了Cookies之外,還可以使用表單變量來保持狀態(tài),比如Asp.net就通過一個叫ViewState的Input=“hidden”的框來保持狀態(tài),比如:
XHTML
<input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="/wEPDwUKMjA0OTM4MTAwNGRkXUfhlDv1Cs7/qhBlyZROCzlvf5U=" />
1
<input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="/wEPDwUKMjA0OTM4MTAwNGRkXUfhlDv1Cs7/qhBlyZROCzlvf5U=" />
這個原理和Cookies大同小異,只是每次請求和響應(yīng)所附帶的信息變成了表單變量。
通過QueryString保持狀態(tài)
這個原理和上述兩種狀態(tài)保持方法原理是一樣的,QueryString通過將信息保存在所請求地址的末尾來向服務(wù)器傳送信息,通常和表單結(jié)合使用,一個典型的QueryString比如: