詳解cookie、session和HTTP快取

魔爭發表於2018-02-08

1. cookie

1.1 什麼是 cookie?

HTTP Cookie(也叫Web Cookie或瀏覽器Cookie)是伺服器傳送到使用者瀏覽器並儲存在本地的一小塊資料,它會在瀏覽器下次向同一伺服器再發起請求時被攜帶併傳送到伺服器上。

cookie,指網站為了辨別使用者身份而儲存在使用者本地終端上的資料。cookie 本質上是 HTTP 的一個內容(請求頭)。
在前端工作中,可以這麼理解 cookie:

  • cookie 是瀏覽器訪問伺服器後,伺服器傳給客戶端的一段資料。
  • 瀏覽器將 cookie 儲存下來,一般情況下不會刪除。
  • 瀏覽器每次訪問返回 cookie 的伺服器時,都會在請求頭(請求的第二部分)中帶入這段 cookie

1.2 cookie 的作用

cookie 一般有兩個作用:

  1. 識別使用者身份
    當瀏覽器 A 首次訪問了 伺服器 a.com 之後,a.com 伺服器會立刻返回一段資料「uid=1」(cookie)給瀏覽器 A,瀏覽器 A 獲得 cookie 之後,將 cookie 儲存下來。每一個cookie都是不一樣的。 當瀏覽器 A 再次訪問伺服器 a.com 時,會帶上「uid=1」 (cookie), 伺服器 a.com 會識別 A 的請求頭中的 cookie,從而區別出瀏覽器 A 的身份。

可以這麼理解:瀏覽器訪問過伺服器之後,伺服器就會傳送一個特殊的身份資訊給瀏覽器。當瀏覽器再次訪問伺服器時,會強制要求帶上身份資訊,而伺服器也可以通過自己頒發出去的身份資訊來識別瀏覽器,而這個身份資訊就是cookie。 活生生的例子:

學校的門卡:第一次去學校,沒有門卡。然後,學校給發了一個門卡,門卡里面有你的姓名、班級等資訊。學校規定,有門卡的必須帶門卡上學,你帶上門卡到學校門口刷卡,學校就能知道你是誰,你來上課了。

  1. 記錄歷史
    cookie 本質上是一段資料,既然是資料,那麼就能記錄東西。在前端領域中,js 可以修改儲存在本地中的 cookie,往 cookie 中新增或刪除資料,從而記錄下使用者的操作,如:加入購物車、瀏覽歷史等等。
    由於瀏覽器不會隨便刪除 cookie,所以下次開啟網站的時候,cookie 依然還儲存著上次的cookie,就能知道歷史操作。

1.3 cookie 在瀏覽器與伺服器之間的交流過程

接下來,以client指代客戶端,server指代服務端,說明一個 cookie 的整個作用機制:

  1. 產生 cookie:client 第一次訪問 server,server 在響應頭中設定一個 cookie 返回給 client,cookie 的內容為要儲存的資料
  2. 儲存 cookie:client 在接收到 server 返回的 cookie 後,將 cookie 儲存下來,並給cookie一個有效期,過了有效期,cookie 就會失效。
  3. 傳遞 cookie:client 再次訪問 server 將會在請求頭中帶上儲存的 cookie,將 cookie 傳遞到 server
  4. 解析 cookie:server 得到 client 傳遞的 cookie 之後,會解析 cookie,然後將相應的資訊返回給 client
    在 cookie 沒有失效之前,cookie 的使用都是圍繞2,3,4三部分來進行的,第1步一般只需要進行一次。

1.4 cookid 存在的問題:

  1. cookie 基於瀏覽器本地儲存資料,因此,只有在儲存了 cookie 的那個瀏覽器上能夠使用該 cookie。同一裝置不同瀏覽器之間,cookie 不通用。
  2. cookie 的儲存大小有限制: 4KB 左右。
  3. cookie 存在C盤的一個檔案中,不同瀏覽器儲存路勁不一樣。
  4. cookie 是可以被使用者手動修改的。
  5. cookie 的有效期:預設有效期20分鐘左右。可以通過後端強制設定有效期,如自動登入時間。
  6. cookie 的同源策略:cookie 同樣也有同源策略,不過與 ajax 略微不用。ajax 需要完全同源,而 cookie 只需要同一父級域名即可。 比如: 請求 qq.com 下的資源時,會帶上 qq.com 對應的 cookie,不會帶上 baidu.com 的 cookie; 請求 v.qq.com 下的資源時,瀏覽器不僅會帶上 v.qq.com 的 cookie,還會帶上 qq.com 的cookie。在這裡,qq.com 就是 v.qq.com 的父級域名。
    需要特別注意的一點是:在瀏覽器的認知中,www.qq.com和qq.com是兩個不同的域名。因此,www.qq.com 不是 v.qq.com 的父域名,qq.com才是。

由於 cookie 是__明文__儲存在客戶端的資料,可能會被客戶端修改,存在資訊洩露的風險,所以,需要一種比 cookie 更加安全的儲存方式來儲存資料。session 就是解決安全問題的方法。

2. session

2.1 什麼是 session?

來自維基百科的解釋:

在電腦科學領域來說,尤其是在網路領域,會話(session)是一種持久網路協議,在使用者(或使用者代理)端和伺服器端之間建立關聯,從而起到交換資料包的作用機制,session在網路協議(例如telnet或FTP)中是非常重要的部分。

個人理解: session 是一種在伺服器端儲存資料的機制。伺服器通過讀取瀏覽器傳送的 cookie 和 伺服器端的 session 來交換資料。
不同於 cookie,session儲存在伺服器端,不同的語言儲存方式不一樣:

  • java,儲存於伺服器記憶體中,重啟伺服器,session 消失
  • php,儲存於伺服器檔案中,重啟伺服器,session 依然存在
  • nodejs,儲存於伺服器記憶體中,重啟伺服器,sessino 消失

2.2 session 的作用

session 的作用和 cookie 的作用大致相同。最大的不同點在於兩者的安全性和實現方式。文章下面會介紹。

2.3 session 的實現

依然將客戶端稱為 client,服務端成為 server,一起了解一下 session 的工作流程:

  1. 產生 sessionID:session 是基於 cookie 的一種方案,所以,首先要產生 cookie。client 第一次訪問 server,server 生成一個隨機數,命名為 sessionID,並將其放在響應頭裡,以 cookie 的形式返回給 client,client 以處理其他 cookie 的方式處理這段 cookie。大概是這樣:cookie:sessionID=135165432165
  2. 儲存 sessionID: server 將要儲存的資料儲存在相對應的 sessionID 之下,再將 sessionID 儲存到伺服器端的特定的儲存 session 的記憶體中(如 一個叫 session 的雜湊表)
  3. 使用 session: client 再次訪問 server,會帶上首次訪問時獲得的 值為 sessionID 的cookie,server 讀取 cookie 中的 sessionID,根據 sessionID 到儲存 session 的記憶體尋找與 sessionID 匹配的資料,若尋找成功就將資料返回給 client。

session 與 cookie 的區別

  1. session 在伺服器端,cookie 在客戶端。
  2. session 使用者無法檢視和修改,cookie 使用者可以檢視修改。
  3. session 和 cookie 的儲存容量不同。
  4. session 的實現依賴於 sessionID,而 sessionID 又儲存在 cookie 上,所以,可以這麼說:session 是基於 cookie 實現的一種資料儲存方式。

3. localStorage

3.1 localStorage 是什麼?

localStorage 是 HTML5 提供的一個 API。
localStorage 的實質是一個hash(雜湊表),是一個存在於瀏覽器上的 hash(雜湊表)。 localStorage 提供了幾個 API 來新增、讀取、刪除 localStorage:

  • localStorage.setItem(key,value) 往 hash 中新增 key: value 的資料
localStorage.setItem('姓名','蕭XX')
console.log(localStorage) // Storage {姓名: "蕭XX", length: 1}
複製程式碼
  • localStorage.getItem(key) 讀取 hash 中 key 的值
localStorage.getItem('姓名')  // "蕭XX"
複製程式碼
  • localStorage.removeItem(key) 刪除 hash 中的 key
localStorage.removeItem('姓名')
console.log(localStorage)  // Storage {length: 0}
複製程式碼
  • localStorage.clear() 刪除整個 localStorage
localStorage.clear()
console.log(localStorage)  // Storage {length: 0}
複製程式碼

3.2 localStorage 的作用

localStorage 是一個儲存於客戶端的雜湊表,可以用來本地儲存一些資料。

  1. 變數持久化儲存 js 中的變數都是存在記憶體中的,一旦重新整理頁面,記憶體釋放之後,所有變數的值全部會被重新初始化。
    而 localStorage 儲存在本地,不會因為重新整理而釋放,所以,可以使用 localStorage 來實現變數的持久化儲存
let a = localStorage.getItem('a')

if(!a){
    a = 0
}else{
    a = (+a) + 1
}
console.log(a)   // 0

localStorage.setItem('a', a)

console.log(localStorage.getItem('a'))  // 0 , 變數 a 被儲存到 localStorage 中了

// 重新整理頁面,這時候會列印出 兩行 1 ,說明變數 a 的值被讀取之後又重新賦值了
複製程式碼

典型應用:
記錄是否提示過:如果不使用localStorage 持久化儲存,每次重新整理頁面都會彈出提示

let already = localStorage.getItem('提示')
if(!already){
    alert("這是我們的提示內容")
    localStorage.setItem('提示'true)
}
複製程式碼

3.3 localStorage 的特點

  1. localStorage 與 HTTP 沒有任何關係。
  2. HTTP 不會帶上 localStorage 的值,因為兩者沒有一毛錢關係。
  3. 只有相同域名的頁面才能互相讀取 localStorage,同源策略與 cookie 一致
  4. 不同的瀏覽器,對每個域名 localStorage 的最大儲存量的規定不一樣,超出儲存量會被拒絕。Chrome 10MB 左右
  5. 常用場景:記錄一些不敏感的資訊(不涉及安全的資訊)
  6. localStorage 理論上永久有效,除非使用者清理快取。

4. sessionStorage(會話儲存)

sessionStorage 的所有性質基本上與 localStorage 一致,唯一的不同區別在於:
sessionStorage 的有效期是頁面會話持續,如果頁面會話(session)結束(關閉頁面),sessionStorage 就會消失。而 localStorage 則會一直存在。

5. Cache-Control

5.1 Cache-Control 是什麼?

Cache-Control 通用訊息頭被用於在http 請求和響應中通過指定指令來實現快取機制。快取指令是單向的, 這意味著在請求設定的指令,在響應中不一定包含相同的指令。

理解:
Cache-Control 第一次請求資源時,將資源快取下來。告訴瀏覽器再次需要該資源時,不要向伺服器請求資源,而是直接使用快取的資源。Cache-Control 是控制快取的 HTTP 內容(請求頭/響應頭)。

5.2 Cache-Control 怎麼使用?

Cache-Control 有 2 種使用方式:

  1. 以請求頭形式使用 客戶端可以在 HTTP 請求中使用 Cache-Control 指令
request.setHeader('Cache-Control','max-age=99999999') // 將此次請求的資源快取 99999999 秒
複製程式碼
  1. 以響應頭形式使用 伺服器(node版本)可以在響應請求時,設定 Cache-Control
response.setHeader('Cache-Control','max-age=99999999')  // 將此次請求的資源快取 99999999 秒
複製程式碼

5.3 Cache-Control 的作用

Cache-Control 使用快取機制,用來縮短二次訪問的響應時間,提高頁面響應效能,實現web效能優化。

5.4 Cache-Control 的實際使用

  1. 首頁不設定快取 Cache-COntrol 的快取時間設定一般都會是一個很長的時間,如果所有頁面全部設定快取,那麼使用者訪問頁面時,不會向伺服器請求任何資源。那麼,如果頁面開發者在使用者快取的有效期內釋出了新版本,由於使用者不會請求資源,所以使用者得不到新版本的資源,也就無法進行版本更新。首頁不使用快取,就是為了給更新留下入口。
  2. 快取更新 在 Cache-Control 的快取機制是基於 URL 的,只有相同的 URL 才會使用快取。那麼,如果想要進行版本更新,讓瀏覽器發起新的資源請求,就只需要改動資源的 URL,瀏覽器就會重新請求資源。
<script src='./js/main.js?v=1.0'></script>  <!-- 第一個版本的js,可以被快取 -->  
<script src='./js/main.js?v=1.1'></script>  <!-- 第二個版本的js,瀏覽器會再次請求資源 -->
複製程式碼

在真正的開發中,資源版本號一般都是使用摘要演算法生成的字串。(md5演算法轉換的字串)

6. Expires

Expires 是以前版本的快取控制,如果你設定了 Cache-Control,那麼 Expires 會失效。

Expires 頭指定了一個日期/時間, 在這個日期/時間之後,HTTP響應被認為是過時的;

Expires 工作原理與 Cache-Control 差不多。區別不同的是,Expires 設定的快取時間是一個時間點,過了這個時間點,快取就過期。

Expires: Expires 使用的是本地時間,會受本地事件影響。

response.setHeader('Expires: Wed', '21 Oct 2019 07:28:00 GMT')  // Expires 使用格林梅治事件,GMT
複製程式碼

Cache-Control 和 Expires ,優先使用 Cache-Control

7. Etag

7.1 Etag 是什麼?

ETag HTTP響應頭是資源的特定版本的識別符號。 Etag 是 HTTP 的內容,通過匹配識別符號來判斷資源是否需要下載。

7.2 Etag 使用

  1. 瀏覽器第一次訪問伺服器資源時,伺服器端返回一個 Etag 響應頭,Etag 的值為資源 MD5 摘要的值(告訴瀏覽器,你這次下載的資源是什麼樣子的)。
  2. 當瀏覽器再次請求資源時,會將 Etag 響應頭的值放在一個 if-None-Match 請求頭之中,傳送給伺服器(將瀏覽器已經擁有的資源的樣子告訴伺服器)
  3. 伺服器獲取 if-None-Match 的值,再將該值與請求檔案的 md5 值進行匹配,若兩者匹配,則返回 304,代表資源沒有改變,不用再次下載。(伺服器比對自己的資源的樣子與瀏覽器傳送過來的樣子,如果兩者一致,就讓瀏覽器用自己的資源,不要再讓伺服器傳送資源了)

8. last-Modified

last-Modified 是什麼?

The Last-Modified 是一個響應首部,其中包含源頭伺服器認定的資源做出修改的日期及時間。

通俗地講:last-Modified 是一個響應頭,它的值是:資源最後一次被修改的時間。

last-Modefied 的使用

  1. 瀏覽器第一次訪問伺服器上的某個資源,伺服器端返回一個 last-Modefied 響應頭,值為瀏覽器請求的資源的最後一次修改時間(告訴瀏覽器,伺服器最後一次修改資源是什麼時候)
  2. 瀏覽器再次訪問伺服器時,會有一個名稱為 If-Modified-Since 的請求頭,值為伺服器返回的 last-Modified 的值(告訴伺服器,瀏覽器得到的最後一次修改資源時間)
  3. 伺服器讀取 If-Modified-Since 的值,將其與自身資源的最後一次修改時間進行比對,若兩者一致,就返回 304,代表資源沒有改變過,不用再次下載。(伺服器比對雙方的修改時間,若時間一致說明沒有修改過,就讓瀏覽器使用自己的資源,不用再下載資源。)

9. 彩蛋(不依賴 cookie 的 session)

9.1 原理

session 實現關鍵是 sessionID,只需要將 sessionID 傳遞給瀏覽器,瀏覽器在請求的時候再將 sessionID 傳遞給伺服器,就可以實現 session。所以,可以使用在 URL 中插入查詢引數的方式來實現 sessionID 的傳遞。

9.2 例子

第一步:服務端(node)直接將 sessionID 用 JSON 傳給前端

let sessionID = Math.random() * 10000000 
response.write(`{"sessionID":${sessionID}}`)
複製程式碼

第二步:前端處理:將獲取的 JSON 解析出來,獲取 sessionID,跳轉頁面的時候,將sessionID寫到查詢引數之中

let object = JSON.parse(request.responseText)
// 將 sessionID 存到 localStorage 中備用
localStorage.setItem('sessionID',object.sessionID)
window.location.href = `/?sessionID=${}`
複製程式碼

第三步:伺服器端獲取查詢引數,使用查詢引數之中的 sessionID 去使用 session

ley sessionID = query.sessionID  // query 為查詢引數
複製程式碼

相關文章