原文:http://thobian.info/?p=318
什麼是HTTP Headers
HTTP是Hypertext Transfer Protocol的缩寫,整個WWW都在使用這種協定,幾乎你在流覽器裏看到的大部分內容都是通過HTTP協定來傳輸的,比如這篇文章。
HTTP Headers是HTTP請求和相應的核心,它承載了關於用戶端流覽器,請求頁面,伺服器等相關的資訊。
示例
當你在流覽器位址欄裏鍵入一個URL,你的流覽器會將類似如下的HTTP請求:
GET /tutorials/other/top-20-mysql-best-practices/ HTTP/1.1 (Request line)
Host: net.tutsplus.com
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)
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,* / <em>;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,</em>;q=0.7
Keep-Alive: 300
Connection: keep-alive
Cookie: PHPSESSID=r2t5uvjq435r4q7ib3vtdjq120
Pragma: no-cache
Cache-Control: no-cache
第一行被稱為Request Line 它描述的是這個請求的基本資訊,剩下的就是HTTP Headers了。
請求完成之後,你的流覽器可能會收到如下的HTTP回應:
HTTP/1.x 200 OK (state line)
Transfer-Encoding: chunked
Date: Sat, 28 Nov 2009 04:36:25 GMT
Server: LiteSpeed
Connection: close
X-Powered-By: W3 Total Cache/0.8
Pragma: public
Expires: Sat, 28 Nov 2009 05:36:25 GMT
Etag: "pub1259380237;gz"
Cache-Control: max-age=3600, public
Content-Type: text/html; charset=UTF-8
Last-Modified: Sat, 28 Nov 2009 03:50:37 GMT
X-Pingback: http://net.tutsplus.com/xmlrpc.php
Content-Encoding: gzip
Vary: Accept-Encoding, Cookie, User-Agent
第一行被稱為Status Line,它之後就是HTTP Headers,空行完了就開始輸出內容了(在這個案例中是一些HTML輸出)。
但你查看頁面源代碼卻不能看到HTTP Headers,雖然它們連同你能看到的東西一起被傳送至流覽器。
這個HTTP請求也發出了一些其他資源的接收請求,例如圖片,CSS檔,JS文件等等。
下面我們來看看細節。
怎樣才能看到HTTP Headers
下面這些FireFox擴展能夠幫助你分析HTTP Headers:
Firebug
Live HTTP Headers
HTTPFox
在PHP中:
文章下面將會看到一些使用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: Submit
當這個表單被提交時,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: Submit
提交這個表單會創建一個如下的HTTP請求:
POST /foo.php HTTP/1.1
Host: localhost
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)
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,* / <em>;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,</em>;q=0.7
Keep-Alive: 300
Connection: keep-alive
Referer: http://localhost/test.php
Content-Type: application/x-www-form-urlencoded
Content-Length: 43
first_name=John&amp;last_name=Doe&amp;action=Submit
這裏有三個需要注意的地方:
第一行的路徑已經變為簡單的 /foo.php , 已經沒了查詢字串。
新增了 Content-Type 和Content-Lenght Header,它提供了發送資訊的相關資訊.
所有資料都在headers之後,以查詢字串的形式被發送.
POST方式的請求也可用在AJAX,應用程式,cURL … 之上。並且所有的檔上傳表單都被要求使用 POST 方式。
HEAD:接收Header資訊
HEAD 和GET很相似,只不過 HEAD 不接受HTTP回應的內容部分。當你發送了一個 HEAD 請求,那就意味著你只對HTTPHeader感興趣,而不是文檔本身。
這個方法可以讓流覽器判斷頁面是否被修改過,從而控制緩存。也可判斷所請求的文檔是否存在。
例如,假如你的網站上有很多鏈結,那麼你就可以簡單的給他們分別發送 HEAD 請求來判斷是否存在死鏈,這比使用GET要快很多。
HTTP回應結構
當流覽器發送了HTTP請求之後,伺服器就會通過一個 HTTP response 來回應這個請求。如果不關心內容,那麼這個請求看起來會是這樣的:
第一個有價值的資訊就是協定。目前伺服器都會使用 HTTP/1.x 或者 HTTP/1.1 。
接下來一個簡短的資訊代表狀態。代碼200意味著我們的請求已經發送成功了,伺服器將會返回給我們所請求的文檔,在Header資訊之後。
我們都見過404頁面。當我向伺服器請求一個不存在的路徑時,伺服器就用用404來代替200回應我們。
餘下的回應內容和HTTP請求相似。這些內容是關於伺服器軟體的,頁面/檔何時被修改過,mime type 等等…
同樣,這些Header資訊也是可選的。
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請求資訊。
所有這些Header資訊都可以在PHP的 $_SERVER 陣列中找到。你也可以用getallheaders() 函數一次性獲取所有的Header資訊。
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)
這個Header可以攜帶如下幾條資訊:
流覽器名和版本號.
作業系統名和版本號.
默認語言.
這就是某些網站用來收集訪客資訊的一般手段。例如,你可以判斷訪客是否在使用手機訪問你的網站,然後決定是否將他們引導至一個在低解析度下表現良好的移動網站。
在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 it
ob_start('ob_gzhandler');
If-Modified-Since
如果一個頁面已經在你的流覽器中被cache,那麼你下次流覽時流覽器將會檢測文檔是否被修改過,那麼它就會發送這樣的Header:
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 time
if ($last_modify_time == strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE'])) {
// send a 304 header, and no content
header("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: bar
echo $_COOKIE['PHPSESSID'];
// output: r2t5uvjq435r4q7ib3vtdjq120
session_start();
echo session_id();
// output: r2t5uvjq435r4q7ib3vtdjq120
Referer
顧名思義, Header將會包含referring url信息。
例如,我訪問Nettuts+的主頁並點擊了一個鏈結,這個Header資訊將會發送到流覽器:
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 headers
You may have noticed the word “referrer” is misspelled as “referer”. Unfortunately it made into the official HTTP specifications like that and got stuck.
Authorization
當一個頁面需要授權,流覽器就會彈出一個登入視窗,輸入正確的帳號後,流覽器會發送一個HTTP請求,但此時會包含這樣一個Header:
Authorization: Basic bXl1c2VyOm15cGFzcw==
包含在Header的這部分資訊是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() 來設置Header回應資訊。PHP已經自動發送了一些必要的Header資訊,如 載入的內容,設置 cookies 等等… 你可以通過 headers_list() 函數看到已發送和將要發送的Header資訊。你也可以使用 headers_sent() 函數來檢查Header資訊是否已經被發送。
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 意味著這個回應可以被任何人cache,max-age 則表明了該cache有效的秒數。允許你的網站被cache降大大減少下載時間和帶寬,同時也提高的流覽器的載入速度。
也可以通過設置 no-cache 指令來禁止緩存:
Cache-Control: no-cache
更多詳情請參見w3.org 。
Content-Type
這個Header包含了文檔的 mime-type 。流覽器將會依據該參數決定如何對文檔進行解析。例如,一個html頁面(或者有html輸出的php頁面)將會返回這樣的東西:
Content-Type: text/html; charset=UTF-8
text 是文檔類型,html 則是文檔子類型。 這個Header還包括了更多資訊,例如 charset 。
如果是一個圖片,將會發送這樣的回應:
Content-Type: image/gif
流覽器可以通過 mime-type 來決定使用外部程式還是自身擴展來打開該文檔。如下的例子降調用Adobe Reader:
Content-Type: application/pdf
直接載入,Apache通常會自動判斷文檔的mime-type並且添加合適的資訊到Header去。並且大部分流覽器都有一定程度的容錯,在Header未提供或者錯誤提供該資訊的情況下它會去自動檢測mime-type。
你可以在這裏 找到一個常用mime-type列表。
在PHP中你可以通過 finfo_file() 來檢測檔的ime-type。
Content-Disposition
這個Header資訊將告訴流覽器打開一個檔下載視窗,而不是試圖解析該回應的內容。例如:
Content-Disposition: attachment; filename="download.zip"
他會導致流覽器出現這樣的對話方塊:
注意,適合它的 Content-Type 頭資訊同時也會被發送
Content-Type: application/zip
Content-Disposition: attachment; filename="download.zip"
Content-Length
當內容將要被傳輸到流覽器時,伺服器可以通過該Header告知流覽器將要傳送檔的大小(bytes)。
Content-Length: 89123
對於檔下載來說這個資訊相當的有用。這就是為什麼流覽器知道下載進度的原因。
例如,這裏我寫了一段虛擬腳本,來模擬一個慢速下載。
// it's a zip file
header('Content-Type: application/zip');
// 1 million bytes (about 1megabyte)
header('Content-Length: 1000000');
// load a download dialogue, and save it as download.zip
header('Content-Disposition: attachment; filename="download.zip"');
// 1000 times 1000 bytes of data
for ($i = 0; $i < 1000; $i++) {
echo str_repeat(".",1000);
// sleep to slow down the download
usleep(50000);
}
結果將會是這樣的:
現在,我將Content-LengthHeader注釋掉:
// it's a zip file
header('Content-Type: application/zip');
// the browser won't know the size
// header('Content-Length: 1000000');
// load a download dialogue, and save it as download.zip
header('Content-Disposition: attachment; filename="download.zip"');
// 1000 times 1000 bytes of data
for ($i = 0; $i < 1000; $i++) {
echo str_repeat(".",1000);
// sleep to slow down the download
usleep(50000);
}
結果就變成了這樣:
這個流覽器只會告訴你已下載了多少,但不會告訴你總共需要下載多少。而且進度條也不會顯示進度。
Etag
這是另一個為緩存而產生的Header資訊。它看起來會是這樣:
Etag: "pub1259380237;gz"
伺服器可能會將該資訊和每個被發送檔一起回應給流覽器。該值可以包含文檔的最後修改日期,檔大小或者檔校驗和。流覽會把它和所接收到的文檔一起緩存。下一次當流覽器再次請求同一檔時將會發送如下的HTTP請求:
If-None-Match: "pub1259380237;gz"
如果所請求的文檔Etag值和它一致,伺服器將會發送304狀態碼,而不是200。並且不返回內容。流覽器此時就會從緩存載入該檔。
Last-Modified
顧名思義,這個Header資訊用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
這個Header是用來重定向的。如果回應代碼為 301 或者 302 ,伺服器就必須發送該Header。例如,當你訪問 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資訊時,它就會使用這樣的Header:
Set-Cookie: skin=noskin; path=/; domain=.amazon.com; expires=Sun, 29-Nov-2009 21:42:28 GMT
Set-Cookie: session-id=120-7333518-8165026; path=/; domain=.amazon.com; expires=Sat Feb 27 08:00:00 2010 GMT
每個cookie會作為單獨的一條Header資訊。注意,通過js設置cookie將不會出現在HTTP頭中。
在PHP中,你可以通過 setcookie() 函數來設置cookie,PHP會發送合適的HTTP 頭。
setcookie("TestCookie", "foobar");
它會發送這樣的頭資訊:
Set-Cookie: TestCookie=foobar
如果未指定到期時間,cookie就會在流覽器關閉後被刪除。
WWW-Authenticate
一個網站可能會通過HTTP發送這個Header資訊來驗證用戶。當流覽器看到Header有這個回應時就會打開一個彈出窗。
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
這個Header通常會在返回內容被壓縮時設置。