轻松认识HTTP协议的概念和工作原理
一、HTTP協議是什么
我 們在瀏覽器的地址欄里輸入的網站地址叫做URL(UniformResourceLocator,統一資源定位符)。就像每家每戶都有一個門牌地址一樣, 每個網頁也都有一個Internet地址。當你在瀏覽器的地址框中輸入一個URL或是單擊一個超級鏈接時,URL就確定了要瀏覽的地址。瀏覽器通過超文本 傳輸協議(HTTP),將Web服務器上站點的網頁代碼提取出來,并翻譯成漂亮的網頁。因此,在我們認識HTTP之前,有必要先弄清楚URL的組成,例 如:http://www.microsoft.com/china/index.htm。它的含義如下:
1.http://:代表超文本傳輸協議,通知microsoft.com服務器顯示Web頁,通常不用輸入;
2.www:代表一個Web(萬維網)服務器;
3.Microsoft.com/:這是裝有網頁的服務器的域名,或站點服務器的名稱;
4.China/:為該服務器上的子目錄,就好像我們的文件夾;
5.Index.htm:index.htm是文件夾中的一個HTML文件(網頁)。
我 們知道,Internet的基本協議是TCP/IP協議,然而在TCP/IP模型最上層的是應用層(Applicationlayer),它包含所有高層 的協議。高層協議有:文件傳輸協議FTP、電子郵件傳輸協議SMTP、域名系統服務DNS、網絡新聞傳輸協議NNTP和HTTP協議等。
HTTP 協議(HypertextTransferProtocol,超文本傳輸協議)是用于從WWW服務器傳輸超文本到本地瀏覽器的傳送協議。它可以使瀏覽器更 加高效,使網絡傳輸減少。它不僅保證計算機正確快速地傳輸超文本文檔,還確定傳輸文檔中的哪一部分,以及哪部分內容首先顯示(如文本先于圖形)等。這就是 你為什么在瀏覽器中看到的網頁地址都是以“http://”開頭的原因。
自WWW誕生以來,一個多姿多彩的資訊和虛擬的世界便出現在我們 眼前,可是我們怎么能夠更加容易地找到我們需要的資訊呢?當決定使用超文本作為WWW文檔的標準格式后,于是在1990年,科學家們立即制定了能夠快速查 找這些超文本文檔的協議,即HTTP協議。經過幾年的使用與發展,得到不斷的完善和擴展,目前在WWW中使用的是HTTP/1.0的第六版。
二、HTTP是怎樣工作的
既然我們明白了URL的構成,那么HTTP是怎么工作呢?我們接下來就要討論這個問題。
由 于HTTP協議是基于請求/響應范式的(相當于客戶機/服務器)。一個客戶機與服務器建立連接后,發送一個請求給服務器,請求方式的格式為:統一資源標識 符(URL)、協議版本號,后邊是MIME信息包括請求修飾符、客戶機信息和可能的內容。服務器接到請求后,給予相應的響應信息,其格式為一個狀態行,包 括信息的協議版本號、一個成功或錯誤的代碼,后邊是MIME信息包括服務器信息、實體信息和可能的內容。
許多HTTP通訊是由一個用戶代 理初始化的并且包括一個申請在源服務器上資源的請求。最簡單的情況可能是在用戶代理和服務器之間通過一個單獨的連接來完成。在Internet上, HTTP通訊通常發生在TCP/IP連接之上。缺省端口是TCP80,但其它的端口也是可用的。但這并不預示著HTTP協議在Internet或其它網絡 的其它協議之上才能完成。HTTP只預示著一個可靠的傳輸。
這個過程就好像我們打電話訂貨一樣,我們可以打電話給商家,告訴他我們需要什么規格的商品,然后商家再告訴我們什么商品有貨,什么商品缺貨。這些,我們是通過電話線用電話聯系(HTTP是通過TCP/IP),當然我們也可以通過傳真,只要商家那邊也有傳真。
以上簡要介紹了HTTP協議的宏觀運作方式,下面介紹一下HTTP協議的內部操作過程。
在WWW 中,“客戶”與“服務器”是一個相對的概念,只存在于一個特定的連接期間,即在某個連接中的客戶在另一個連接中可能作為服務器。基于HTTP協議的客戶/ 服務器模式的信息交換過程,它分四個過程:建立連接、發送請求信息、發送響應信息、關閉連接。這就好像上面的例子,我們電話訂貨的全過程。
其 實簡單說就是任何服務器除了包括HTML文件以外,還有一個HTTP駐留程序,用于響應用戶請求。你的瀏覽器是HTTP客戶,向服務器發送請求,當瀏覽器 中輸入了一個開始文件或點擊了一個超級鏈接時,瀏覽器就向服務器發送了HTTP請求,此請求被送往由IP地址指定的URL。駐留程序接收到請求,在進行必 要的操作后回送所要求的文件。在這一過程中,在網絡上發送和接收的數據已經被分成一個或多個數據包(packet),每個數據包包括:要傳送的數據;控制 信息,即告訴網絡怎樣處理數據包。TCP/IP決定了每個數據包的格式。如果事先不告訴你,你可能不會知道信息被分成用于傳輸和再重新組合起來的許多小 塊。
也就是說商家除了擁有商品之外,它也有一個職員在接聽你的電話,當你打電話的時候,你的聲音轉換成各種復雜的數據,通過電話線傳輸到 對方的電話機,對方的電話機又把各種復雜的數據轉換成聲音,使得對方商家的職員能夠明白你的請求。這個過程你不需要明白聲音是怎么轉換成復雜的數據的。 了解WWW服務與HTTP協議 ?
| 在前一篇文章里,我們介紹了網絡應用和網絡協議的一些基礎知識,現在,讓我們來了解一下WWW服務應用和HTTP網絡協議。 歷史上,先后問世了多個具有重大社會影響的電子通信技術。第一個這樣的技術是19世紀70年代發明的電話。電話使得不在同一物理位置的兩人得以實時地口 頭交流。它對社會有重大的影響——有好的也有壞的。下一個電子通信技術是20世紀20年代及30年代問世的廣播收音機/電視機。廣播收音機/電視機使得人 們能收聽收視大量的音頻和視頻信息。它對社會同樣有重大的影響——有好的也有壞的。改變了人們的生活與工作方式的第三個重大通信技術是web。web最吸 引用戶的也許是它的隨選(on demand)操作性。用戶只在想要時收到所要的東西。這一點不同于廣播收音機/電視機。廣播收音機/電視機的用戶是在其內容供應商播出內容期間被迫收聽 收視。除了隨選操作性,Web還有許多大家喜愛的其他精彩特性。任何個人都可以極其容易地在Web上公布任何信息;任何人都可能以極低的成本成為發行人。 超鏈接和搜索引擎幫助我們在Web站點的海洋中導航。圖形和動畫刺激著我們的感官。表單、Java小應用程序、Activex控件以及其他許多設備使得我 們能與Web頁面和站點交互。Web還越來越普遍地提供存放在因特網中的、可隨選訪問(即點播)的大量音頻和視頻材料的菜單接口。 HTTP概貌 Web的應用層協議HTTP是Web的核心。HTTP在Web的客戶程序和服務器程序中得以實現。運行在不同端系統上的客戶程序和服務器程序通過交換 HTTP消息彼此交流。HTTP定義這些消息的結構以及客戶和服務器如何交換這些消息。在詳細解釋HTTP之前,我們先來回顧一些web中的術語。 Web頁面(web page,也稱為文檔)由多個對象構成。對象(object)僅僅是可由單個URL尋址的文件,例如HTML文件、JPG圖像、GIF圖像、JAVA小應 用程序、語音片段等。大多數Web頁面由單個基本HIML文件和若干個所引用的對象構成。例如,如果一個Web頁面包含HTML文本和5個JPEG圖像, 那么它由6個對象構成,即基本H1ML文件加5個圖像。基本HTML文件使用相應的URL來引用本頁面的其他對象。每個URL由存放該對象的服務器主機名 和該對象的路徑名兩部分構成。例如,在如下的URL中: www.chinaitlab.com/urlpath/picture.qif www.chinaitlab.com是一個主機名,/urlpath/picture.qif是一個路徑名。瀏覽器是web的用戶代理,它顯示所請求 的Web頁面,并提供大量的導航與配置特性。Web瀏覽器還實現HTTP的客戶端,因此在web上下文中,我們會從進程意義上互換使用“瀏覽器”和“客 戶”兩詞。流行的Web瀏覽器有Netscape Communicator,firefox和微軟的IE等。Web服務器存放可由URL尋址的Web對象。web服務器還實現HTTP的服務器端。流行的 Web服務器有Apache、微軟的IIS以及Netscape Enterprise Server。Netcraft提供了web服務器的概要剖析[Netcrft 2000]。 HTTP定義Web客戶(即瀏覽 器)如何從web服務器請求Web頁面,以及服務器如何把Web頁面傳送給客戶。下圖展示了這種請求—響應行為。當用戶請求一個Web頁面(譬如說點擊某 個超鏈接)時,瀏覽器把請求該頁面中各個對象的HTTP請求消息發送給服務器。服務器收到請求后,以運送含有這些對象HTTP響應消息作為響應。到 1997年底,基本上所有的瀏覽器和Web服務器軟件都實現了在RFC 1945中定義的HTTP/1.0版本。1998年初,一些Web服務器軟件和瀏覽器軟件開始實現在RFC 2616中定義的HTTP/1.1版本。H1TP/1.1與HTTP/1.0后向兼容;運行1.1版本的web服務器可以與運行1.0版本的瀏覽器“對 話”,運行1.1版本的瀏覽器也可以與運行1.0版本的Web服務器“對話”。 圖1 HTTP請求與響應行為 HTTP/1.0和HTTP/1.1都把TCP作為底層的傳輸協議。HTTP客戶首先發起建立與服務器TCP連接。一旦建立連接,瀏覽器進程和服務器進 程就可以通過各自的套接字來訪問TCP。如前所述,客戶端套接字是客戶進程和TCP連接之間的“門”,服務器端套接字是服務器進程和同一TCP連接之間的 “門”。客戶往自己的套接字發送HTTP請求消息,也從自己的套接字接收HTTP響應消息。類似地,服務器從自己的套接字接收HTTP請求消息,也往自己 的套接字發送HTTP響應消息。客戶或服務器一旦把某個消息送入各自的套接字,這個消息就完全落入TCP的控制之中。TCP給HTTP提供一個可靠的數據 傳輸服務;這意味著由客戶發出的每個HTTP請求消息最終將無損地到達服務器,由服務器發出的每個HTTP響應消息最終也將無損地到達客戶。我們可從中看 到分層網絡體系結構的一個明顯優勢——HTTP不必擔心數據會丟失,也無需關心TCP如何從數據的丟失和錯序中恢復出來的細節。這些是TCP和協議棧中更 低協議層的任務。 TCP還使用一個擁塞控制機制。該機制迫使每個新的TCP連接一開始以相對緩慢的速率傳輸數據,然而只要網絡不擁塞,每個連接可以迅速上升到相對較高的速率。這個慢速傳輸的初始階段稱為緩啟動(slow start)。 需要注意的是,在向客戶發送所請求文件的同時,服務器并沒有存儲關于該客戶的任何狀態信息。即便某個客戶在幾秒鐘內再次請求同一個對象,服務器也不會響 應說:自己剛剛給它發送了這個對象。相反,服務器重新發送這個對象,因為它已經徹底忘記早先做過什么。既然HTTP服務器不維護客戶的狀態信息,我們于是 說HTTP是一個無狀態的協議(stateless protocol)。 非持久連接和持久連接 HTTP既可以使用非持久連接(nonpersistent connection),也可以使用持久連接(persistent connection)。HTTP/1.0使用非持久連接,HTTP/1.1默認使用持久連接。 非持久連接 讓我們查看一下非持久連接情況下從服務器到客戶傳送一個Web頁面的步驟。假設該貝面由1個基本HTML文件和10個JPEG圖像構成,而且所有這些對 象都存放在同一臺服務器主機中。 再假設該基本HTML文件的URL為:www.chinaitlab.com/somepath/index.html。 下面是具體步騾: 1.HTTP客戶初始化一個與服務器主機www.chinaitlab.com中的HTTP服務器的TCP連接。HTTP服務器使用默認端口號80監聽來自HTTP客戶的連接建立請求。 2.HTTP客戶經由與TCP連接相關聯的本地套接字發出—個HTTP請求消息。這個消息中包含路徑名/somepath/index.html。 3.HTTP服務器經由與TCP連接相關聯的本地套接字接收這個請求消息,再從服務器主機的內存或硬盤中取出對象/somepath/index.html,經由同一個套接字發出包含該對象的響應消息。 4.HTTP服務器告知TCP關閉這個TCP連接(不過TCP要到客戶收到剛才這個響應消息之后才會真正終止這個連接)。 5.HTTP客戶經由同一個套接字接收這個響應消息。TCP連接隨后終止。該消息標明所封裝的對象是一個HTML文件。客戶從中取出這個文件,加以分析后發現其中有10個JPEG對象的引用。 6.給每一個引用到的JPEG對象重復步騾1-4。 瀏覽器在接收web頁面的同時把它顯示給用戶。不同的瀏覽器可能會以略有不同的方式解釋(也就是向用戶顯示)同一個web頁面。HTTP與客戶如何解釋 Web頁面沒有任何關系,其規范([RFC 1945]和[RFC 2616I)僅僅定義HTTP客戶程序和服務器程序之間的通信協議。 上述步驟之所以稱為使用非持久連接,原因是每次服務器發出一個對象后,相應的TCP連接就被關閉,也就是說每個連接都沒有持續到可用于傳送其他對象。每 個TCP連接只用于傳輸一個請求消息和一個響應消息。就上述例子而言,用戶每請求一次那個web頁面,就產生11個TCP連接。 在上述步騾中,我們有意不說清客戶是通過10個串行的TCP連接先后取得所有JPEG對象,還是通過并行的TCP連接同時取得其中某些JPEG對象。實際 上,現今的瀏覽器允許用戶通過配置來控制并行連接的程度。大多數瀏覽器默認可以打開5到10個并行的TCP連接,每個連接處理一個請求—響應事務。用戶要 是喜歡,可以把最大并行連接數設為l,那樣的話這10個連接是串行地建立的。我們將在第3章看到,使用并行連接可以縮短響應時間。 繼續介紹之前,先估算一下從客戶請求基本HTML文件到它收到該文件所經歷的時間。為此我們定義往返時間(round trip time,簡稱RTT),它是一個小分組從客戶主機游動到服務器主機再返回客戶主機所花的時間。RTT包括分組傳播延遲、在中間路由器和交換機土的分組排 隊延遲以及分組處理延遲。下面考慮用戶點擊某個超鏈接時會發生什么。用戶的點擊導致瀏覽器發起建立一個與Web服務器的TCP連接;這里涉及·—次“三次 握手”過程——首先是客戶向服務器發送一個小的冗余消息,接著是服務器向客戶確認并響應以一個小的TCP消息,最后是客戶向服務器回確認。三次握手過程的 前兩次結束時,流逝的時間為1個RTT。此時客戶把HTTP請求消息發送到TCP連接中,客戶接著把三次握手過程最后一次中的確認捎帶在包含這個消息的數 據分節中發送以去。服務器收到來自TCP連接的請求消息后,把相應的HTML文件發送到TCP連接中,服務器接著把對早先收到的客戶請求的確認捎帶在包含 該HTML文件的數據分節中發送出去。這個HTTP請求順應交互也花去1個RTT時間。因此,總的響應時間粗略地算是2個RTT加上服務器發送這個 HTMI文件的時間。 持久連接 非持久連接有些缺點。首先,客戶得為每個待請求的對象建立 并維護一個新的連接。對于每個這樣的連接,TCP得在客戶端和服務器端分配TCP緩沖區,并維持TCP變量。對于有可能同時為來自數百個不同客戶的請求提 供服務的web服務器來說,這會嚴重增加其負擔。其次,如前所述,每個對象都有2個RTT的響應延長——一個RTT用于建立TCP連接,另—個RTT用于 請求和接收對象。最后,每個對象都遭受TCP緩啟動,因為每個TCP連接都起始于緩啟動階段。不過并行TCP連接的使用能夠部分減輕RTT延遲和緩啟動延 遲的影響。 在持久連接情況下,服務器在發出響應后讓TCP連接繼續打開著。同一對客戶/服務器之間的后續請求和響應可以通過這個 連接發送。整個Web頁面(上例中為包含一個基本HTMLL文件和10個圖像的頁面)自不用說可以通過單個持久TCP連接發送:甚至存放在同一個服務器中 的多個web頁面也可以通過單個持久TCP連接發送。通常,HTTP服務器在某個連接閑置一段特定時間后關閉它,而這段時間通常是可以配置的。持久連接分 為不帶流水線(without pipelining)和帶流水線(with pipelining)兩個版本。如果是不帶流水線的版本,那么客戶只在收到前一個請求的響應后才發出新的請求。這種情況下,web頁面所引用的每個對象 (上例中的10個圖像)都經歷1個RTT的延遲,用于請求和接收該對象。與非持久連接2個RTT的延遲相比,不帶流水線的持久連接已有所改善,不過帶流水 線的持久連接還能進一步降低響應延遲。不帶流水線版本的另一個缺點是,服務器送出一個對象后開始等待下一個請求,而這個新請求卻不能馬上到達。這段時間服 務器資源便閑置了。 HTTP/1.1的默認模式使用帶流水線的持久連接。這種情況下,HTTP客戶每碰到一個引用就立即發出一個 請求,因而HTTP客戶可以一個接一個緊挨著發出各個引用對象的請求。服務器收到這些請求后,也可以一個接一個緊挨著發出各個對象。如果所有的請求和響應 都是緊挨著發送的,那么所有引用到的對象一共只經歷1個RTT的延遲(而不是像不帶流水線的版本那樣,每個引用到的對象都各有1個RTT的延遲)。另外, 帶流水線的持久連接中服務器空等請求的時間比較少。與非持久連接相比,持久連接(不論是否帶流水線)除降低了1個RTT的響應延遲外,緩啟動延遲也比較 小。其原因在于既然各個對象使用同一個TCP連接,服務器發出第一個對象后就不必再以一開始的緩慢速率發送后續對象。相反,服務器可以按照第一個對象發送 完畢時的速率開始發送下一個對象。 HTTP消息格式 HTTP規范1.0[RPcl945]和1.1[RFC 2616]定義了HTTP消息的格式。HTTP消息分為請求消息和響應稍息兩類。下面我們分別進行介紹。 HTTP請求消息 下面是一個典型的HTTP請求消息: GET /somedir/page.html H7TP/1.1 Host:www.chinaitlab.com Connection:close User-agent:Mozilla/4.0 Accept-language:zh-cn (額外的回車符和換行符) 仔細檢查這個簡單的請求消息,我們可從中學到不少東西。首先,這個消息是用普通的ASCII文本書寫的。其次,這個消息共有5行(每行以一個回車符和一 個換行符結束),最后一行后面還有額外的一個回車特和換行符。當然,一個請求消息可以不止這么多行,也可以僅僅只有一行。該請求消息的第一行稱為請求行 (request line),后續各行都稱為頭部行(header)。請求行有3個寧段:方法字段、URL字段、HTTP版本宇段。方法字段有若干個值可供選擇,包括 GET、POST和HEAD。HTTP請求消息絕大多數使用GET方法,這是瀏覽器用來請求對象的方法,所請求的對象就在URL字段中標識。本例表明瀏覽 器在請求對象/somedir/page.html。版本字段是不言自明的;本例中瀏覽器實現的是HTTP/1.1版本。 現在看 一下本例中的各個頭部行。頭部行Host:www.chinaitlab.com定存放所請求對象的主機。請求消息中包含頭部Connection: close是在告知服務器本瀏覽器不想使用持久連接;服務器發出所請求的對象后應關閉連接。盡管產生這個請求消息的瀏覽器實現的是HTTP/1.1版本, 它還是不想使用持久連接。User-agent頭部行指定用戶代理,也就是產生當前請求的瀏覽器的類型。本例的用戶代理是Mozilla/4.0,它是 Nelscape瀏覽器的一個版本。這個頭部行很有用,因為服務器實際上可以給不同類型的用戶代理發送同一個對象的不同版本(這些不同版本位用同一個 URL尋址)。最后,Accept-languag:頭部行指出要是所請求對象有簡體中文版本,那么用戶寧愿接收這個版本;如果沒有這個語言版本,那么服 務器應該發送其默認版本。Accept-languag:僅僅是HTTP的眾多內容協商頭部之一。 我們接著看一下下圖所示的請求消息的一般格式。 圖2:HTTP請求格式 上面的請求消息例子符合這個格式,不過一般格式中還有一個位于各個頭部(及額外的回車符和換行符)之后的“附屬體”(毗叮body)。附屬體不在GET 方法中使用,而是在POST方法中使用。POST方法適用于需由用戶填寫表單的場合,如往google搜索引擎中填入待搜索的詞。用戶提交表單后,瀏覽器 就像用戶點擊了超鏈接那樣仍然從服務器請求一個Web頁面,不過該頁面的具體內容卻取決于用戶填寫在表單各個字段中的值。如果瀏覽器使用POST方法提出 該請求,那么請求消息附屬體中包含的是用戶填寫在表單各個字段中的值。與GET方法類似的是HEAD方法,兩者的差別只是服務器在對HEAD方法的響應消 息中去掉了所請求的對象,其他內容則與對GET方法的響應消息一樣。HEAD方法通常用于HTTP服務器軟件開發人員進行調試。 HTTP響應消息 下面是一個典型的HTTP響應消息: HTTP/1.1 200 0K Connectlon:close Date: Thu, 13 Oct 2005 03:17:33 GMT Server: Apache/2.0.54 (Unix) Last—Nodified:Mon,22 Jun 1998 09;23;24 GMT Content—Length:682l Content—Type:text/html (數據 數據 數據 數據 數據…………) 這個響應消息分為3部分:1個起始的狀態行(status line),6個頭部行、1個包含所請求對象本身的附屬體。狀態行有3個字段:協議版本字段、狀態碼字段、原因短語字段。本例的狀態行表明,服務器使用 HTTP/1.1版本,響應過程完全正常(也就是說服務器找到了所請求的對象,并正在發送)。 現在看一下本例中的各個頭部行。服 務器使用Connectlon:close頭部行告知客戶自己將在發送完本消息后關閉TCP連接。Date:頭部行指出服務器創建并發送本響應消息的日期 和時間。注意,這并不是對象本身的創建時間或最后修改時間,而是服務器把該對象從其文件系統中取出,插入響應消息中發送出去的時間。Server:頭部行 指出本消息是由Apache服務器產生的;它與HTTP請求消息中的User-agent:頭部行類似。Last—Nodified:頭部行指出對象本身 的創建或最后修改日期或時間。Last—Nodified:頭部對于對象的高速緩存至關重要,且不論這種高速緩存是發生在本地客戶主機上還是發生在網絡高 速緩存服務器主機(也就是代理服務器主機)上。Content—Length:頭部行指出所發送對象的字節數。Content—Type:頭部行指出包含 在附屬體中的對象是HTML文本。對象的類型是由Content—Type:頭部而不是由文件擴展名正式指出的。 注意,如果服務器收到一個HTTP/1.0的請求,那么它即使是一個HTTP/1.1服務器,也不會使用持久連接。相反,這樣的HTTP/1.1服務器會 在發出所請求的對象后關閉TCP連接。這么做是必要的,因為HTTP/1.0客戶期待服務器馬上關閉連接。 我們接著看一下如下圖所示的響應消息的一般格式。前面的響應消息例子完全符合這個格式。響應消息中的狀態碼和原因短語指示相應請求的處理結果,下面列出了一些常見的狀態碼和相應的原因短語: 圖3:響應消息的一般格式 ●200 0K;請求成功,所請求信息在響應消息中返回。 ●301 Moved Permanently:所請求的對象己永久性遷移;新的URL在本響應消息的Location:頭部指出。客戶軟件會自動請求這個新的URL。 ●400 Bad Request;表示服務器無法理解相應請求的普通錯誤的狀態碼 ●404 Not Found:服務器上不存在所請求的文檔。 ●HTTP Version Not Support:服務器不支持所請求的HTTP協議版本。 你想如何看到一個真實的H1TP應答消息呢?這非常簡單。可以使用nc工具連接到你喜歡的服務器(nc/netcat是一個黑客很喜歡用的工具,可以方 便在主機之間建立TCP連接),然后輸入一行請求消息,用來請求位于該服務器上的某個對象。例如,如果你可以輸入以下指令: nc www.chinaitlab.com 80 GET /index.shtml HTTP/1.0 (在輸入第二行之后,敲兩次回車),這就打開了一個到主機www.chinaitlab.com的端口80的TCP連接,然后發送HTTP GET命令。你應該能看到包含著YESKY主頁的基本HTML文件的應苔消息。如果你想只看到HTTP消息行而不接收該對象本身,那么就把上面的GET換 成HEAD。最后,看一下能得到什么樣的應答消息。 在這里我們討論了大量能夠在HTTP請求和應答消息中使用的頭部行。HTTP規范(尤其是HTTP/1.1)定義了更多可以由瀏覽器、Web服務器和網絡緩沖服務器插入的頭部行。 我們可以便用nc工具完全控制在請求消息中包含哪些頭部,那么瀏覽器如何決定該在請求消息個包含哪些頭部呢?Web服務器又是如何決定該在響應消息中包 含哪些頭部?瀏覽器是根據自己的用戶代理類型、所支持的HTTP版本(HTTP/1.0版本的瀏覽器自然不會產生HTTP/1.1版本的頭部)、用戶對瀏 覽器的配置(如所偏愛的語言)等因素生成請求消息中的各個頭部的。web服務器有類似的情形:它們有不同的產品、版本和配置,所有這些因素都會影響在響應 消息中包含哪些頭部。 本文討論過的和即將討論的用于HTTP請求消息和響應消息中的頭部僅僅是很小的一部分,HTTP規范中定義了更多可用的頭部,可以查閱相關的RFC文檔進行更詳細的了解。 用戶—服務器交互 身份認證和cookie 我們已經知道HTTP服務器是無狀態的。這樣的處理可以簡化服務器程序的設計,以便開發出更高性能的Web服務器軟件。然而,一個Web站點往往有標識 其用戶的需求,因為其web服務器可能希望限制用戶的訪問,也可能想要根據用戶的身份來提供內容。HTTP提供了兩種幫助服務器標識用戶的機制:身份認證 和cookle。 身份認證許多web站點要求用戶提供一個用戶名—口令對才能訪問存放在其服務器中的文檔。這種要求稱為身份認證 (authentication)。HTTP提供特殊的狀態碼和頭部來幫助Web站點執行身份認證。我們通過查看一個例子來領會這些特殊的狀態碼和頭部如 何工作。假設有—個客戶在請求來自某個服務器的一個對象,而該服務器要求用戶授予權限。 客戶首先發送一個不合特殊頭部的普通請求 消息。服務器以空的附屬體和一個“401Authorization Required”狀態碼作為響應。服務器還在這個響應消息中包含“個WWW-Authenticate:頭部,說明具體如何執行身份認證。這個頭部的典 型值是指出用戶需要提供一個用戶名—口令對。 客戶收到這個響應消息后提示用戶輸入用戶名和口令,然后重新發送請求消息。這一回客戶在請求消息中包含了一個Authorization:頭部,其中包含有用戶輸入的用戶名和口令。 取得第一個對象后,客戶在同為請求該服務器上對象的后續請求中繼續發送這個用戶名—口令對。這個做法一般將持續到用戶關閉瀏覽器為止。在瀏覽器未被關閉 之前,這個用戶名—口令對是高速緩存著的,因此瀏覽器不會每請求一個對象就提示用戶輸入一次用戶名和口令。通過上述方式,要求用戶授權的Web站點就能標 識出每個請求的用戶了。 我們需要知道,HTTP執行的是一種相當脆弱的身份認證方式,不難攻破。現代有很多更為安全的認證方式,我們會在以后介紹。 cookie是一種可讓Web站點用來跟蹤用戶的候選機制,定義在RFC 2109中。有些Web站點使用cookie,其他Web站點則不用。下面查看一個例子。假設一個客戶首次聯系一個使用cookie的web站點。服務器 會在其響應中包含一個Set—Cookie:頭部。該頭部的值可以是一個由Web服務器產生的客戶標識數.例如: Set-Cookie:1678453 客戶收到這個響應消息,看到其中的Set-Cookie:頭部和標識數后,會在存放在客戶主機中的某個特殊的cookie文件中添加一行。這一行一般包 含服務器主機的主機名和這個與用戶關聯的標識數。在一段時間(如一個星期)之后請求同一個服務器時,由同一個用戶啟動的新客戶會在請求消息中包含一個 cookie頭部,其值為早先由該服務器產生的標識數,例如:Cookie:1678453 在這種方式中,服務器并不知道提出請求的用戶的用戶名,但是它確實知道該用戶與一個星期前提出請求的用戶是同一個。 Web服務器有多個使用coohe的目的: ●如果服務器要求身份認證,但又不想在同一用戶每次訪問本Web站點時都麻煩他輸入用戶名和口令,那么可以設置一個cookie。 ●如果服務器想要記住用戶的偏好,以便在他們后續訪問期間有目的地提供廣告,那么可以設置一個cookie。 ●如果web站點提供購物服務,那么服務器可以使用cookie跟蹤用戶購買的物品,就是建立一個虛擬的購物車。 需指出的是,cookie不適用于會從不同主機訪問同一web站點的游動用戶。這種情況下,該web站點會把同一個用戶在不同主機上的使用看成是由新的用戶執行的。 帶條件的GET Web高速緩存技術通過就近存取先前取得的對象來降低對象檢索延遲,減少因特網上的web流量。Web的高速緩存既可以駐留在客戶主機中,也可以駐留在中 間網絡高速緩存服務器主機中。我們將在稍后討論網絡高速緩存,這里只關注客戶的高速緩存。 Web高速緩存在降低用戶可感知的響應時間的同時,卻引入了一個新的問題——高速緩存中存放的對象的拷貝可能是過期的。換句話說,存放在web服務器中 的對象可能己在客戶高速緩存下它的一個拷貝之后被修改了。幸運的是,HTTP提供一個專門的機制,使得在允許客戶進行高速緩存的同時,仍確保傳遞給瀏覽器 的所有對象都是最新的。這個機制稱為帶條件的0ET(conditional GET)。滿足條件(1)使用GET方法和(2)包含If-Modified-S1nce:頭部的HTTP請求消息就是所謂的帶條件的Get消息。 我們通過查看一個例子來說明帶條件的GET如何工作,向服務器請求一個尚未高速緩存的對象: GET /fruit/kiwi.gif HTTP/1.0 User—agent: Mozilla/4.0 接著,web服務器把帶這個對象的一個響應消息發送給客戶: HTTP/1.0 200 OK Date: Thu, 13 Oct 2005 05:33:47 GMT Server: Apache/2.0.54 (Unix) Last-Modified:Thu, 13 Oct 2005 02:32:47 GMT Content-Type:image/gif (數據 數據 數據 數據 數據……) 客戶把這個對象顯示給用戶,同時把它保存在自己的本地高速緩存中客戶還隨該對象本身高速緩存最后修改日期與時間。一個星期之后,同一個用戶請求同一個對 象,而該對象仍然存放在高速緩存中。既然web服務器中的該對象有可能已在最近一個星期被修改過,于是瀏覽器發出一個帶條件的GET消息,執行判定高速緩 存的對象拷貝是否為最新的檢查; GET /fruit/kiwi.gif HTTP/1.0 User—agent: Mozilla/4.0 If—Modlfied—Since:Thu, 13 Oct 2005 02:32:47 GMT 其中,If—Modlfied—Since:頭部的值就等于一個星期前由服務器發送的Last-Modified:頭部的值。這個帶條件的GET消息告 知服務器,只有在該對象自所指定的時間以來被修改了的前提下才發送它。假設該對象在這段時間內未曾被修改過,那么服務器將發送一個附屬體為空的響應消息給 客戶; HTTP/1.0 304 Not Modified Date: Thu, 20 Oct 2005 05:33:47 GMT Server: Apache/2.0.54 (Unix) 我們看到,web服務器仍然發送——個響應消息作為帶條件的GET消息的響應,不過其中不包含所請求的對象。包含該對象只會浪費帶寬,并延長用戶可感知 的響應時間,特別是在該對象很大的時候。注意,這個響應消息的狀態為“304 Not Modified”,它告知客戶可以放心使用所請求對象的高速緩存版本。 |
用Socket類實現HTTP協議客戶端應用
Http客戶端程序已集成在Java語言中,可以通過URLConnection類調用。遺憾的
是,由于SUN沒有公布Http客戶程序的源碼,它實現的細節仍是一個謎。本文根據HTTP
協議規范,用Java.net.Socket類實現一個HTTP協議客戶端程序。
1.Socket類:
了解TCP/IP協議集通信的讀者知道,協議間的通信是通過Socket完成的。在
Java.net包中,Socket類就是對Socket的具體實現。它通過連接到主機后,返回一個
I/O流,實現協議間的信息交換。
2 . HTTP協議
HTTP協議同其它TCP/IP協議集中的協議一樣,是遵循客戶/服務器模型工作的。客
戶端發往服務端的信息格式如下:
------------------------------
請求方法 URL HTTP協議的版本號
提交的元信息
**空行**
實體
------------------------------
請求方法是對這次連接工作的說明,目前HTTP協議已經發展到1.1版,它包括GET、
HEAD、POST、DELETE、OPTIONS、TRACE、PUT七種。元信息是關于當前請求的信息。通
過分析元信息,可以檢查實體數據是否完整,接收過程是否出錯,類型是否匹配等。元
信息的引入使HTTP協議通信更加穩妥可靠。實體是請求的具體內容。
將上述報文發往Web服務器,如果成功,應答格式如下:
--------------------------------
HTTP協議的版本號 應答狀態碼 應答狀態碼說明
接收的元信息
**空行**
實體
--------------------------------
以上報文發向客戶端,并且接收成功,彼此間關閉連接,完成一次握手。
下面用最常用的GET方法,來說明具體的報文應用
----------------------------------
GET http://www.youhost.com HTTP/1.0
accept: www/source; text/html; image/gif; image/jpeg; */*
User_Agent: myAgent
**空行**
-----------------------------------
這個報文是向www.youhost.com主機請求一個缺省HTML文檔。客戶端HTTP協議版本
號是1.0版,元信息包括可接收的文件格式,用戶代理,每一段之間用回車換行符分
隔,最后以一個空行結束。發向服務器后,如果執行過程正常,服務器返回以下代碼:
------------------------------------
HTTP/1.1 200 OK
Date: Tue, 14 Sep 1999 02:19:57 GMT
Server: Apache/1.2.6
Connection: close
Content-Type: text/html
**空行**
......
------------------------------------
HTTP/1.1表示這個HTTP服務器是1.1版,200是服務器對客戶請求的應答狀態碼,OK
是對應答狀態碼的解釋,之后是這個文檔的元信息和文檔正文。(相關應答狀態碼和元
信息的解釋請參閱Inetrnet標準草案:RFC2616)。
3. HTTP客戶端程序:
import java.net.*;
import java.io.*;
import java.util.Properties;
import java.util.Enumeration;
public class Http {
protected Socket client;
protected BufferedOutputStream sender;
protected BufferedInputStream receiver;
protected ByteArrayInputStream byteStream;
protected URL target;
private int responseCode=-1;
private String responseMessage="";
private String serverVersion="";
private Properties header = new Properties();
public Http() { }
public Http(String url) {
GET(url) ;
}
/* GET方法根據URL,會請求文件、數據庫查詢結果、程序運行結果等多種內容 */
public void GET(String url) {
try {
checkHTTP(url);
openServer(target.getHost(),target.getPort() );
String cmd = "GET "+ getURLFormat(target) +" HTTP/1.0\r\n"
+ getBaseHeads()+"\r\n";
sendMessage(cmd);
receiveMessage();
}catch(ProtocolException p) {
p.printStackTrace();
return;
}catch(UnknownHostException e) {
e.printStackTrace();
return;
}catch(IOException i)
i.printStackTrace();
return;
}
}
/*
* HEAD方法只請求URL的元信息,不包括URL本身。若懷疑本機和服務器上的
* 文件相同,用這個方法檢查最快捷有效。
*/
public void HEAD(String url) {
try {
checkHTTP(url);
openServer(target.getHost(),target.getPort() );
String cmd = "HEAD "+getURLFormat(target)+" HTTP/1.0\r\n"
+getBaseHeads()+"\r\n";
sendMessage(cmd);
receiveMessage();
}catch(ProtocolException p) {
p.printStackTrace();
return;
}catch(UnknownHostException e) {
e.printStackTrace();
return;
}catch(IOException i)
i.printStackTrace();
return;
}
}
/*
* POST方法是向服務器傳送數據,以便服務器做出相應的處理。例如網頁上常用的
* 提交表格。
*/
public void POST(String url,String content) {
try {
checkHTTP(url);
openServer(target.getHost(),target.getPort() );
String cmd = "POST "+ getURLFormat(target) +"
HTTP/1.0\r\n"+getBaseHeads();
cmd += "Content-type: application/x-www-form-urlencoded\r\n";
cmd += "Content-length: " + content.length() + "\r\n\r\n";
cmd += content+"\r\n";
sendMessage(cmd);
receiveMessage();
}catch(ProtocolException p) {
p.printStackTrace();
return;
}catch(UnknownHostException e) {
e.printStackTrace();
return;
}catch(IOException i)
i.printStackTrace();
return;
}
}
protected void checkHTTP(String url) throws ProtocolException {
try {
URL target = new URL(url);
if(target==null || !target.getProtocol().toUpperCase().equals("HTTP") )
throw new ProtocolException("這不是HTTP協議");
this.target = target;
}catch(MalformedURLException m) {
throw new ProtocolException("協議格式錯誤");
}
}
/*
* 與Web服務器連接。若找不到Web服務器,InetAddress會引發UnknownHostException
* 異常。若Socket連接失敗,會引發IOException異常。
*/
protected void openServer(String host,int port) throws
UnknownHostException,IOException {
header.clear();
responseMessage=""; responseCode=-1;
try {
if(client!=null) closeServer();
if(byteStream != null) {
byteStream.close(); byteStream=null;
}
InetAddress address = InetAddress.getByName(host);
client = new Socket(address,port==-1?80:port);
sender = new BufferedOutputStream(client.getOutputStream());
receiver = new BufferedInputStream(client.getInputStream());
}catch(UnknownHostException u) {
throw u;
}catch(IOException i) {
throw i;
}
}
/* 關閉與Web服務器的連接 */
protected void closeServer() throws IOException {
if(client==null) return;
try {
client.close(); sender.close(); receiver.close();
}catch(IOException i) {
throw i;
}
client=null; sender=null; receiver=null;
}
protected String getURLFormat(URL target) {
String spec = "http://"+target.getHost();
if(target.getPort()!=-1)
spec+=":"+target.getPort();
return spec+=target.getFile();
}
/* 向Web服務器傳送數據 */
protected void sendMessage(String data) throws IOException{
sender.write(data.getBytes(),0,data.length());
sender.flush();
}
/* 接收來自Web服務器的數據 */
protected void receiveMessage() throws IOException{
byte data[] = new byte[1024];
int count=0;
int word=-1;
// 解析第一行
while( (word=receiver.read())!=-1 ) {
if(word=='\r'||word=='\n') {
word=receiver.read();
if(word=='\n') word=receiver.read();
break;
}
if(count == data.length) data = addCapacity(data);
data[count++]=(byte)word;
}
String message = new String(data,0,count);
int mark = message.indexOf(32);
serverVersion = message.substring(0,mark);
while( mark responseCode = Integer.parseInt(message.substring(mark+1,mark+=4));
responseMessage = message.substring(mark,message.length()).trim();
// 應答狀態碼和處理請讀者添加
switch(responseCode) {
case 400:
throw new IOException("錯誤請求");
case 404:
throw new FileNotFoundException( getURLFormat(target) );
case 503:
throw new IOException("服務器不可用" );
}
if(word==-1) throw new ProtocolException("信息接收異常終止");
int symbol=-1;
count=0;
// 解析元信息
while( word!='\r' && word!='\n' && word>-1) {
if(word=='\t') word=32;
if(count==data.length) data = addCapacity(data);
data[count++] = (byte)word;
parseLine: {
while( (symbol=receiver.read()) >-1 ) {
switch(symbol) {
case '\t':
symbol=32; break;
case '\r':
case '\n':
word = receiver.read();
if( symbol=='\r' && word=='\n') {
word=receiver.read();
if(word=='\r') word=receiver.read();
}
if( word=='\r' || word=='\n' || word>32) break parseLine;
symbol=32; break;
}
if(count==data.length) data = addCapacity(data);
data[count++] = (byte)symbol;
}
word=-1;
}
message = new String(data,0,count);
mark = message.indexOf(':');
String key = null;
if(mark>0) key = message.substring(0,mark);
mark++;
while( mark String value = message.substring(mark,message.length() );
header.put(key,value);
count=0;
}
// 獲得正文數據
while( (word=receiver.read())!=-1) {
if(count == data.length) data = addCapacity(data);
data[count++] = (byte)word;
}
if(count>0) byteStream = new ByteArrayInputStream(data,0,count);
data=null;
closeServer();
}
public String getResponseMessage() {
return responseMessage;
}
public int getResponseCode() {
return responseCode;
}
public String getServerVersion() {
return serverVersion;
}
public InputStream getInputStream() {
return byteStream;
}
public synchronized String getHeaderKey(int i) {
if(i>=header.size()) return null;
Enumeration enum = header.propertyNames();
String key = null;
for(int j=0; j<=i; j++)
key = (String)enum.nextElement();
return key;
}
public synchronized String getHeaderValue(int i) {
if(i>=header.size()) return null;
return header.getProperty(getHeaderKey(i));
}
public synchronized String getHeaderValue(String key) {
return header.getProperty(key);
}
protected String getBaseHeads() {
String inf = "User-Agent: myselfHttp/1.0\r\n"+
"Accept: www/source; text/html; image/gif; */*\r\n";
return inf;
}
private byte[] addCapacity(byte rece[]){
byte temp[] = new byte[rece.length+1024];
System.arraycopy(rece,0,temp,0,rece.length);
return temp;
}
}
注: 程序中只實現GET、HEAD、POST三種方法。其他幾種因不常使用,暫且忽略。
? ? ? ? ? ?| HTTP:超文本傳輸協議 |
| HTTP:Hypertext Transfer Protocol |
| 超文本傳輸協議(HTTP)是應用層協議,由于其簡捷、快速的方式,適用于分布式和合作式超媒體信息系統。自 1990 年起, HTTP 就已經被應用于 WWW 全球信息服務系統。 HTTP 允許使用自由答復的方法表明請求目的,它建立在統一資源識別器(URI)提供的參考原則下,作為一個地址(URL)或名字(URN),用以標志采用哪種方法,它用類似于網絡郵件和多用途網際郵件擴充協議(MIME)的格式傳遞消息。 HTTP 也可用作普通協議,實現用戶代理與連接其它 Internet 服務(如 SMTP 、 NNTP 、 FTP 、 GOPHER 及 WAIS )的代理服務器或網關之間的通信,允許基本的超媒體訪問各種應用提供的資源,同時簡化了用戶代理系統的實施。 HTTP 是一種請求 / 響應式的協議。一個客戶機與服務器建立連接后,發送一個請求給服務器,請求的格式是:統一資源標識符(URI)、協議版本號,后面是類似 MIME 的信息,包括請求修飾符、客戶機信息和可能的內容。服務器接到請求后,給予相應的響應信息,其格式是:一個狀態行包括信息的協議版本號、一個成功或錯誤的 代碼,后面也是類似 MIME 的信息,包括服務器信息、實體信息和可能的內容。 HTTP 的第一版本 HTTP/0.9 是一種簡單的用于網絡間原始數據傳輸的協議。而由 RFC 1945 定義的 HTTP/1.0 ,在原 HTTP/0.9 的基礎上,有了進一步的改進,允許消息以類 MIME 信息格式存在,包括請求 / 響應范式中的已傳輸數據和修飾符等方面的信息。但是, HTTP/1.0 沒有充分考慮到分層代理服務器、高速緩沖存儲器、持久連接需求或虛擬主機等方面的效能。相比之下, HTTP/1.1 要求更加嚴格以確保服務的可靠性。關于安全增強版的 HTTP (即S-HTTP),將在相關文件中再作介紹。 |
| 協議結構 |
| HTTP報文由從客戶機到服務器的請求和從服務器到客戶機的響應構成。?請求報文格式如下: |
| 請求行 | 通用信息頭 | 請求頭 | 實體頭 | 報文主體 |
| 請求行以方法字段開始,后面分別是 URL 字段和 HTTP 協議版本字段,并以 CRLF 結尾。SP 是分隔符。除了在最后的 CRLF 序列中 CF 和 LF 是必需的之外,其他都可以不要。有關通用信息頭,請求頭和實體頭方面的具體內容可以參照相關文件。 應報文格式如下: |
| 狀態行 | 通用信息頭 | 響應頭 | 實體頭 | 報文主體 |
| 狀態碼元由3位數字組成,表示請求是否被理解或被滿足。原因分析是對原文的狀態碼作簡短的描述,狀態碼用來支持自動操作,而原因分析用來供用戶使用。客戶機無需用來檢查或顯示語法。有關通用信息頭,響應頭和實體頭方面的具體內容可以參照相關文件。 |
| 相關協議 | WWW、FTP、STMP、NNTP、Gopher、WAIS、DNS、S-HTTP |
| 組織來源 | HTTP 定義在 IETF (http://www.ietf.org) 的 RFC 1945和2616中。 |
總結
以上是生活随笔為你收集整理的轻松认识HTTP协议的概念和工作原理的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 常规调幅系统matlab结果,matla
- 下一篇: 实验室设计如何搭配颜色色彩