1. 程式人生 > >淺析http協議、cookies和session機制、瀏覽器快取

淺析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()的時候,則不管是否有expirescache-controllast-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

Firefox11

Maxthon3

點選重新整理鍵

重發請求,返回200狀態

重發請求,返回200狀態

重發請求,返回200狀態

重發請求,返回200狀態

位址列回車

重發請求,返回200狀態

重發請求,返回200狀態

重發請求,返回200狀態

重發請求,返回200狀態

點選後退鍵

同上

同上

同上

同上

(2)   header(“cache-control: no-cache”)

IE9

Google17.0

Firefox11

Maxthon3

點選重新整理鍵

重發請求,返回200狀態

重發請求,返回200狀態

重發請求,返回200狀態

重發請求,返回200狀態

位址列回車

重發請求,返回200狀態

重發請求,返回200狀態

重發請求,返回200狀態

重發請求,返回200狀態

點選後退鍵

同上

From cache

From cache

同上

(3)   header(“cache-control:bublic”)

IE9

Google17.0

Firefox11

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

Firefox11

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

Firefox11

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

Firefox11

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

Firefox11

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回寫正常執行)