什么是HTTP Headers
HTTP是“Hypertext Transfer Protocol”的所寫,整個萬維網都在使用這種協議,幾乎你在瀏覽器里看到的大部分內容都是通過http協議來傳輸的,比如這篇文章。
HTTP Headers是HTTP請求和相應的核心,它承載了關于客戶端瀏覽器,請求頁面,服務器等相關的信息。
示例
當你在瀏覽器地址欄里鍵入一個url,你的瀏覽器將會類似如下的http請求:
GET /tutorials/other/top-20-mysql-best-practices/ HTTP/1.1Host: net.tutsplus.comUser-Agent: Mozilla/5.0 (Windows;U; Windows NT 6.1; en-US; rv:1.9.1.5) Gecko/20091102 Firefox/3.5.5 (.NET CLR 3.5.30729)Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8Accept-Language: en-us,en;q=0.5Accept-Encoding: gzip,deflateAccept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7Keep-Alive: 300Connection: keep-aliveCookie: PHPSESSID=r2t5uvjq435r4q7ib3vtdjq120Pragma: no-cacheCache-Control: no-cache
第一行被稱為“Request Line” 它描述的是這個請求的基本信息,剩下的就是HTTP headers了。
請求完成之后,你的瀏覽器可能會收到如下的HTTP響應:
HTTP/1.x 200 OKTransfer-Encoding: chunkedDate: Sat, 28 Nov 2009 04:36:25 GMTServer: LiteSpeedConnection: closeX-Powered-By: W3 Total Cache/0.8Pragma: publicExpires: Sat, 28 Nov 2009 05:36:25 GMTEtag: "pub1259380237;gz"Cache-Control: max-age=3600, publicContent-Type: text/html; charset=UTF-8Last-Modified: Sat, 28 Nov 2009 03:50:37 GMTX-Pingback: http://net.tutsplus.com/xmlrpc.phpContent-Encoding: gzipVary: Accept-Encoding, Cookie, User-Agent
第一行呢被稱為“Status Line”,它之后就是http headers,空行完了就開始輸出內容了(在這個案例中是一些html輸出)。
但你查看頁面源代碼卻不能看到HTTP headers,雖然它們同你能看到的東西一起被傳送至瀏覽器了。
這個HTTP請求也發出了一些其它資源的接收請求,例如圖片,css文件,js文件等等。
下面我們來看看細節。
怎樣才能看到HTTP Headers
下面這些FireFox擴展能夠幫助你分析HTTP headers:
1.Firebug
3. 在PHP中:
getallheaders()用來獲取請求頭部. 你也可以使用 $_SERVER 數組.
headers_list()用來獲取響應頭部.
文章下面將會看到一些使用php示范的例子。
HTTP Request 的結構
被稱作“first line”的第一行包含三個部分:
“method” 表明這是何種類型的請求. 最常見的請求類型有 GET, POST 和 HEAD.
“path”
體現的是主機之后的路徑. 例如,當你請求
“http://net.tutsplus.com/tutorials/other/top-20-mysql-best-practices/”時 ,
path 就會是 “/tutorials/other/top-20-mysql-best-practices/”.
“protocol” 包含有 “HTTP” 和版本號, 現代瀏覽器都會使用1.1.
剩下的部分每行都是一個“Name:Value”對。它們包含了各式各樣關于請求和你瀏覽器的信息。例如”User-Agent“就表明了你的瀏覽器版本和你所用的操作系統。”Accept-Encoding“會告訴服務器你的瀏覽可以接受類似gzip的壓縮輸出。
這些headers大部分都是可選的。HTTP 請求甚至可以被精簡成這樣子:
GET /tutorials/other/top-20-mysql-best-practices/ HTTP/1.1
Host: net.tutsplus.com
并且你仍舊可以從服務器收到有效的響應。
請求類型
三種最常見的請求類型是:GET,POST 和 HEAD ,從html的編寫過程中你可能已經熟悉了前兩種。
GET:獲取一個文檔
大部分被傳輸到瀏覽器的html, images, js, css, … 都是通過GET方法發出請求的。它是獲取數據的主要方法。
例如,要獲取Nettuts+ 的文章,http request的第一行通常看起來是這樣的:
GET /tutorials/other/top-20-mysql-best-practices/ HTTP/1.1
一旦html加載完成,瀏覽器將會發送GET 請求去獲取圖片,就像下面這樣:
GET /wp-content/themes/tuts_theme/images/header_bg_tall.png HTTP/1.1
表單也可以通過GET方法發送,下面是個例子:
First Name:Last Name:
當這個表單被提交時,HTTP request 就會像這樣:
GET /foo.php?first_name=John&last_name=Doe&action=Submit HTTP/1.1
...
你可以將表單輸入通過附加進查詢字符串的方式發送至服務器。
POST:發送數據至服務器
盡管你可以通過GET方法將數據附加到url中傳送給服務器,但在很多情況下使用POST發送數據給服務器更加合適。通過GET發送大量數據是不現實的,它有一定的局限性。
用POST請求來發送表單數據是普遍的做法。我們來吧上面的例子改造成使用POST方式:
First Name:Last Name:
提交這個表單會創建一個如下的HTTP 請求:
POST /foo.php HTTP/1.1Host: localhostUser-Agent: Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.5) Gecko/20091102 Firefox/3.5.5 (.NET CLR 3.5.30729)Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8Accept-Language: en-us,en;q=0.5Accept-Encoding: gzip,deflateAccept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7Keep-Alive: 300Connection: keep-aliveReferer: http://localhost/test.phpContent-Type: application/x-www-form-urlencodedContent-Length: 43first_name=John&last_name=Doe&action=Submit
這里有三個需要注意的地方:
第一行的路徑已經變為簡單的 /foo.php , 已經沒了查詢字符串。
新增了 Content-Type 和 Content-Lenght 頭部,它提供了發送信息的相關信息.
所有數據都在headers之后,以查詢字符串的形式被發送.
POST方式的請求也可用在AJAX,應用程序,cURL … 之上。并且所有的文件上傳表單都被要求使用POST方式。
HEAD:接收頭部信息
HEAD和GET很相似,只不過HEAD不接受HTTP響應的內容部分。當你發送了一個HEAD請求,那就意味著你只對HTTP頭部感興趣,而不是文檔本身。
這個方法可以讓瀏覽器判斷頁面是否被修改過,從而控制緩存。也可判斷所請求的文檔是否存在。
例如,假如你的網站上有很多鏈接,那么你就可以簡單的給他們分別發送HEAD請求來判斷是否存在死鏈,這比使用GET要快很多。
HTTP響應結構
當瀏覽器發送了HTTP請求之后,服務器就會通過一個HTTP response來響應這個請求。如果不關心內容,那么這個請求看起來會是這樣的:
第一個有價值的信息就是協議。目前服務器都會使用 HTTP/1.x 或者 HTTP/1.1。
接下來一個簡短的信息代表狀態。代碼200意味著我們的請求已經發送成功了,服務器將會返回給我們所請求的文檔,在頭部信息之后。
我們都見過“404”頁面。當我向服務器請求一個不存在的路徑時,服務器就用用404來代替200響應我們。
余下的響應內容和HTTP請求相似。這些內容是關于服務器軟件的,頁面/文件何時被修改過,mime type 等等…
同樣,這些頭部信息也是可選的。
HTTP狀態碼
200 用來表示請求成功.
300 來表示重定向.
400 用來表示請求出現問題.
500 用來表示服務器出現問題.
200 成功 (OK)
前文已經提到,200是用來表示請求成功的。
206 部分內容 (Partial Content)
如果一個應用只請求某范圍之內的文件,那么就會返回206.
這通常被用來進行下載管理,斷點續傳或者文件分塊下載。
404 沒有找到 (Not Found)
很容易理解
401 未經授權 (Unauthorized)
受密碼保護的頁面會返回這個狀態。如果你沒有輸入正確的密碼,那么你就會在瀏覽器中看到如下的信息:
注意這只是受密碼保護頁面,請求輸入密碼的彈出框是下面這個樣子的:
403 被禁止(Forbidden)
如果你沒有權限訪問某個頁面,那么就會返回403狀態。這種情況通常會發生在你試圖打開一個沒有index頁面的文件夾。如果服務器設置不允許查看目錄內容,那么你就會看到403錯誤。
其它一些一些方式也會發送權限限制,例如你可以通過IP地址進行阻止,這需要一些htaccess的協助。
order allow,deny
deny from 192.168.44.201
deny from 224.39.163.12
deny from 172.16.7.92
allow from all
302(或307)臨時移動(Moved Temporarily) 和 301 永久移動(Moved Permanently)
這兩個狀態會出現在瀏覽器重定向時。例如,你使用了類似 bit.ly 的網址縮短服務。這也是它們如何獲知誰點擊了他們鏈接的方法。
302和301對于瀏覽器來說是非常相似的,但對于搜索引擎爬蟲就有一些差別。打個比方,如果你的網站正在維護,那么你就會將客戶端瀏覽器用302重定
向到另外一個地址。搜索引擎爬蟲就會在將來重新索引你的頁面。但是如果你使用了301重定向,這就等于你告訴了搜索引擎爬蟲:你的網站已經永久的移動到了
新的地址。
500 服務器錯誤(Internal Server Error)
這個代碼通常會在頁面腳本崩潰時出現。大部分CGI腳本都不會像PHP那樣輸出錯誤信息給瀏覽器。如果出現了致命的錯誤,它們只會發送一個500的狀態碼。這時需要查看服務器錯誤日志來排錯。
完整的列表
你可以在這里找到完整的HTTP 狀態碼說明。
HTTP Headers 中的 HTTP請求
現在我們來看一些在HTTP headers中常見的HTTP請求信息。
所有這些頭部信息都可以在PHP的$_SERVER數組中找到。你也可以用getallheaders()函數一次性獲取所有的頭部信息。
Host
一個HTTP請求會發送至一個特定的IP地址,但是大部分服務器都有在同一IP地址下托管多個網站的能力,那么服務器必須知道瀏覽器請求的是哪個域名下的資源。
Host: rlog.cn
只是基本的主機名,包含域名和子級域名。
在PHP中,可以通過$_SERVER['HTTP_HOST'] 或 $_SERVER['SERVER_NAME']來查看。
User-Agent
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.5) Gecko/20091102 Firefox/3.5.5 (.NET CLR 3.5.30729)
這個頭部可以攜帶如下幾條信息:
瀏覽器名和版本號.
操作系統名和版本號.
默認語言.
這就是某些網站用來收集訪客信息的一般手段。例如,你可以判斷訪客是否在使用手機訪問你的網站,然后決定是否將他們引導至一個在低分辨率下表現良好的移動網站。
在PHP中,可以通過 $_SERVER['HTTP_USER_AGENT'] 來獲取User-Agent
if ( strstr($_SERVER['HTTP_USER_AGENT'],'MSIE 6') ) {echo "Please stop using IE6!";}
Accept-Language
Accept-Language: en-us,en;q=0.5
這個信息可以說明用戶的默認語言設置。如果網站有不同的語言版本,那么就可以通過這個信息來重定向用戶的瀏覽器。
它可以通過逗號分割來攜帶多國語言。第一個會是首選的語言,其它語言會攜帶一個“q”值,來表示用戶對該語言的喜好程度(0~1)。
在PHP中用?$_SERVER["HTTP_ACCEPT_LANGUAGE"] 來獲取這一信息。
if (substr($_SERVER['HTTP_ACCEPT_LANGUAGE'], 0, 2) == 'fr') {header('Location: http://french.mydomain.com');}
Accept-Encoding
Accept-Encoding: gzip,deflate
大部分的現代瀏覽器都支持gzip壓縮,并會把這一信息報告給服務器。這時服務器就會壓縮過的HTML發送給瀏覽器。這可以減少近80%的文件大小,以節省下載時間和帶寬。
在PHP中可以使用?$_SERVER["HTTP_ACCEPT_ENCODING"] 獲取該信息。 然后調用ob_gzhandler()方法時會自動檢測該值,所以你無需手動檢測。
// enables output buffering// and all output is compressed if the browser supports itob_start('ob_gzhandler');
If-Modified-Since
如果一個頁面已經在你的瀏覽器中被緩存,那么你下次瀏覽時瀏覽器將會檢測文檔是否被修改過,那么它就會發送這樣的頭部:
If-Modified-Since: Sat, 28 Nov 2009 06:38:19 GMT
如果自從這個時間以來未被修改過,那么服務器將會返回“304 Not Modified”,而且不會再返回內容。瀏覽器將自動去緩存中讀取內容
在PHP中,可以用$_SERVER['HTTP_IF_MODIFIED_SINCE'] 來檢測。
//assume $last_modify_time was the last the output was updated
// did the browser send If-Modified-Since header?if(isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])) {//if the browser cache matches the modify timeif($last_modify_time==strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE'])) {//send a 304 header, and no contentheader("HTTP/1.1 304 Not Modified");exit;}}
還有一個叫Etag的HTTP頭信息,它被用來確定緩存的信息是否正確,稍后我們將會解釋它。
Cookie
顧名思義,他會發送你瀏覽器中存儲的Cookie信息給服務器。
Cookie: PHPSESSID=r2t5uvjq435r4q7ib3vtdjq120; foo=bar
它是用分號分割的一組名值對。Cookie也可以包含session id。
在PHP中,單一的Cookie可以訪問$_COOKIE數組獲得。你可以直接用$_SESSION array獲取session變量。如果你需要session id,那么你可以使用session_id()函數代替cookie。
echo$_COOKIE['foo'];//output: barecho$_COOKIE['PHPSESSID'];//output: r2t5uvjq435r4q7ib3vtdjq120session_start();echosession_id();//output: r2t5uvjq435r4q7ib3vtdjq120
Referer
顧名思義, 頭部將會包含referring url信息。
例如,我訪問Nettuts+的主頁并點擊了一個鏈接,這個頭部信息將會發送到瀏覽器:
Referer: http://net.tutsplus.com/
在PHP中,可以通過?$_SERVER['HTTP_REFERER'] 獲取該值。
if(isset($_SERVER['HTTP_REFERER'])) {$url_info=parse_url($_SERVER['HTTP_REFERER']);//is the surfer coming from Google?if($url_info['host'] == 'www.google.com') {parse_str($url_info['query'],$vars);echo"You searched on Google for this keyword: ".$vars['q'];}}//if the referring url was:
// http://www.google.com/search?source=ig&hl=en&rlz=&=&q=http+headers&aq=f&oq=&aqi=g-p1g9
// the output will be:
// You searched on Google for this keyword: http headersYou may have noticed the word “referrer” is misspelledas“referer”. Unfortunately it made into the official HTTP specifications like that and got stuck.
Authorization
當一個頁面需要授權,瀏覽器就會彈出一個登陸窗口,輸入正確的帳號后,瀏覽器會發送一個HTTP請求,但此時會包含這樣一個頭部:
Authorization: Basic bXl1c2VyOm15cGFzcw==
包含在頭部的這部分信息是base64 encoded。例如,base64_decode(‘bXl1c2VyOm15cGFzcw==’) 會被轉化為 ‘myuser:mypass’ 。
在PHP中,這個值可以用$_SERVER['PHP_AUTH_USER'] 和 $_SERVER['PHP_AUTH_PW'] 獲得。
更多細節我們會在WWW-Authenticate部分講解。
HTTP Headers 中的 HTTP響應
現在讓我了解一些常見的HTTP Headers中的HTTP響應信息。
在PHP中,你可以通過header()來設置頭部響應信息。PHP已經自動發送了一些必要的頭部信息,如 載入的內容,設置 cookies 等等… 你可以通過headers_list()函數看到已發送和將要發送的頭部信息。你也可以使用headers_sent()函數來檢查頭部信息是否已經被發送。
Cache-Control
w3.org 的定義是:“The Cache-Control general-header field is used to specify
directives which MUST be obeyed by all caching mechanisms along the
request/response chain.” 其中“caching mechanisms” 包含一些你ISP可能會用到的 網關和代理信息。
例如:
Cache-Control: max-age=3600, public
“public”意味著這個響應可以被任何人緩存,“max-age” 則表明了該緩存有效的秒數。允許你的網站被緩存降大大減少下載時間和帶寬,同時也提高的瀏覽器的載入速度。
也可以通過設置 “no-cache” ?指令來禁止緩存:
Cache-Control: no-cache
更多詳情請參見w3.org。
Content-Type
這個頭部包含了文檔的”mime-type”。瀏覽器將會依據該參數決定如何對文檔進行解析。例如,一個html頁面(或者有html輸出的php頁面)將會返回這樣的東西:
Content-Type: text/html; charset=UTF-8
‘text’ 是文檔類型,‘html’則是文檔子類型。 這個頭部還包括了更多信息,例如 charset。
如果是一個圖片,將會發送這樣的響應:
Content-Type: image/gif
瀏覽器可以通過mime-type來決定使用外部程序還是自身擴展來打開該文檔。如下的例子降調用Adobe Reader:
Content-Type: application/pdf
直接載入,Apache通常會自動判斷文檔的mime-type并且添加合適的信息到頭部去。并且大部分瀏覽器都有一定程度的容錯,在頭部未提供或者錯誤提供該信息的情況下它會去自動檢測mime-type。
你可以在這里找到一個常用mime-type列表。
在PHP中你可以通過finfo_file()來檢測文件的ime-type。
Content-Disposition
這個頭部信息將告訴瀏覽器打開一個文件下載窗口,而不是試圖解析該響應的內容。例如:
Content-Disposition: attachment;filename="download.zip"
他會導致瀏覽器出現這樣的對話框:
注意,適合它的Content-Type頭信息同時也會被發送
Content-Type: application/zipContent-Disposition: attachment;filename="download.zip"
Content-Length
當內容將要被傳輸到瀏覽器時,服務器可以通過該頭部告知瀏覽器將要傳送文件的大小(bytes)。
Content-Length: 89123
對于文件下載來說這個信息相當的有用。這就是為什么瀏覽器知道下載進度的原因。
例如,這里我寫了一段虛擬腳本,來模擬一個慢速下載。
//it's a zip fileheader('Content-Type: application/zip');//1 million bytes (about 1megabyte)header('Content-Length: 1000000');//load a download dialogue, and save it as download.zipheader('Content-Disposition: attachment; filename="download.zip"');//1000 times 1000 bytes of datafor($i= 0;$i< 1000;$i++) {echostr_repeat(".",1000);//sleep to slow down the downloadusleep(50000);}
結果將會是這樣的:
現在,我將Content-Length頭部注釋掉:
//it's a zip fileheader('Content-Type: application/zip');//the browser won't know the size
// header('Content-Length: 1000000');
// load a download dialogue, and save it as download.zipheader('Content-Disposition: attachment; filename="download.zip"');//1000 times 1000 bytes of datafor($i= 0;$i< 1000;$i++) {echostr_repeat(".",1000);//sleep to slow down the downloadusleep(50000);}
結果就變成了這樣:
這個瀏覽器只會告訴你已下載了多少,但不會告訴你總共需要下載多少。而且進度條也不會顯示進度。
Etag
這是另一個為緩存而產生的頭部信息。它看起來會是這樣:
Etag: "pub1259380237;gz"
服務器可能會將該信息和每個被發送文件一起響應給瀏覽器。該值可以包含文檔的最后修改日期,文件大小或者文件校驗和。瀏覽會把它和所接收到的文檔一起緩存。下一次當瀏覽器再次請求同一文件時將會發送如下的HTTP請求:
If-None-Match: "pub1259380237;gz"
如果所請求的文檔Etag值和它一致,服務器將會發送304狀態碼,而不是2oo。并且不返回內容。瀏覽器此時就會從緩存加載該文件。
Last-Modified
顧名思義,這個頭部信息用GMT格式表明了文檔的最后修改時間:
Last-Modified: Sat, 28 Nov 2009 03:50:37 GMT$modify_time = filemtime($file);header("Last-Modified: " . gmdate("D, d M Y H:i:s", $modify_time) . " GMT");
它提供了另一種緩存機制。瀏覽器可能會發送這樣的請求:
If-Modified-Since: Sat, 28 Nov 2009 06:38:19 GMT
在If-Modified-Since一節我們已經討論過了。
Location
這個頭部是用來重定向的。如果響應代碼為 301 或者 302 ,服務器就必須發送該頭部。例如,當你訪問?http://www.nettuts.com 時瀏覽器就會收到如下的響應:
HTTP/1.x 301 Moved Permanently
...
Location: http://net.tutsplus.com/
...
在PHP中你可以通過這種方式對訪客重定向:
header('Location: http://net.tutsplus.com/');
默認會發送302狀態碼,如果你想發送301,就這樣寫:
header('Location: http://net.tutsplus.com/', true, 301);
Set-Cookie
當一個網站需要設置或者更新你瀏覽的cookie信息時,它就會使用這樣的頭部:
Set-Cookie: skin=noskin;path=/; domain=.amazon.com; expires=Sun, 29-Nov-2009 21:42:28 GMTSet-Cookie: session-id=120-7333518-8165026; path=/; domain=.amazon.com; expires=Sat Feb 27 08:00:00 2010 GMT
每個cookie會作為單獨的一條頭部信息。注意,通過js設置cookie將不會體現在HTTP頭中。
在PHP中,你可以通過setcookie()函數來設置cookie,PHP會發送合適的HTTP 頭。
setcookie("TestCookie", "foobar");
它會發送這樣的頭信息:
Set-Cookie: TestCookie=foobar
如果未指定到期時間,cookie就會在瀏覽器關閉后被刪除。
WWW-Authenticate
一個網站可能會通過HTTP發送這個頭部信息來驗證用戶。當瀏覽器看到頭部有這個響應時就會打開一個彈出窗。
WWW-Authenticate: Basic realm="Restricted Area"
它會看起來像這樣:
在PHP手冊的一章中就有一段簡單的代碼演示了如果用PHP做這樣的事情:
if(!isset($_SERVER['PHP_AUTH_USER'])) {header('WWW-Authenticate: Basic realm="My Realm"');header('HTTP/1.0 401 Unauthorized');echo'Text to send if user hits Cancel button';exit;}else{echo"
Hello {$_SERVER['PHP_AUTH_USER']}.
";echo"You entered {$_SERVER['PHP_AUTH_PW']} as your password.
";}Content-Encoding
這個頭部通常會在返回內容被壓縮時設置。
Content-Encoding: gzip
在PHP中,如果你調用了ob_gzhandler()函數,這個頭部將會自動被設置。