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