淺析http協議、cookies和session機制、瀏覽器快取
最近幾天在複習http協議中headers,cookies、session、快取等相關知識,發現些新知識點。
這篇文章注重結合PHP去理解這些內容,也就是比較注重實踐部分。
一、 http headers
NO1:對於web應用,使用者群在客戶端 (各種瀏覽器)點選任何一個連線向伺服器傳送http請求,這過程肯定需要3次握手,建立連線,伺服器響應返回資料。
每次請求都有頭部和實體部分,先看下面筆者監聽QQ空間的headers,QQ空間的原因是它頭部內容比較全
Request Headers: GET http://user.qzone.qq.com/445235728 HTTP/1.1 Host: user.qzone.qq.com Connection: keep-alive Cache-Control: max-age=0 User-Agent: Mozilla/5.0 (Windows NT 6.1) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.56 Safari/535.11 Accept:text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Referer: http://qzone.qq.com/ Accept-Encoding:gzip,deflate,sdch Accept-Language: zh-CN,zh;q=0.8 Accept-Charset: GBK,utf-8;q=0.7,*;q=0.3 Cookie:o_cookie=445235728;(省略很多……) If-Modified-Since: Wed, 13 Jun 2012 01:32:19 GMT ----------------- Response Headers: HTTP/1.1 200 OK Connection:close Server: QZHTTP-2.34.0 Date: Wed, 13 Jun 2012 02:59:31 GMT Content-Encoding: gzip Set-Cookie:login_time=61F0EEA02D704B1DBCF25166A74941B24F4BE24B205C466F;PATH=/; DOMAIN=qzone.qq.com Set-Cookie:Loading=Yes;expires=Wed,13-Jun-201216:00:00GMT;PATH=/;DOMAIN=qzone.qq.com X-UA-Compatible: IE=Edge Last-Modified: Wed, 13 Jun 2012 02:59:31 GMT Cache-Control: max-age=0, no-transform Content-Type: text/html;charset=utf-8 Transfer-Encoding: chunked
客戶端向服務端發請求headers和服務端響應客戶端headers圖:
通過圖片可以看出:
1、 客戶端請求headers包含了請求行和一些頭域。
請求行:請求的方法 統一資源標識器(URL)協議版本 ------這三者用空格分開,最後換行回車(\r\n) 例如:GET http://user.qzone.qq.com/445235728 HTTP/1.1
各種頭域:這些頭域都是有關鍵字和鍵值成對組合,最後換行回車(\r\n)結束,這些頭域告訴伺服器應該怎麼去響應以及本身一些資訊。
2、 伺服器響應
狀態行:協議版本 響應狀態 狀態描述 ------這三者用空格分開,最後換行回車(\r\n) 例如:HTTP/1.1 200 OK
各種頭域:這些頭域也是有關鍵字和鍵值成對組合,最後換行回車(\r\n)結束,這些頭域告訴客戶端應該怎麼去響應以及本身一些資訊。
NO2
這裡就不一一說每個頭域的概念和作用,想了解的請看:http://www.phpben.com/?post=34 現在介紹幾個認為重要、在一些網站上的測試資料、以及請求返回各頭域php程式碼實現
測試時間:2012.6.14前
測試物件:csdn 、cnbeta 、cnblos、騰訊(QQ空間、朋友網、新聞網)、新浪(微博、主頁)、人人網、百度、淘寶、優酷、土豆這些網站
(1) Connection頭域:這個頭域只有http/1.1才有,預設是keep-alive值表示長連線,這樣的話就不用每請求一個資源比如圖片,css檔案,js檔案都要和伺服器進行3此握手連線,這個在一定程度上彌補了http沒狀態的一個缺陷,減少連線伺服器的時間。
檢視測試網站Connection頭域發現騰訊QQ空間、騰訊新聞網、新浪主頁和微博,優酷和土豆Connection:close;除了這些其他的都是Connection:keep-alive
為什麼?
1、connection: keep-alive 能正常使用的一個前提條件是還要提供content-length的頭域告訴客戶端正文的長度。那計算正文長度是個問題,對於多內容,叢集伺服器來說不是件易事。騰訊和新浪,優酷的這些都很難計算,對與工程師來說之間關閉了(預設是開啟的)。
2、老伺服器端不支援,對於騰訊,新浪這些老油條,伺服器叢集很龐大,難免有些老舊的不支援長連線的,為了一些相容性問題,直接關閉了
Ps:這兩點原因未求證過!^-^
用php headers(“Connection:keep-alive”);
(2) Content-Encoding頭域
Content-Encoding文件的編碼(Encode)方法.
上述網站出了cnbeta不用gzip壓縮,優酷用deflate,其餘都是。這也透漏一個重要資訊,那就phper要掌握壓縮gzip傳輸技術。
Php可以通過mod_gzip模組來實現。程式碼:ob_start("ob_gzhandler");
(3) Server頭域暴漏伺服器重要的安全資訊。
Csdn:Server:nginx/0.7.68 ------------版本都暴露
騰訊QQ空間:Server:QZHTTP-2.34.0--------某位tx朋友透漏這是內部自己開發的伺服器,這個可夠安全
新浪微博:Server:apache -------------這個沒暴漏版本
鳳凰網:Server: nginx/0.8.53
人人網:Server:nginx/1.2.0
淘寶網:Tengine ---------這是淘寶內部技術團隊開發的web伺服器,基於Nginx
cnblogs部落格園:Server:Microsoft-IIS/7.5
騰訊朋友網:Tencent/PWS ---------騰訊內部開發
騰訊新聞網:Server:squid/3.1.18
優酷網:Server: fswww1-----------是不是內部就不清楚,至少筆者不知道什麼來的^_^
土豆網:Tengine/1.2.3
百度:server: BWS/1.0 ---------應該也是百度內部自己開發的伺服器
很明顯Server頭域是返回伺服器的資訊,但也可以說暴漏資訊,面對這個問題,大公司就自己開發基於自己功能的內部伺服器。
(4) X-Powered-By頭域可供修改,基於安全則可以修改
X-Powered-By頭域反應什麼語言什麼版本執行後臺程式。這個可以同個header函式修改
header("X-Powered-By:acb");
(5) Cache-control、expires、last-modified等重要頭域
Cache-control:指定請求和響應遵循的快取機制。在請求訊息或響應訊息中設定Cache-Control並不會修改另一個訊息處理過程中的快取處理過程。請求時的快取指令包括no-cache、no-store、max-age、max-stale、min-fresh、only-if-cached,響應訊息中的指令包括public、private、no-cache、no-store、no-transform、must-revalidate、proxy-revalidate、max-age。
Php程式碼實現:header("cache-control: abc");abc是上述指令值一個或多個,多個用’,’分開
Expires:告訴瀏覽器指明應該在什麼時候認為文件已經過期,從而不再快取它。程式碼實現:header("Expires:". date('D, d M Y H:i:s \G\M\T', time()+10));--------這個是把時間截轉化成格林時區字串給expires頭域,這個顯示時間會比中國北京時間少8個小時,東8區的實現:header("Expires:". date('r', time()+10))
last-modified:這個是伺服器返回給瀏覽器,瀏覽器下次請求則把該值賦給if-modified-since頭域傳給伺服器,伺服器就可以根據此值判斷是否有改變,有則繼續執行下去,否者返回304 not modified。Php設定expires頭域一樣。
程式碼:
<?php
if(isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) && (time()-strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) < 10)) {
header("HTTP/1.1 304 Not Modified");
exit;
}
header("Last-Modified: " . date('D, d M Y H:i:s \G\M\T', time()) );或者header("Last-Modified: " . date('r', time()) );
?>
前者是格林時間格式,後者是中國時間。需要注意的就是php.ini的時區prc則用後則,否者前者。筆者曾經試過在時區是prc的情況下用了前者,導致time()-strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) <0永遠成立,因為是負值。
注意:當請求頁面有session_start()的時候,則不管是否有expires、cache-control、last-modified設定,則返回給客戶端Cache-Control頭域為Cache-Control:no-store, no-cache, must-revalidate Expires頭域 Expires:Thu, 19 Nov 1981 08:52:00 GMT。這個問題煩了筆者2天,都以為php.ini 或是apache的問題。最後竟然是session_start()的問題。
二、 瀏覽器快取動態
前面介紹了http headers幾個告訴瀏覽器如何處理快取。但不同瀏覽器處理各種頭域的方式不同,以下就是筆者。
(1) header(“cache-control: no-store”)
IE9 |
Google17.0 |
|
Maxthon3 |
|
點選重新整理鍵 |
重發請求,返回200狀態 |
重發請求,返回200狀態 |
重發請求,返回200狀態 |
重發請求,返回200狀態 |
位址列回車 |
重發請求,返回200狀態 |
重發請求,返回200狀態 |
重發請求,返回200狀態 |
重發請求,返回200狀態 |
點選後退鍵 |
同上 |
同上 |
同上 |
同上 |
(2) header(“cache-control: no-cache”)
IE9 |
Google17.0 |
|
Maxthon3 |
|
點選重新整理鍵 |
重發請求,返回200狀態 |
重發請求,返回200狀態 |
重發請求,返回200狀態 |
重發請求,返回200狀態 |
位址列回車 |
重發請求,返回200狀態 |
重發請求,返回200狀態 |
重發請求,返回200狀態 |
重發請求,返回200狀態 |
點選後退鍵 |
同上 |
From cache |
From cache |
同上 |
(3) header(“cache-control:bublic”)
IE9 |
Google17.0 |
|
Maxthon3 |
|
點選重新整理鍵 |
重發請求,返回200狀態 |
重發請求,返回200狀態 |
重發請求,返回200狀態 |
重發請求,返回200狀態 |
位址列回車 |
from cache |
重發請求,返回200狀態 |
重發請求,返回200狀態 |
重發請求,返回200狀態 |
點選後退鍵 |
From cache |
From cache |
From cache |
同上 |
(4) header("cache-control:private"); header("cache-control: must-revalidate ")
IE9 |
Google17.0 |
|
Maxthon3 |
|
點選重新整理鍵 |
重發請求,返回200狀態 |
重發請求,返回200狀態 |
重發請求,返回200狀態 |
重發請求,返回200狀態 |
位址列回車 |
除第一次外都是from cache |
重發請求,返回200狀態 |
重發請求,返回200狀態 |
重發請求,返回200狀態 |
點選後退鍵 |
From cache |
From cache |
From cache |
同上 |
(5) header("cache-control:max-age=num");num是秒數
IE9 |
Google17.0 |
|
Maxthon3 |
|
點選重新整理鍵 |
重發請求,返回200狀態 |
重發請求,返回200狀態 |
重發請求,返回200狀態 |
重發請求,返回200狀態 |
位址列回車 |
秒數<num from cache |
重發請求,返回200狀態 |
重發請求,返回200狀態 |
重發請求,返回200狀態 |
點選後退鍵 |
From cache |
From cache |
From cache |
同上 |
(6) header("Expires:". date('D, d M Y H:i:s \G\M\T', time()+num)); num是秒數
IE9 |
Google17.0 |
|
Maxthon3 |
|
點選重新整理鍵 |
重發請求,返回200狀態 |
重發請求,返回200狀態 |
重發請求,返回200狀態 |
重發請求,返回200狀態 |
位址列回車 |
秒數<num from cache |
重發請求,返回200狀態 |
重發請求,返回200狀態 |
重發請求,返回200狀態 |
點選後退鍵 |
From cache |
From cache |
From cache |
同上 |
(7) if(isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) && (time()-strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) < num)) {
header("HTTP/1.1 304 Not Modified");
exit;
} header("Last-Modified: " . date('D, d M Y H:i:s \G\M\T', time()) );
IE9 |
Google17.0 |
|
Maxthon3 |
|
點選重新整理鍵 |
秒數<num 304 not modified |
秒數<num 304 not modified |
秒數<num 304 not modified |
重發請求,返回200狀態 |
位址列回車 |
from cache |
秒數<num 304 not modified |
秒數<num 304 not modified |
重發請求,返回200狀態 |
點選後退鍵 |
From cache |
From cache |
From cache |
同上 |
結論:
1、 重新整理對於任何瀏覽器且不管是什麼cache-control,都會重新請求,一般返回是200,除非Last-Modified設定
2、 後退鍵除非no-cache; no-store外都是使用快取
3、 Cache-control:no-store 在瀏覽器中任何操作都重新提交請求,包括後退
4、 遨遊3的快取很差
5、 IE9 的快取很強,所以用ie9除錯的時候儘可能點重新整理而不是在位址列回車
鑑於這種情況,對於不同的應用(有些要快取,有些經常更新)對於不同的國家各種瀏覽器份額,而哪種快取方式。中國IE比較多,加上360瀏覽器的加入(用IE核心),那就要主要參照IE瀏覽器。
但筆者還是比較喜歡header("Last-Modified: " . date('D, d M Y H:i:s \G\M\T', time()) );這種方式。結合起來connection:keep-alive能讓快取技術更成熟。
注意:
1、 也許你會問,用Cache-control:no-store或Cache-control:no-store,但除錯頁面還是沒原來的快取。然後清除瀏覽器快取關掉重啟瀏覽器, 快取還在。這是因為你的web應用用了檔案快取如ecshop常出現這種情況,這種情況就要進web應用後臺刪除檔案快取。
2、 除錯的時候儘可能不要在位址列回車,特別是IE,google還好一點,但是要知道這次的測試只是各個瀏覽器中的一個版本,所以除錯的時候儘可能點重新整理按鈕。
3、 但在cache-control:max-age=num 和expires 一起使用的時候,前者級別比較高,瀏覽器會忽略expires的設定。(上面沒給出測試內容)
三、 Session和cookies
Session 、cookies是程式設計師永遠討論的話題之一。
1、 簡單說一下cookies、session
(1) Cookies是儲存在客戶端的小段文字,隨客戶端點每一個請求傳送該url下的所有cookies到伺服器端。比如在谷歌瀏覽器下,開啟ww.abc.com下的兩個檔案,a.php包含cookies1和cookies2,b.php包含了cookies3和cookies4,那麼在a.php或b.php 點任意一個連線(當然是ww.abc.com伺服器上的),瀏覽器就會把cookies1~4這4個cookies傳送給伺服器。但是如果在IE9有開啟一個包含cookies5的c.php,哪門在google瀏覽器點選連線是不會發送cookies5的。
(2) Session則儲存伺服器段,通過唯一的值sessionID來區別每一個使用者。SessionID隨每個連線請求傳送到伺服器,伺服器根據sessionID來識別客戶端,再通過session 的key獲取session值。SessionID傳回伺服器的實現方式可以通過cookies和url回寫來實現。
注意:
1、 同一個瀏覽器開啟同一個檔案,如a.php ,或同時有設定session的兩個檔案a.php、b.php sessionID則只有一個。(時間上不能是開啟a.php 關閉瀏覽器再開啟b.php)
2、 不同瀏覽器在同一時間開啟同意檔案的sessionID也不一樣
3、 sessionID是伺服器生成的不規則唯一字串,如:
PHPSESSID=05dbfffd3453b7be02898fdca4fcd82b;------ PHPSESSID可以通過php.ini中session.name來改變,所以筆者在監聽一些大型網站的時候查不出PHPSESSID,這是一個安全因素。
(3) cookies、session在php中的主要相關引數
(1) session.save_handler = ”files”
預設以檔案方式存取session資料,如果想要使用自定義的處理器來存取session資料,比如資料庫,用”user”。
(2) session.use_cookies = 1 前面說到sessionID用cookies來實現,這裡就是,1表示用cookies
(3) session.use_trans_sid = 0 上面是用cookies來實現sessionID,這裡值若是1則使用url回寫方式,級別比session.use_cookies高
(4) session.use_only_cookies = 0 值為1則sessionID只可以用cookies實現,級別比前兩個高
(5) session.cache_expire =180 session 快取過期的秒數
(6) session.gc_maxlifetime = 1440
設定儲存的session檔案生存期,超過此引數設定秒數後,儲存的資料將被視為’垃圾’並由垃圾回收程式清理。判斷標準是最後訪問資料的時間(對於FAT檔案系統是最後重新整理資料的時間)。如果多個指令碼共享同一個session.save_path目錄但session.gc_maxlifetime不同,將以所有session.gc_maxlifetime指令中的最小值為準。
(4) 圖說cookie 、ssession
php程式碼如下
session_start();
$_SESSION['favcolor'] = 'green';
$_SESSION['animal'] = 'cat';
$_SESSION['time'] = time();
setcookie("cookie1","www.phpben.com",time()+3600*10);
setcookie("cookie2","www.phpben.com",time()+3600*10);
圖片:
結論:
1、 第一次請求是沒用cookies的,而第二次有PHPSESSID和兩個cookies是因為伺服器第一請求返回這個三個cookies。
2、第二次請求比第一次多返回PHPSESSID這個cookies,在第二次則沒有了,直到session過期後重新設定。
2、 ;瀏覽器關掉cookies,session是否可以正常執行?
前面提及sessionID的時候有兩種方式。
(1) cookies 方式,在session.use_trans_sid=0 and session.use_cookies = 1的情況下使用。這種方法是每次瀏覽器端點每個請求,都把sessionID傳送到伺服器。
(2) url回寫,session.use_only_cookies = 0 and session.use_trans_sid=1的情況下,伺服器會忽略session.use_trans_sid,在瀏覽器發hhtp請求後,伺服器會在返回頁面內容中每個連線後面加上PHPSESSID=05dbfffd3453b7be02898fdca4fcd82b (在php.ini沒改session.name,預設是PHPSESSID),這樣就算客戶端的瀏覽器禁止了cookies,一樣能實現session功能。
這裡來個測試:
在1.php檔案程式碼:
<?php
echo 'Welcome to page #1<br/>';
session_start();
$_SESSION['favcolor'] = 'green';
$_SESSION['animal'] = 'cat';
$_SESSION['time'] = time();
// Works if session cookie was accepted
echo '<br /><a href="2.php">page 1 (這個href中沒SID引數)</a><br/>';
// Or maybe pass along the session id, if needed
echo '<br /><a href="2.php?' . SID . '">page 2 (這個href中有SID引數)</a><br/>';
?>
在2.php檔案程式碼:
<?php
session_start();
echo 'Welcome to page #2<br />';
echo $_SESSION['favcolor'],'<br/>'; // green
echo $_SESSION['animal'],'<br/>'; // cat
echo date('Y m d H:i:s', $_SESSION['time']),'<br/>';
// You may want to use SID here, like we did in page1.php
echo '<br /><a href="1.php">return page 1</a>';
?>
情景1:沒禁用瀏覽器的cookies(用cookies實現session),則在2.php能正常輸出
情景2:禁用用瀏覽器的cookies且在php.ini開啟session.use_trans_sid=1,通過1.php第一連線過去顯示不了session的值,但第二個連線則正常顯示。(說明url回寫正常執行)