cookie知識總結

最近,清除cookie的時候,使用了document.cookie = '',而後就發現沒有做用。因而,帶着上述疑問,找個時間研究了下cookie。本文主要是基於我遇到問題,而後解決問題的思路寫的,並非以從0到1講述cookie的思路寫的。html

爲了解決上述問題,以及在解決問題中的發散思考,主要經過如下問題:算法

  1. document.cookie = ''可否用來清除cookie;
  2. 同時設置了expires和max-age,哪一個有效;
  3. 假設已經有一個名爲hello的cookie,再次設置名爲hello的cookie是新增仍是修改
  4. 如何把字符串形式的cookie改爲json的形式;
  5. 若是設置了多個同名cookie,獲取cookie時以哪一個爲準;

學習瞭如下知識點:json

  1. cookie賦值語法;
  2. cookie賦值算法;
  3. cookie取值算法。

預備知識

本文參考了英文文檔,因此提早說明如下英文對應的中文翻譯:segmentfault

  1. cookie store: cookie表,瀏覽器內部用於存儲cookie信息的數據結構;
  2. cookie name: 一個cookie的名稱;
  3. cookie value: 一個cookie的值;
  4. cookie attribute: 一個cookie的屬性,cookie除了基本的名稱和值以外,還有用來進一步描述cookie的屬性,好比Expires,Max-Age,Domain,Path,Secure HttpOnly等。這些屬性是提供給用戶設置cookie的時候使用的,並非cookie內部實際存儲的屬性。

其餘預備知識:數組

每一個cookie在實際存儲的時候,會有下述字段:name, value, expiry-time, domain, path, creation-time, last-access-time, persistent-flag, host-only-flag, secure-only-flag, and http-only-flag。能夠看出這些字段和設置cookie時的屬性並非一對一關係,其中,瀏覽器

  1. expiry-time是經過Expires和Max-Age得出來的;
  2. persistent-flag和是否設置了過時時間有關;
  3. creation-time(建立時間)和last-access-time(最後一次訪問時間)是瀏覽器自動存儲的屬性。

document.cookie賦值語法

這部份內容主要是基於如下問題:cookie

  1. document.cookie = ''可否用來清除cookie?

首先看下document.cookie的定義:session

The Document property cookie lets you read and write cookies associated with the document. It serves as a getter and setter for the actual values of the cookies.

從上面能夠看到,document.cookie只是提供了一個getter和setter方法來訪問和設置cookie,並非直接修改cookie的值。什麼意思呢?數據結構

咱們知道,JS提供了兩種類型的屬性,data propertyaccessor property。分別舉個例子對比看下:dom

// data property
var obj1 = {
    hello: 'name'
}
Object.getOwnPropertyDescriptor(obj1, 'hello')

image.png

// accessor property
var realHello = '' // 實際存儲obj2.hello的變量
var obj2 = {
    get hello () {
        return 'custom get ' + realHello
    },
    set hello (val) {
        realHello = 'custom set ' + val
    }
}
obj2.hello = 'name'

console.log(obj2.hello)
Object.getOwnPropertyDescriptor(obj2, 'hello')

image.png

能夠看到用於描述obj1hello屬性的是valuewritable,用於描述obj2hello屬性的是getset。簡單的說,獲取和修改data property是直接的,獲取和修改accessor property是間接的,能夠經過該屬性的getset作自定義處理。咱們熟悉的Vue 2.0響應式的data的屬性就是accessor property

document的cookie屬性也是accessor property。驗證的時候發現一個有意思的問題,最終是在document的原型上找到的屬性描述符,之後有時間了研究下:

Object.getOwnPropertyDescriptor(document, 'cookie') // undefined
Object.getOwnPropertyDescriptor(document.__proto__.__proto__, 'cookie')

image.png

咱們看下document.cookie的語法:

document.cookie = newCookie;

修改的時候,newCookie的格式須要知足以下形式,且一次只能設置/更新單個cookie:

In the code above, newCookie is a string of form key=value. Note that you can only set/update a single cookie at a time using this method.

舉個例子,打開MDN document.cookie頁面。添加一個cookie,每一個cookie後面能夠添加和該cookie相關的一系列屬性,經過分號;分割:

// cookie默認過時時間是session,也就是瀏覽器關閉的時候,該cookie就失效了
document.cookie = 'hello1=world1;'

// 使用max-age設置過時時間:max-age以秒爲單位,設置1個小時以後過時:3600 = 60 * 60
document.cookie = 'hello2=world2;max-age=3600'

// 使用expires設置過時時間:expires是GMT形式的日期格式,設置1小時以後過時
var current = new Date()
current.setTime(current.getTime() + 3600000) // setTime單位是ms,3600000 = 60 * 60 * 1000
document.cookie = `hello3=world3;expires=${current.toUTCString()}`

Chrome Application Cookies內容截圖以下:
image.png

既然document.cookie只能設置/更新單個cookie,當賦值爲空字符串的時候,那就是什麼都沒有作。也就是document.cookie = ''不會產生任何做用,不能用於清除cookie。

那麼,
問:如何使用document.cookie清除一個cookie呢?
答:能夠在設置cookie的時候,設置一個已通過去的過時時間:

// 使用expires設置一個過去一小時以前的時間
document.cookie = 'hello2=world2;max-age=-3600'

// 使用expires設置一個過去一小時以前的時間
var current = new Date()
current.setTime(current.getTime() - 3600000)
document.cookie = `hello3=world3;expires=${current.toUTCString()}`

如今,只剩下了一個hello1:
image.png

注:瀏覽器開發者工具是提供了清除cookie的操做的,上述主要討論的是如何經過js清除cookie。

cookie賦值算法

這部份內容主要是基於如下兩個問題:

  1. 同時設置了expires和max-age,哪一個有效?
  2. 假設已經有一個名爲hello的cookie,再次設置名爲hello的cookie的時候是新增仍是修改?

問題1: 同時設置了expires和max-age,哪一個有效?

前面的例子中,我使用了expires和max-age,個人疑問是感受這兩個屬性是幹了同樣的事情,若是我同時設置了這兩個屬性的話,哪一個生效呢?

帶着這個疑問,找到了RFC 6265 - HTTP State Management Mechanism。這個規範裏面明肯定義瞭如何設置/更新cookie。

就下面的例子,咱們按照文檔找一下答案:

var current = new Date()
console.log('now: ', current.toUTCString()) // now:  Sat, 20 Feb 2021 11:41:02 GMT
current.setTime(current.getTime() + 3600000)
// 使用expires設置爲一小時以後過時:60 * 60 * 1000 = 3600000ms
// 使用max-age設置爲兩小時以後過時:2 * 60 * 60 = 7200s
document.cookie = `hello=world;expires=${current.toUTCString()};max-age=7200` // hello=world;expires=Sat, 20 Feb 2021 12:41:02 GMT;max-age=7200

這部分的內容主要是在第五部分的5.2。首先,它會把設置的這個值經過逗號分割成多個部分,而後經過等號把每一個部分分紅key-value的形式:

hello=world;expires=Sat, 20 Feb 2021 12:41:02 GMT;max-age=7200
// 變爲
hello=world // 第一個分號前面的是這個cookie的name和value,後面的是這個cookie的屬性
expires=Sat, 20 Feb 2021 12:41:02 GMT // 屬性
max-age=7200 // 屬性
// 變爲
hello: world
expires: expires=Sat, 20 Feb 2021 12:41:02 GMT
max-age: 7200

而後看每一個屬性,文檔中5.2.*部分,算法會解析每一個key-value,並放在一個cookie屬性列表cookie-attribute-list裏面,分析屬性的時候不區分大小寫,expires和max-age轉化爲:

Expires: expires=Sat, 20 Feb 2021 12:41:02 GMT
Max-Age: expires=Sat, 20 Feb 2021 13:41:02 GMT

屬性都分析完以後,咱們能夠獲得和每條cookie相關的三個部分:cookie-name(cookie名),cookie-value(cookie值),cookie-attribute-list(cookie屬性列表)。而後進入設置階段,關鍵在5.3部分的第三步:
image.png
首先看上面生成的cookie屬性列表裏面是否包含Max-Age,若是包含,把cookie的過時時間設置成最後一個Max-Age的值;若是沒有Max-Age,纔會查看是否包含Expires,若是包含,把cookie的過時時間設置成最後一個Expires的值。這裏last attribute的意思是咱們在給cookie賦值的時候,是能夠寫多個max-age的,這個時候取最後一個語法有效的max-age。

因此,簡單的說:當expires和max-age同時出現的時候,max-age的優先級更高。

Chrome截圖也證實了max-age優先級更高:
image.png

假設已經有一個名爲hello的cookie,再次設置名爲hello的cookie是新增仍是修改?

咱們接着往下看5.3部分的第十一、12步:
image.png
在11步中,首先判斷已有cookie中是否有和本cookie同時具備相同的name(名稱)domain(域)path(路徑)的cookie。
11.1 若是有,標記爲old-cookie;
11.2 若是新cookie不是經過http的方式設置的,而且old-cookie設置了只能用於http,忽略新cookie;
11.3 把新cookie的creation-time(建立時間)字段更新爲old-cookie的建立時間;
11.4 從cookie表中移除old-cookie;
12 把新cookie插入到cookie表裏面。

因此,能夠得出:判斷是新增仍是修改的標準是cookie表中是否存在和新cookie的name,domain和path屬性都同樣的cookie。

在設置cookie的時候,若是設置結果和你想要的不符,能夠按照上面例子的思路,查看RFC 6265 - HTTP State Management Mechanism文檔的5.2和5.3部分。

cookie取值算法

這部份內容主要是基於如下兩個問題:

  1. 如何把字符串形式的cookie改爲json的形式?
  2. 若是設置了多個同名cookie,獲取cookie時以哪一個爲準?

如何把字符串形式的cookie改爲json的形式?

之因此會遇到這個問題,主要是解決下面這個場景:
若是經過WebSocket發送請求,在鏈接創建的時候,是能夠獲取cookie信息的。可是,一旦鏈接創建成功,WebSocket一直鏈接的時候,發送的數據是不會攜帶cookie信息的。若是在鏈接創建以後,用戶作了登出操做,這個時候其實不該該再響應用戶請求,而且斷開鏈接。

這時,問題來了,如何判斷當用戶登出的時候再也不響應用戶請求呢?
其實和http同樣,每次請求帶上cookie信息就能夠了。只不過發送http請求的時候,瀏覽器幫咱們作了這部分工做。WebSocket的話,就得咱們手動添加上了。

咱們能夠經過document.cookie獲取cookie值,可是該值是一個表示全部cookie的字符串。爲了獲取某個cookie的值,咱們就得本身解析。要想解析,就得知道cookie字符串的取值規則。這部份內容主要是RFC 6265 - HTTP State Management Mechanism文檔的5.4部分

image.png

  1. 經過步驟4的第2步,能夠知道不一樣的cookie是經過分號和空格; 拼接起來的;
  2. 經過步驟4的第1步,能夠知道最終結果中只會出現cookie的name和value,cookie的屬性並不會出現。name和value是經過等號=拼接起來的;

因此,解析cookie的爲代碼能夠表示以下:

var cookieStr = document.cookie
var cookieArray = cookieStr.split('; ') // 得到每一個cookie的字符串形式組成的數組
var cookieObj = {}
cookieArray.forEach(item => {
    var index = item.indexOf('=') // 獲取每一個cookie字符串的key和value
    if (index !== -1) { // 沒有使用split的緣由是value裏面也是能夠包含=號的
        cookieObj[item.slice(0, index)] = item.slice(index + 1)
    }
})
console.log(cookieObj)

若是設置了多個同名cookie,獲取cookie時以哪一個爲準?

例如,有下面幾個cookie:

document.cookie = 'hello4=world4;'
document.cookie = 'hello5=world;'
document.cookie = 'hello5=worldShortPath;Path=/en-US'
document.cookie = 'hello6=world6;'

Chrome截圖以下:
image.png

這部份內容仍是在RFC 6265 - HTTP State Management Mechanism文檔的[5.4部分],只不過是在第2步。首先第1步是從cookie store(cookie列表)中找到和請求地址相關的全部cookie。而後執行第2步:
image.png

  1. 經過步驟2的第1步,能夠知道具備更長path屬性的cookie是放在前面的;
  2. 經過步驟2的第2步,能夠知道path屬性長度相同的時候,creation-times建立時間越早的放在前面。

綜上,全部同名的cookie都會出如今最終的cookie字符串中。而且,cookie是先按照path屬性就行排序,而後按照creation-times建立時間屬性進行排序。可是,步驟二下面有註釋,這種排序方式是實踐得出的比較好的排序方式,可是瀏覽器不必定按照這種排序方式實現。

就上面的例子,咱們在設置完cookie以後,在控制檯中查看document.cookie:

console.log(document.cookie) // hello4=world4; hello5=world; hello6=world6; hello5=worldShortPath

能夠看到hello5的路徑最短,因此放在最後,其餘的按照設置順序,也就是建立時間排序。

總結

在cookie賦值語法部分,咱們知道document.cookie = ''不能用來清除cookie;
在cookie賦值算法部分,咱們知道:

  1. 同時設置了expires和max-age,max-age有效;
  2. 假設已經有一個名爲hello的cookie,再次設置名爲hello的cookie的時候,若是name,domain和path屬性徹底相同就是修改,不然是新增;

在cookie取值算法部分,咱們知道:

  1. 如何把字符串形式的cookie改爲json的形式的時候,能夠先經過; 分割一次,在經過=分割一次;
  2. 若是設置了多個同名cookie,全部同名的cookie都會出如今最終的cookie字符串中,按照path屬性的長度和creation-times排序。

若是你遇到和cookie賦值語法、賦值算法和取值算法相關的問題,能夠參照上述部分遇到的問題的解決思路去看看可否解決問題。

若有錯誤,歡迎留言討論。

參考連接

  1. RFC 6265 - HTTP State Management Mechanism
  2. MDN document.cookie
相關文章
相關標籤/搜索