Http協議詳解

(一)初識HTTP消息頭

但凡搞WEB開發的人都離不開HTTP(超文本傳輸協議),而要了解HTTP,除了HTML本身以外,還有一部分不可忽視的就是HTTP消息頭。
做過Socket編程的人都知道,當我們設計一個通信協議時,“消息頭/消息體”的分割方式是很常用的,消息頭告訴對方這個消息是幹什麼的,消息體告訴對方怎麼幹。HTTP傳輸的消息也是這樣規定的,每一個HTTP包都分爲HTTP頭和HTTP體兩部分,後者是可選的,而前者是必須的。每當我們打開一個網頁,在上面點擊右鍵,選擇“查看源文件”,這時看到的HTML代碼就是HTTP的消息體,那麼消息頭又在哪呢?IE瀏覽器不讓我們看到這部分,但我們可以通過截取數據包等方法看到它。
下面就來看一個簡單的例子:
首先製作一個非常簡單的網頁,它的內容只有一行:
<html><body>hello world</body></html>
把它放到WEB服務器上,比如IIS,然後用IE瀏覽器請求這個頁面(http://localhost:8080/simple.htm),當我們請求這個頁面時,瀏覽器實際做了以下四項工作:
1 解析我們輸入的地址,從中分解出協議名、主機名、端口、對象路徑等部分,對於我們的這個地址,解析得到的結果如下:
協議名:http
主機名:localhost
端口:8080
對象路徑:/simple.htm
2 把以上部分結合本機自己的信息,封裝成一個HTTP請求數據包
3 使用TCP協議連接到主機的指定端口(localhost, 8080),併發送已封裝好的數據包
4 等待服務器返回數據,並解析返回數據,最後顯示出來
由截取到的數據包我們不難發現瀏覽器生成的HTTP數據包的內容如下:
GET /simple.htm HTTP/1.1<CR>
Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/x-shockwave-flash, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */*<CR>
Accept-Language: zh-cn<CR>
Accept-Encoding: gzip, deflate<CR>
User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322; .NET CLR 2.0.50727)<CR>
Host: localhost:8080<CR>
Connection: Keep-Alive<CR>
<CR>
爲了顯示清楚我把所有的回車的地方都加上了“<CR>”,注意最後還有一個空行加一個回車,這個空行正是HTTP規定的消息頭和消息體的分界線,第一個空行以下的內容就是消息體,這個請求數據包是沒有消息體的。
消息的第一行“GET”表示我們所使用的HTTP動作,其他可能的還有“POST”等,GET的消息沒有消息體,而POST消息是有消息體的,消息體的內容就是要POST的數據。後面/simple.htm就是我們要請求的對象,之後HTTP1.1表示使用的是HTTP1.1協議。
第二行表示我們所用的瀏覽器能接受的Content-type,三四兩行則是語言和編碼信息,第五行顯示出本機的相關係信息,包括瀏覽器類型、操作系統信息等,很多網站可以顯示出你所使用的瀏覽器和操作系統版本,就是因爲可以從這裏獲取到這些信息。
第六行表示我們所請求的主機和端口,第七行表示使用Keep-Alive方式,即數據傳遞完並不立即關閉連接。
服務器接收到這樣的數據包以後會根據其內容做相應的處理,例如查找有沒有“/simple.htm”這個對象,如果有,根據服務器的設置來決定如何處理,如果是HTM,則不需要什麼複雜的處理,直接返回其內容即可。但在直接返回之前,還需要加上HTTP消息頭。
服務器發回的完整HTTP消息如下:
HTTP/1.1 200 OK<CR>
Server: Microsoft-IIS/5.1<CR>
X-Powered-By: ASP.NET<CR>
Date: Fri, 03 Mar 2006 06:34:03 GMT<CR>
Content-Type: text/html<CR>
Accept-Ranges: bytes<CR>
Last-Modified: Fri, 03 Mar 2006 06:33:18 GMT<CR>
ETag: "5ca4f75b8c3ec61:9ee"<CR>
Content-Length: 37<CR>
<CR>
<html><body>hello world</body></html>
同樣,我用“<CR>”來表示回車。可以看到,這個消息也是用空行切分成消息頭和消息體兩部分,消息體的部分正是我們前面寫好的HTML代碼。
消息頭第一行“HTTP/1.1”也是表示所使用的協議,後面的“200 OK”是HTTP返回代碼,200就表示操作成功,還有其他常見的如404表示對象未找到,500表示服務器錯誤,403表示不能瀏覽目錄等等。
第二行表示這個服務器使用的WEB服務器軟件,這裏是IIS 5.1。第三行是ASP.Net的一個附加提示,沒什麼實際用處。第四行是處理此請求的時間。第五行就是所返回的消息的content-type,瀏覽器會根據它來決定如何處理消息體裏面的內容,例如這裏是text/html,那麼瀏覽器就會啓用HTML解析器來處理它,如果是image/jpeg,那麼就會使用JPEG的解碼器來處理。
消息頭最後一行“Content-Length”表示消息體的長度,從空行以後的內容算起,以字節爲單位,瀏覽器接收到它所指定的字節數的內容以後就會認爲這個消息已經被完整接收了。
 
 
 
 
理解HTTP消息頭 (二)
常見的HTTP返回碼
上一篇文章裏我簡要的說了說HTTP消息頭的格式,注意到在服務器返回的HTTP消息頭裏有一個“HTTP/1.1 200 OK”,這裏的200是HTTP規定的返回代碼,表示請求已經被正常處理完成。瀏覽器通過這個返回代碼就可以知道服務器對所發請求的處理情況是什麼,每一種返回代碼都有自己的含義。這裏列舉幾種常見的返回碼。
1 403 Access Forbidden
如果我們試圖請求服務器上一個文件夾,而在WEB服務器上這個文件夾並沒有允許對這個文件夾列目錄的話,就會返回這個代碼。一個完整的403回覆可能是這樣的:(IIS5.1)
HTTP/1.1 403 Access Forbidden
Server: Microsoft-IIS/5.1
Date: Mon, 06 Mar 2006 08:57:39 GMT
Connection: close
Content-Type: text/html
Content-Length: 172
 
<html><head><title>Directory Listing Denied</title></head>
<body><h1>Directory Listing Denied</h1>This Virtual Directory does not allow contents to be listed.</body></html>
2 404 Object not found
當我們請求的對象在服務器上並不存在時,就會給出這個返回代碼,這可能也是最常見的錯誤代碼了。IIS給出的404消息內容很長,除了消息頭以外還有一個完整的說明“爲什麼會這樣”的網頁。APACHE服務器的404消息比較簡短,如下:
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>
也許你會問,無論是404還是200,都會在消息體內給出一個說明網頁,那麼對於客戶端來說二者有什麼區別呢?一個比較明顯的區別在於200是成功請求,瀏覽器會記錄下這個地址,以便下次再訪問時可以自動提示該地址,而404是失敗請求,瀏覽器只會顯示出返回的頁面內容,並不會記錄此地址,要再次訪問時還需要輸入完整的地址。
3 401 Access Denied
當WEB服務器不允許匿名訪問,而我們又沒有提供正確的用戶名/密碼時,服務器就會給出這個返回代碼。在IIS中,設置IIS的安全屬性爲不允許匿名訪問(如下圖),此時直接訪問的話就會得到以下返回結果:

HTTP/1.1 401 Access Denied
Server: Microsoft-IIS/5.1
Date: Mon, 06 Mar 2006 09:15:55 GMT
WWW-Authenticate: Negotiate
WWW-Authenticate: NTLM
Connection: close
Content-Length: 3964
Content-Type: text/html
 
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<html dir=ltr>
……
此時瀏覽器上給出的提示如下圖,讓我們輸入用戶名和密碼:

因返回信息中消息體較長,只取前面兩行內容。注意,如果是用localhost來訪問本機的IIS,因IE可以直接取得當前用戶的身份,它會和服務器間直接進行協商,所以不會看到401提示。
當我們在輸入了用戶名和密碼以後,服務器與客戶端會再進行兩次對話。首先客戶端向服務器索取一個公鑰,服務器端會返回一個公鑰,二者都用BASE64編碼,相應的消息如下(編碼部分已經做了處理):
GET / HTTP/1.1
Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/x-shockwave-flash, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */*
Accept-Language: zh-cn
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322; .NET CLR 2.0.50727)
Host: 192.168.0.55:8080
Connection: Keep-Alive
Authorization: Negotiate ABCDEFG……
 
HTTP/1.1 401 Access Denied
Server: Microsoft-IIS/5.1
Date: Mon, 06 Mar 2006 09:20:53 GMT
WWW-Authenticate: Negotiate HIJKLMN……
Content-Length: 3715
Content-Type: text/html
 
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<html dir=ltr>
……
客戶端拿到公鑰之後使用公鑰對用戶名和密碼進行加密碼,然後把加密以後的結果重新發給服務器:
GET / HTTP/1.1
Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/x-shockwave-flash, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */*
Accept-Language: zh-cn
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322; .NET CLR 2.0.50727)
Host: 192.168.0.55:8080
Connection: Keep-Alive
Authorization: Negotiate OPQRST……
這樣,如果驗證通過,服務器端就會把請求的內容發送過來了,也就是說禁止匿名訪問的網站會經過三次請求纔可以看到頁面。但因爲客戶端瀏覽器已經緩存了公鑰,用同一個瀏覽器窗口再次請求這個網站上的其它頁面時就可以直接發送驗證信息,從而一次交互就可以完成了。
4 302 Object Moved
用過ASP的人都知道ASP中頁面重定向至少有Redirect和Transfer兩種方法。二的區別在於Redirect是客戶端重定向,而Transfer是服務器端重定向,那麼它們具體是如何通過HTTP消息頭實現的呢?
先來看一下Transfer的例子:
例如ASP文件1.asp只有一行
<% Server.Transfer "1.htm" %>
HTML文件1.htm也只有一行:
<p>this is 1.htm</p>
如果我們從瀏覽器裏請求1.asp,發送的請求是:
GET /1.asp HTTP/1.1
Accept: */*
Accept-Language: zh-cn
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322; .NET CLR 2.0.50727)
Host: localhost:8080
Connection: Keep-Alive
Cookie: ASPSESSIONIDACCTRTTT=PKKDJOPBAKMAMBNANIPIFDAP
注意請求的文件確實是1.asp,而得到的迴應則是:
HTTP/1.1 200 OK
Server: Microsoft-IIS/5.1
Date: Mon, 06 Mar 2006 12:52:44 GMT
X-Powered-By: ASP.NET
Content-Length: 20
Content-Type: text/html
Cache-control: private
 
<p>this is 1.htm</p>
不難看出,通過Server.Transfer語句服務器端已經做了頁面重定向,而客戶端對此一無所知,表面上看上去得到的就是1.asp的結果。
如果把1.asp的內容改爲:
<% Response.Redirect "1.htm" %>
再次請求1.asp,發送的請求沒有變化,得到的迴應卻變成了:
HTTP/1.1 302 Object moved
Server: Microsoft-IIS/5.1
Date: Mon, 06 Mar 2006 12:55:57 GMT
X-Powered-By: ASP.NET
Location: 1.htm
Content-Length: 121
Content-Type: text/html
Cache-control: private
 
<head><title>Object moved</title></head>
<body><h1>Object Moved</h1>This object may be found <a HREF="">here</a>.</body>
注意HTTP的返回代碼由200變成了302,表示這是一個重定向消息,客戶端需要根據消息頭中Location字段的值重新發送請求,於是就有了下面一組對話:
GET /1.htm HTTP/1.1
Accept: */*
Accept-Language: zh-cn
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322; .NET CLR 2.0.50727)
Host: localhost:8080
Connection: Keep-Alive
If-Modified-Since: Thu, 02 Mar 2006 06:50:13 GMT
If-None-Match: "b224758ec53dc61:9f0"
Cookie: ASPSESSIONIDACCTRTTT=PKKDJOPBAKMAMBNANIPIFDAP

HTTP/1.1 200 OK
Server: Microsoft-IIS/5.1
X-Powered-By: ASP.NET
Date: Mon, 06 Mar 2006 12:55:57 GMT
Content-Type: text/html
Accept-Ranges: bytes
Last-Modified: Mon, 06 Mar 2006 12:52:32 GMT
ETag: "76d85bd51c41c61:9f0"
Content-Length: 20
 
<p>this is 1.htm</p>
很明顯,兩種重定向方式雖然看上去結果很像,但在實現原理上有很大的不同。
5 500 Internal Server Error
500號錯誤發生在服務器程序有錯誤的時候,例如,ASP程序爲
<% if %>
顯然這個程序並不完整,於是得到的結果爲:
HTTP/1.1 500 Internal Server Error
Server: Microsoft-IIS/5.1
Date: Mon, 06 Mar 2006 12:58:55 GMT
X-Powered-By: ASP.NET
Content-Length: 4301
Content-Type: text/html
Expires: Mon, 06 Mar 2006 12:58:55 GMT
Set-Cookie: ASPSESSIONIDACCTRTTT=ALKDJOPBPPKNPCNOEPCNOOPD; path=/
Cache-control: private

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<html dir=ltr>
……
服務器發送了500號錯誤,並且後面通過HTML的方式說明了錯誤的原因。
 
 
理解HTTP消息頭 (三)

(三) 客戶端發送的內容
這一次主要來觀察HTTP消息頭中客戶端的請求,從中找到一些有意思的內容。
 
1 HTTP_REFERER
寫兩個簡單的網頁:
a.htm:
<a href=b.htm>to page b</a>
b.htm:
haha
內容很簡單,就是網頁A中有一個到B的鏈接。把它們放到IIS上,並訪問網頁A,從中再點擊到B的鏈接,於是看到了B頁的“haha”。那麼這兩次請求有什麼不同嗎?觀察它們所發送的HTTP消息頭,最明顯的區別就是訪問B頁時比訪問A頁時多了一行:
Referer: http://localhost/a.htm
這一行就表示,用戶要訪問的B頁是從A頁鏈接過來的。
服務器端要想取得這個值也是很容易的,以ASP爲例,只需要寫一句
<% =Request.ServerVariables("HTTP_REFERER") %>
就可以了。
一些網站通過HTTP_REFERER來做安全驗證,判斷用戶是不是從允許的頁面鏈接來的,而不是直接從瀏覽器上打URL或從其他頁面鏈接過來,這樣可以從一定程度上防止網頁被做非法使用。但從上述原理來看,想要騙過服務器也並不困難,只要手工構造輸入的HTTP消息頭就可以了,其他常用的手段還有通過HOSTS文件僞造域名等。
除了超鏈接以外,還有其他幾種方式會導致HTTP_REFERER信息被髮送,如:
內聯框架:<iframe src=b.asp></iframe>
框架集:<frameset><frame src=b.asp></frameset>
表單提交:<form action=b.asp><input type=submit></form>
SCRIPT引用:<script src=b.asp></script>
CSS引用:<link rel=stylesheet type=text/css href=b.asp>
XML數據島:<xml src=b.asp></xml>
而以下形式不會發送HTTP_REFERER:
script轉向:<script>location.href="b.asp"</script>
script開新窗口:<script>window.open("b.asp");</script>
META轉向:<meta http-equiv="refresh" content="0;URL=b.asp">
引入圖片:<img src=b.asp>
 
2 COOKIE
COOKIE是大家都非常熟悉的了,通過它可以在客戶端保存用戶狀態,即使用戶關閉瀏覽器也能繼續保存。那麼客戶端與服務器端是如何交換COOKIE信息的呢?沒錯,也是通過HTTP消息頭。
首先寫一個簡單的ASP網頁:
<%
Dim i
i =  Request.Cookies("key")
Response.Write i
Response.Cookies("key") = "haha"
Response.Cookies("key").Expires = #2007-1-1#
%>
第一次訪問此網頁時,屏幕上一片白,第二次訪問時,則會顯示出“haha”。通過閱讀程序不難發現,屏幕上顯示的內容實際上是COOKIE的內容,而第一次訪問時還沒有設置COOKIE的值,所以不會有顯示,第二次顯示的是第一次設置的值。那麼對應的HTTP消息頭應該是什麼樣的呢?
第一次請求時沒什麼不同,略過
第一次返回時消息內容多了下面這一行:
Set-Cookie: key=haha; expires=Sun, 31-Dec-2006 16:00:00 GMT; path=/
很明顯,key=haha表示鍵名爲“key”的COOKIE的值爲“haha”,後面是這則COOKIE的過期時間,因爲我用的中文操作系統的時區是東八區,2007年1月1日0點對應的GMT時間就是2006年12月31日16點。
第二次再訪問此網頁時,發送的內容多瞭如下一行:
Cookie: key=haha
它的內容就是剛纔設的COOKIE的內容。可見,客戶端在從服務器端得到COOKIE值以後就保存在硬盤上,再次訪問時就會把它發送到服務器。發送時並沒有發送過期時間,因爲服務器對過期時間並不關心,當COOKIE過期後瀏覽器就不會再發送它了。
如果使用IE6.0瀏覽器並且禁用COOKIE功能,可以發現服務器端的set-cookie還是有的,但客戶端並不會接受它,也不會發送它。有些網站,特別是在線投票網站通過記錄COOKIE防止用戶重複投票,破解很簡單,只要用IE6瀏覽器並禁用COOKIE就可以了。也有的網站通過COOKIE值爲某值來判斷用戶是否合法,這種判斷也非常容易通過手工構造HTTP消息頭來欺騙,當然用HOSTS的方式也是可以欺騙的。
 
3 SESSION
HTTP協議本身是無狀態的,服務器和客戶端都不保證用戶訪問期間連接會一直保持,事實上保持連接是HTTP1.1纔有的新內容,當客戶端發送的消息頭中有“Connection: Keep-Alive”時表示客戶端瀏覽器支持保持連接的工作方式,但這個連接也會在一段時間沒有請求後自動斷開,以節省服務器資源。爲了在服務器端維持用戶狀態,SESSION就被髮明出來了,現在各主流的動態網頁製做工具都支持SESSION,但支持的方式不完全相同,以下皆以ASP爲例。
當用戶請求一個ASP網頁時,在返回的HTTP消息頭中會有一行:
Set-Cookie: ASPSESSIONIDCSQCRTBS=KOIPGIMBCOCBFMOBENDCAKDP; path=/
服務器通過COOKIE的方式告訴客戶端你的SESSIONID是多少,在這裏是“KOIPGIMBCOCBFMOBENDCAKDP”,並且服務器上保留了和此SESSIONID相關的數據,當同一用戶再次發送請求時,還會把這個COOKIE再發送回去,服務器端根據此ID找到此用戶的數據,也就實現了服務器端用戶狀態的保存。所以我們用ASP編程時可以使用“session("name")=user”這樣的方式保存用戶信息。注意此COOKIE內容裏並沒有過期時間,這表示這是一個當關閉瀏覽器時立即過期的COOKIE,它不會被保存到硬盤上。這種工作方式比單純用COOKIE的方式要安全很多,因爲在客戶端並沒有什麼能讓我們修改和欺騙的值,唯一的信息就是SESSIONID,而這個ID在瀏覽器關閉時會立即失效,除非別人能在你瀏覽網站期間或關閉瀏覽器後很短時間內知道此ID的值,才能做一些欺騙活動。因爲服務器端判斷SESSION過期的方式並不是斷開連接或關閉瀏覽器,而是通過用戶手工結束SESSION或等待超時,當用戶關閉瀏覽器後的一段時間裏SESSION還沒有超時,所以這時如果知道了剛纔的SESSIONID,還是可以欺騙的。因此最安全的辦法還是在離開網站之前手工結束SESSION,很多網站都提供“Logout”功能,它會通過設置SESSION中的值爲已退出狀態或讓SESSION立即過期從而起到安全的目的。
SESSION和COOKIE的方式各有優缺點。SESSION的優點是比較安全,不容易被欺騙,缺點是過期時間短,如果用過在超過過期時間裏沒有向服務器發送任何信息,就會被認爲超過過期了;COOKIE則相反,根據服務器端設置的超時時間,可以長時間保留信息,即使關機再開機也可能保留狀態,而安全性自然大打折扣。很多網站都提供兩種驗證方式相結合,如果用戶臨時用這臺電腦訪問此訪問則需要輸入用戶名和密碼,不保存COOKIE;如果用戶使用的是自己的個人電腦,則可以讓網站在自己硬盤上保留COOKIE,以後訪問時就不需要重新輸入用戶名和密碼了。
 
4 POST
瀏覽器訪問服務器常用的方式有GET和POST兩種,GET方式只發送HTTP消息頭,沒有消息體,也就是除了要GET的基本信息之外不向服務器提供其他信息,網頁表單(FROM)的默認提交方式就是用GET方式,它會把所有向服務器提交的信息都作爲URL後面的參數,如a.asp?a=1&b=2這樣的方式。而當要提交的數據量很大,或者所提交內容不希望別人直接看到時,應該使用POST方式。POST方式提交的數據是作爲HTTP消息體存在的,例如,寫一個網頁表單:
<form method=post>
<input type=text name=text1>
<input type=submit>
</form>
訪問此網頁,並在表單中填入一個“haha”,然後提交,可以看到此次提交所發送的信息如下:
POST /form.asp HTTP/1.1
Accept: */*
Referer: http://localhost:8080/form.asp
Accept-Language: zh-cn
Content-Type: application/x-www-form-urlencoded
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0; InfoPath.1; .NET CLR 1.1.4322; .NET CLR 2.0.50727)
Host: localhost:8080
Content-Length: 10
Connection: Keep-Alive
Cache-Control: no-cache
Cookie: key=haha; ASPSESSIONIDCSQCRTBS=LOIPGIMBLMNOGCOBOMPJBOKP
text1=haha
前面關鍵字從“GET”變爲了“POST”,Content-Type變成了“application/x-www-form-urlencoded”,後面內容並無大變化,只是多了一行:Content-Length: 10,表示提交的內容的長度。空行後面是消息體,內容就是表單中所填的內容。注意此時發送的內容只是“Name=Value”的形式,表單上其他的信息不會被髮送,所以想直接從服務器端取得list box中所有的list item是辦不到的,除非在提交前用一段script把所有的item內容都連在一起放到一個隱含表單域中。
如果是用表單上傳文件,情況就要複雜一些了,首先是表單聲明中要加上一句話:enctype='multipart/form-data',表示這個表單將提交多段數據,並用HTML:input type=file來聲明一個文件提交域。
表單內容如下:
<form method=post enctype='multipart/form-data'>
<input type=text name=text1>
<input type=file name=file1>
<input type=submit>
</form>
我們爲text1輸入文字:hehe,爲file1選擇文件haha.txt,其內容爲“ABCDEFG”,然後提交此表單。提交的完全信息爲:
POST /form.asp HTTP/1.1
Accept: */*
Referer: http://localhost:8080/form.asp
Accept-Language: zh-cn
Content-Type: multipart/form-data; boundary=---------------------------7d62bf2f9066c
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0; InfoPath.1; .NET CLR 1.1.4322; .NET CLR 2.0.50727)
Host: localhost:8080
Content-Length: 337
Connection: Keep-Alive
Cache-Control: no-cache
Cookie: key=haha; ASPSESSIONIDCSQCRTBS=LOIPGIMBLMNOGCOBOMPJBOKP
-----------------------------7d62bf2f9066c
Content-Disposition: form-data; name="text1"
hehe
-----------------------------7d62bf2f9066c
Content-Disposition: form-data; name="file1"; filename="H:/Documents and Settings/Administrator/桌面/haha.txt"
Content-Type: text/plain
ABCDEFG
-----------------------------7d62bf2f9066c--
 
顯然這個提交的信息要比前述的複雜很多。Content-Type變成了“multipart/form-data”,後面還多了一個boundary,此值是爲了區分POST的內容的區段用的,只要在內容中遇到了此值,就表示下面要開始一個新的區段了,每個區段的內容相對獨立。如果遇到的是此值後面連着兩個減號,則表示全部內容到此結束。每個段也分爲段頭和段體兩部分,用空行隔開,每段都有自己的類型和相關信息。如第一區段是text1的值,它的名稱是“text1”,值爲“hehe”。第二段是文件內容,段首裏表明了此文件域的名稱“file1”和此文件在用戶磁盤上的位置,後面就是文件的內容。
如果我們想要自己寫一個上傳文件組件來接收HTML表單傳送的文件數據,那麼最核心的任務就是解析此數據包,從中取得需要的信息。
 
 
 
理解HTTP消息頭 (四)
服務器返回的消息
服務器返回的HTTP消息也分爲消息頭和消息體兩部分。前面連載的第二篇裏已經介紹了返回消息中常見返回代碼的含義。對於非正常的返回代碼的處理比較簡單,只要照着要求去做就好了,而對於正常的返回代碼(200),其處理方式就多種多樣了。

1 Content-Type
Content-Type是返回消息中非常重要的內容,它標識出這個返回內容的類型,其值爲“主類型/子類型”的格式,例如最常見的就是text/html,它的意思是說返回的內容是文本類型,這個文本又是HTML格式的。原則上瀏覽器會根據Content-Type來決定如何顯示返回的消息體內容。常見的內容類型有:
text/html HTML文本
image/jpeg JPG圖片
image/gif GIF圖片
application/xml XML文檔
audio/x-mpegurl MP3文件列表,如果安裝了Winamp,則可以直接把它當面M3U文件來打開
更多的內容類型可以在註冊表“HKCR/MIME/Database/Content Type”下看到
對於IE6瀏覽器來說,如果Content-Type中的類型和實際的消息體類型不一致,那麼它會根據內容中的類型來分析實際應該是什麼類型,對於JPG、GIF等常用圖片格式都可以正確的識別出來,而不管Content-Type中寫的是什麼。
如果Content-Type中指定的是瀏覽器可以直接打開的類型,那麼瀏覽器就會直接打開其內容顯示出來,如果是被關聯到其它應用程序的類型,這時就要查找註冊表中關於這種類型的註冊情況,如果是允許直接打開而不需要詢問的,就會直接調出這個關聯的應用程序來打開這個文件,但如果是不允許直接打開的,就會詢問是否打開。對於沒有關聯到任何應用程序的類型,IE瀏覽器不知道它該如何打開,此時IE6就會把它當成XML來嘗試打開。
2 Content-Disposition
如果用AddHeader的方法在HTTP消息頭中加入Content-Disposition段,並指定其值爲“attachment”,那麼無論這個文件是何類型,瀏覽器都會提示我們下載此文件,因爲此時它認爲後面的消息體是一個“附件”,不需要由瀏覽器來處理了。例如,在ASP.Net中寫入如下語句:
Response.AddHeader("Content-Disposition: attachment");
請求此頁面是得到的結果如:
HTTP/1.1 200 OK
Server: Microsoft-IIS/5.1
Date: Thu, 23 Mar 2006 07:54:53 GMT
Content-Disposition: attachment
Cache-Control: private
Content-Type: text/html; charset=utf-8
……
也就是說,通過AddHeader函數可以爲HTTP消息頭加入我們自定義的內容。使用這種方法可以強制讓瀏覽器提示下載文件,即使這個文件是我們已知的類型,基於是HTML網頁。如果想要讓用戶下載時提示一個默認的文件名,只需要在前面一句話後加上“filename=文件名”即可。例如:
Response.AddHeader("Content-Disposition: attachment; filename=mypage.htm");
3 Content-Type與Content-Disposition
如果把Content-Type和Content-Disposition結合在一起使用會怎麼樣呢?
打開一個網頁時,瀏覽器會首先看是否有Content-Disposition: attachment這一項,如果有,無論Content-Type的值是什麼,都會提示文件下載。
如果指定了filename,就會提示默認的文件名爲此文件名。注意到在IE6中除了“保存”按扭外還有“打開”按扭,此時打開文件的類型是由在filename中指定的文件擴展名決定的,例如讓filename=mypic.jpg,瀏覽器就會查找默認的圖片查看器來打開此文件。
如果沒有指定filename,那麼瀏覽器就根據Content-Type中的類型來決定文件的類型,例如Content-Type類型爲image/gif,那麼就會去查找默認的看GIF圖片的工具,並且設置此文件的名字爲所請求的網頁的主名(不帶擴展名)加上對應於此文件類弄擴展名,例如請求的mypage.aspx,就會自動變成mypage.gif。如果並沒有指定Content-Type值,那麼就默認它爲“text/html”,並且保存的文件名就是所請求的網頁文件名。
但如果沒有指定Content-Disposition,那麼就和前面關於Content-Type中所討論的情況是一樣的了。

4 Cache
返回消息中的Cache用於指定網頁緩存。我們經常可以看到這樣的情況,打開一個網頁時速度不快,但再次打開時就會快很多,原因是瀏覽器已經對此頁面進行了緩存,那麼在同一瀏覽器窗口中再次打開此頁時不會重新從服務器端獲取。網頁的緩存是由HTTP消息頭中的“Cache-control”來控制的,常見的取值有private、no-cache、max-age、must-revalidate等,默認爲private。其作用根據不同的重新瀏覽方式分爲以下幾種情況:
(1) 打開新窗口
如果指定cache-control的值爲private、no-cache、must-revalidate,那麼打開新窗口訪問時都會重新訪問服務器。而如果指定了max-age值,那麼在此值內的時間裏就不會重新訪問服務器,例如:
Cache-control: max-age=5
表示當訪問此網頁後的5秒內再次訪問不會去服務器
(2) 在地址欄回車
如果值爲private或must-revalidate(和網上說的不一樣),則只有第一次訪問時會訪問服務器,以後就不再訪問。如果值爲no-cache,那麼每次都會訪問。如果值爲max-age,則在過期之前不會重複訪問。
(3) 按後退按扭
如果值爲private、must-revalidate、max-age,則不會重訪問,而如果爲no-cache,則每次都重複訪問
(4) 按刷新按扭
無論爲何值,都會重複訪問

當指定Cache-control值爲“no-cache”時,訪問此頁面不會在Internet臨時文章夾留下頁面備份。
另外,通過指定“Expires”值也會影響到緩存。例如,指定Expires值爲一個早已過去的時間,那麼訪問此網時若重複在地址欄按回車,那麼每次都會重複訪問:
Expires: Fri, 31 Dec 1999 16:00:00 GMT

在ASP中,可以通過Response對象的Expires、ExpiresAbsolute屬性控制Expires值;通過Response對象的CacheControl屬性控制Cache-control的值,例如:
Response.ExpiresAbsolute = #2000-1-1# ' 指定絕對的過期時間,這個時間用的是服務器當地時間,會被自動轉換爲GMT時間
Response.Expires = 20  ' 指定相對的過期時間,以分鐘爲單位,表示從當前時間起過多少分鐘過期。
Response.CacheControl = "no-cache" 
Expires值是可以通過在Internet臨時文件夾中查看臨時文件的屬性看到的,如:


HTTP請求:GET與POST方法的區別

 HTTP 定義了與服務器交互的不同方法,最基本的方法是 GET 和 POST。事實上 GET 適用於多數請求,而保留 POST 僅用於更新站點。根據 HTTP 規範,GET 用於信息獲取,而且應該是 安全的和 冪等的。所謂安全的意味着該操作用於獲取信息而非修改信息。換句話說,GET 請求一般不應產生副作用。冪等的意味着對同一 URL 的多個請求應該返回同樣的結果。完整的定義並不像看起來那樣嚴格。從根本上講,其目標是當用戶打開一個鏈接時,她可以確信從自身的角度來看沒有改變資源。比如,新聞站點的頭版不斷更新。雖然第二次請求會返回不同的一批新聞,該操作仍然被認爲是安全的和冪等的,因爲它總是返回當前的新聞。反之亦然。POST 請求就不那麼輕鬆了。POST 表示可能改變服務器上的資源的請求。仍然以新聞站點爲例,讀者對文章的註解應該通過 POST 請求實現,因爲在註解提交之後站點已經不同了(比方說文章下面出現一條註解);
 
在FORM提交的時候,如果不指定Method,則默認爲GET請求,Form中提交的數據將會附加在url之後,以?分開與url分開。字母數字字符原樣發送,但空格轉換爲“+“號,其它符號轉換爲%XX,其中XX爲該符號以16進製表示的ASCII(或ISO Latin-1)值。GET請求請提交的數據放置在HTTP請求協議頭中,而POST提交的數據則放在實體數據中;

GET方式提交的數據最多只能有1024字節,而POST則沒有此限制。
HTTP 狀態碼
200 - 服務器成功返回網頁 
404 - 請求的網頁不存在 
503 - 服務器超時 
以下是 HTTP 狀態碼的完整列表。您也可以訪問 HTTP 狀態碼上的 W3C 頁以瞭解更多信息。

1xx 狀態碼

表示臨時響應並需要請求者繼續執行操作的狀態碼。

100(繼續) 請求者應當繼續提出請求。服務器返回此代碼表示已收到請求的第一部分,正在等待其餘部分。 
101(切換協議) 請求者已要求服務器切換協議,服務器已確認並準備切換。

2xx 狀態碼

表示成功處理了請求的狀態碼。

200(成功) 服務器已成功處理了請求。通常,這表示服務器提供了請求的網頁。如果針對您的 robots.txt 文件顯示此狀態碼,則表示 Googlebot 已成功檢索到該文件。 
201(已創建) 請求成功並且服務器創建了新的資源。 
202(已接受) 服務器已接受請求,但尚未處理。 
203(非授權信息) 服務器已成功處理了請求,但返回的信息可能來自另一來源。 
204(無內容) 服務器成功處理了請求,但沒有返回任何內容。 
205(重置內容) 服務器成功處理了請求,但沒有返回任何內容。與 204 響應不同,此響應要求請求者重置文檔視圖(例如,清除表單內容以輸入新內容)。 
206(部分內容) 服務器成功處理了部分 GET 請求。

3xx 狀態碼

要完成請求,需要進一步操作。通常,這些狀態碼用來重定向。建議您在每次請求中使用重定向不要超過 5 次。您可以使用網站管理員工具查看一下 Googlebot 在抓取重定向網頁時是否遇到問題。診斷下的網絡抓取頁列出了由於重定向錯誤導致 Googlebot 無法抓取的網址。

300(多種選擇) 針對請求,服務器可執行多種操作。服務器可根據請求者 (user-agent) 選擇一項操作,或提供操作列表供請求者選擇。 
301(永久移動) 請求的網頁已永久移動到新位置。服務器返回此響應(對 GET 或 HEAD 請求的響應)時,會自動將請求者轉到新位置。您應使用此代碼告訴 Googlebot 某個網頁或網站已永久移動到新位置。 
302(臨時移動) 服務器目前從不同位置的網頁響應請求,但申請人應當繼續使用原有位置來響應以後的請求。此代碼與響應 GET 和 HEAD 請求的 301 代碼類似,會自動將請求者轉到不同的位置,但不應使用此代碼來告訴 Googlebot 頁面或網站已經移動,因爲 Googlebot 要繼續抓取原來的位置並編制索引。 
303(查看其他位置) 請求者應當對不同的位置使用單獨的 GET 請求來檢索響應時,服務器返回此代碼。對於除 HEAD 之外的所有請求,服務器會自動轉到其他位置。 
304(未修改) 自從上次請求後,請求的網頁未修改過。服務器返回此響應時,不會返回網頁內容。

如果網頁自請求者上次請求後再也沒有更改過,您應當將服務器配置爲返回此響應(稱爲 If-Modified-Since HTTP 標頭)。由於服務器可以告訴 Googlebot 自從上次抓取後網頁沒有變更,因此可節省帶寬和開銷。
 
305(使用代理) 請求者只能使用代理訪問請求的網頁。如果服務器返回此響應,還表示請求者應當使用代理。 
307(臨時重定向) 服務器目前從不同位置的網頁響應請求,但請求者應當繼續使用原有位置來響應以後的請求。此代碼與響應 GET 和 HEAD 請求的 <a href=answer.py?answer=>301</a> 代碼類似,會自動將請求者轉到不同的位置,但您不應使用此代碼來告訴 Googlebot 某個網頁或網站已經移動,因爲 Googlebot 會繼續抓取原有位置並編制索引。

4xx 狀態碼

這些狀態碼錶示請求可能出錯,這妨礙了服務器的處理。

400(錯誤請求) 服務器不理解請求的語法。 
401(身份驗證錯誤) 此頁要求授權。您可能不希望將此網頁納入索引。如果您的 Sitemap 中列出該網頁,您可以將其刪除。但如果您將其保留在您的 Sitemap 中,我們就不會抓取或索引該網頁(儘管該網頁將繼續保持錯誤狀態在此處列出)。如果我們將其作爲搜索抓取的一部分抓取,您可以在我們的網站管理員信息中查閱其原因。 
403(禁止) 服務器拒絕請求。如果您在 Googlebot 嘗試抓取您網站上的有效網頁時看到此狀態碼(可以在 Google 網站管理員工具<strong>診斷</strong>下的<strong>網絡抓取< /strong>頁面上看到此信息),可能是您的服務器或主機拒絕 Googlebot 訪問。 
404(未找到) 服務器找不到請求的網頁。例如,對於服務器上不存在的網頁經常會返回此代碼。

如果您的網站上沒有 robots.txt 文件,而您在 Google 網站管理員工具"診斷"標籤的 robots.txt 頁上看到此狀態碼,那麼這是正確的狀態碼。但是,如果您有 robots.txt 文件而又看到此狀態碼,則說明您的 robots.txt 文件可能命名錯誤或位於錯誤的位置(該文件應當位於頂級域,名爲 robots.txt)。

如果對於 Googlebot 嘗試抓取的網址看到此狀態碼(在"診斷"標籤的 HTTP 錯誤頁面上),則表示 Googlebot 追蹤的可能是另一個頁面的無效鏈接(是舊鏈接或輸入有誤的鏈接)。
 
405(方法禁用) 禁用請求中指定的方法。
 
406(不接受) 無法使用請求的內容特性響應請求的網頁。 
407(需要代理授權) 此狀態碼與 401 類似,但指定請求者必須授權使用代理。如果服務器返回此響應,還表示請求者應當使用代理。 
408(請求超時) 服務器等候請求時發生超時。 
409(衝突) 服務器在完成請求時發生衝突。服務器必須在響應中包含有關衝突的信息。服務器在響應與前一個請求相沖突的 PUT 請求時可能會返回此代碼,以及兩個請求的差異列表。 
410(已刪除) 請求的資源永久刪除後,服務器返回此響應。該代碼與 404(未找到)代碼相似,但在資源以前存在而現在不存在的情況下,有時會用來替代 404 代碼。如果資源已永久刪除,您應當使用 301 指定資源的新位置。 
411(需要有效長度) 服務器不接受不含有效內容長度標頭字段的請求。 
412(未滿足前提條件) 服務器未滿足請求者在請求中設置的其中一個前提條件。 
413(請求實體過大) 服務器無法處理請求,因爲請求實體過大,超出服務器的處理能力。 
414(請求的 URI 過長) 請求的 URI(通常爲網址)過長,服務器無法處理。 
415(不支持的媒體類型) 請求的格式不受請求頁面的支持。 
416(請求範圍不符合要求) 如果頁面無法提供請求的範圍,則服務器會返回此狀態碼。 
417(未滿足期望值) 服務器未滿足"期望"請求標頭字段的要求。

5xx 狀態碼

這些狀態碼錶示服務器在處理請求時發生內部錯誤。這些錯誤可能是服務器本身的錯誤,而不是請求出錯。

500(服務器內部錯誤) 服務器遇到錯誤,無法完成請求。 
501(尚未實施) 服務器不具備完成請求的功能。例如,服務器無法識別請求方法時則會返回此代碼。 
502(錯誤網關) 服務器作爲網關或代理,從上游服務器收到無效響應。 
503(服務不可用) 服務器目前無法使用(由於超載或停機維護)。通常,這只是暫時狀態。 
504(網關超時) 服務器作爲網關或代理,但是沒有及時從上游服務器收到請求。 
505(HTTP 版本不受支持) 服務器不支持請求中所用的 HTTP 協議版本。

發佈了27 篇原創文章 · 獲贊 5 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章