Cookie 在前端中的實踐

本篇文章的主題,用 Node.js 搭一個服務,來看看 Cookie 的實際應用場景html

環境配置

咱們新建一個文件 main.js,並在 main.js 寫入如下代碼:node

const express = require('express')
const app = express()

app.listen(3000, err => {
  if (err) {
    return console.log(err)
  }
  console.log('---- 打開 http://localhost:3000 吧----')
})

app.get('/', (req, res) => {
  res.send('<h1>hello world!</h1>')
})
node main.js

// 一個本地服務就跑起來了,如今打開 http://localhost:3000
// 就能夠看到一個大大的 hello world!

Cookie 是怎樣工做的

在介紹 Cookie 是什麼以前,咱們來看看 Cookie 是如何工做的:git

1. 首先,咱們假設當前域名下仍是沒有 Cookie 的
2. 接下來,瀏覽器發送了一個請求給服務器(這個請求是還沒帶上 Cookie 的)
3. 服務器設置 Cookie 併發送給瀏覽器(固然也能夠不設置)
4. 瀏覽器將 Cookie 保存下來
5. 接下來,之後的每一次請求,都會帶上這些 Cookie,發送給服務器

驗證

咱們來驗證一下github

// 修改 main.js

app.get('/', (req, res) => {
  // 服務器接收到請求,在給響應設置一個 Cookie
  // 這個 Cookie 的 name 爲 testName
  // value 爲 testValue
  res.cookie('testName', 'testValue')
  res.send('<h1>hello world!</h1>')
})

// 保存以後,重啓服務器
// node main.js

如今打開 http://localhost:3000express

  1. 咱們看到 Request Headers 並無 Cookie 這個字段
  2. 可是 Response Headers 有了 Set-Cookie 這個字段

如今咱們刷新一下頁面,至關於從新向 http://localhost:3000/ 這個地址發起了一次請求。segmentfault

如今咱們就能夠看到 Cookie 字段已經帶上了,再刷新幾回看 Cookie 也仍是在的。瀏覽器

document.cookie

JS 提供了獲取 Cookie 的方法:document.cookie,咱們先去設置多幾個 Cookie。安全

app.get('/', (req, res) => {
  res.cookie('testName0', 'testValue0')
  res.cookie('testName1', 'testValue1')
  res.cookie('testName2', 'testValue2')
  res.cookie('testName3', 'testValue3')
  res.send('<h1>hello world!</h1>')
})

咱們能夠看到,Cookie 就是一段字符串。但這個字符串是有格式的,由鍵值對 key=value 構成,鍵值對之間由一個分號一個空格隔開。服務器

什麼是 Cookie

說了這麼多,你們應該知道 Cookie 是什麼吧。整理一下有如下幾個點:cookie

  • Cookie 就是瀏覽器儲存在用戶電腦上的一小段文本文件
  • Cookie 是純文本格式,不包含任何可執行的代碼
  • Cookie 由鍵值對構成,由分號和空格隔開
  • Cookie 雖然是存儲在瀏覽器,可是一般由服務器端進行設置
  • Cookie 的大小限制在 4kb 左右

Cookie 的屬性選項

每一個 Cookie 都有必定的屬性,如何時失效,要發送到哪一個域名,哪一個路徑等等。在設置任一個 Cookie 時均可以設置相關的這些屬性,固然也能夠不設置,這時會使用這些屬性的默認值。

expires / max-age

expires / max-age 都是控制 Cookie 失效時刻的選項。若是沒有設置這兩個選項,則默認有效期爲 session,即會話 Cookie。這種 Cookie 在瀏覽器關閉後就沒有了。

expires

expires 選項用來設置 Cookie 什麼時間內有效,expires 實際上是 Cookie 失效日期。
expires 必須是 GMT 格式的時間(能夠經過 new Date().toGMTString() 或者 new Date().toUTCString() 來得到)

app.get('/', (req, res) => {
  // 這個 Cookie 設置十秒後失效
  res.cookie('testName0', 'testValue0', {
    expires: new Date(Date.now() + 10000)
  })
  // 這個 Cookie 不設置失效時間
  res.cookie('testName1', 'testValue1')
  res.send('<h1>hello world!</h1>')
})

上面的代碼服務器設置了兩個 Cookie,一個設置了失效刻,另一個沒有設置,也就是默認的失效時刻 session。如今咱們重啓服務而且刷新一下頁面。

如今響應頭部已經加上了響應的設置失效時刻的字段了。在控制檯輸入下面的代碼。

console.log(`如今的 cookie 是:${document.cookie}`)
setTimeout(() => {
  console.log(`5 秒後的 cookie 是:${document.cookie}`)
}, 5000)
setTimeout(() => {
  console.log(`10 秒後的 cookie 是:${document.cookie}`)
}, 10000)

因此,Cookie 的失效時刻到了以後,經過 document.cookie 就訪問不到這個 Cookie 了,固然之後發送請求也不會再帶上這個失效的 Cookie 了。

max-age

expires 是 http/1.0 協議中的選項,在新的 http/1.1 協議中 expires 已經由 max-age 選項代替,二者的做用都是限制 Cookie 的有效時間。expires 的值是一個時間點 (Cookie 失效時刻 = expires),而 max-age 的值是一個以爲單位時間段 (Cookie 失效時刻 = 建立時刻 + max-age)

// 設置 max-age,就是設置從 cookie 建立的時刻算起
// 再過多少秒 cookie 就會失效
app.get('/', (req, res) => {
  res.cookie('testName0', 'testValue0', {
    // express 這個參數是以毫秒來作單位的
    // 實際發送給瀏覽器就會轉換爲秒
    // 十秒後失效
    maxAge: 10000
  })
  res.cookie('testName1', 'testValue1')
  res.send('<h1>hello world!</h1>')
})

優先級

若是同時設置了 max-age 和 expires,以 max-age 的時間爲準。

app.get('/', (req, res) => {
  res.cookie('name0', 'value0')
  res.cookie('name1', 'value1', {
    expires: new Date(Date.now() + 30 * 1000),
    maxAge: 60 * 1000
  })
  res.cookie('name2', 'value2', {
    maxAge: 60 * 1000
  })
  res.send('<h1>hello world!</h1>')
})

domain 和 path

namedomainpath 能夠標識一個惟一的 Cookie。domainpath 兩個選項共同決定了 Cookie 什麼時候被瀏覽器自動添加到請求頭部中發送出去。具體是什麼原理請看 Cookie 的做用域和做用路徑 這個章節。

若是沒有設置這兩個選項,則會使用默認值。domain 的默認值爲設置該 Cookie 的網頁所在的域名,path 默認值爲設置該 Cookie 的網頁所在的目錄。

secure

secure 選項用來設置 Cookie 只在確保安全的請求中才會發送。當請求是 HTTPS 或者其餘安全協議時,包含 secure 選項的 Cookie 才能被保存到瀏覽器或者發送至服務器。

默認狀況下,Cookie 不會帶 secure 選項(即爲空)。因此默認狀況下,不論是 HTTPS 協議仍是 HTTP 協議的請求,Cookie 都會被髮送至服務端。

httpOnly

這個選項用來設置 Cookie 是否能經過 js 去訪問。默認狀況下,Cookie 不會帶 httpOnly 選項(即爲空),客戶端是能夠經過 js 代碼去訪問(包括讀取、修改、刪除等)這個 Cookie 的。當 Cookie 帶 httpOnly 選項時,客戶端則沒法經過 js 代碼去訪問(包括讀取、修改、刪除等)這個 Cookie。

看看代碼吧,修改 main.js,保存重啓服務,刷新頁面。

app.get('/', (req, res) => {
  res.cookie('notHttpOnly', 'testValue')
  res.cookie('httpOnlyTest', 'testValue', {
    httpOnly: true
  })
  res.send('<h1>hello world!</h1>')
})

看圖,設置了 httpOnly 的 Cookie 多了一個勾。並且經過 document.cookie 沒法訪問到那個 Cookie。

在客戶端是不能經過 js 代碼去設置 一個 httpOnly 類型的 Cookie 的,這種類型的 Cookie 只能經過服務端來設置,發送請求的時候,咱們看到請求頭仍是會帶上這個設置了 httpOnly 的 Cookie,以下圖。

設置 Cookie

明確一點:Cookie 能夠由服務端設置,也能夠由客戶端設置。看到這裏相信你們均可以理解了吧。

服務端設置 Cookie

看回剛剛的那張圖,咱們設置了不少個 Cookie。

  • 一個 Set-Cookie 字段只能設置一個 Cookie,當你要想設置多個 Cookie,須要添加一樣多的 Set-Cookie 字段
  • 服務端能夠設置 Cookie 的全部選項:expires、domain、path、secure、HttpOnly

客戶端設置 Cookie

在網頁即客戶端中咱們也能夠經過 js 代碼來設置 Cookie。

設置

document.cookie = 'name=value'

能夠設置 Cookie 的下列選項:expires、domain、path,各個鍵值對之間都要用 ;空格 隔開

document.cookie='name=value; expires=Thu, 26 Feb 2116 11:50:25 GMT; domain=sankuai.com; path=/';

secure

只有在 https 協議的網頁中,客戶端設置 secure 類型的 Cookie 才能成功

HttpOnly

客戶端中沒法設置 HttpOnly 選項

刪除 Cookie

Cookie 的 name、path 和 domain 是惟一標識一個 Cookie 的。咱們只要將一個 Cookie 的 max-age 設置爲 0,就能夠刪除一個 Cookie 了。

let removeCookie = (name, path, domain) => {
  document.cookie = `${name}=; path=${path}; domain=${domain}; max-age=0`
}

Cookie 的做用域和做用路徑

做用域

在說這個做用域以前,咱們先來對域名作一個簡單的瞭解。

子域,是相對父域來講的,指域名中的每個段。各子域之間用小數點分隔開。放在域名最後的子域稱爲最高級子域,或稱爲一級域,在它前面的子域稱爲二級域。

如下圖爲例,news.163.comsports.163.com 是子域,163.com 是父域。

當 Cookie 的 domain 爲 news.163.com,那麼訪問 news.163.com 的時候就會帶上 Cookie;
當 Cookie 的 domain 爲 163.com,那麼訪問 news.163.comsports.163.com 就會帶上 Cookie

做用路徑

當 Cookie 的 domain 是相同的狀況下,也有是否帶上 Cookie 也有必定的規則。

在子路徑內能夠訪問訪問到父路徑的 Cookie,反過來就不行。

看看例子,仍是先修改 main.js

app.get('/parent', (req, res) => {
  res.cookie('parent-name', 'parent-value', {
    path: '/parent'
  })
  res.send('<h1>父路徑!</h1>')
})

app.get('/parent/childA', (req, res) => {
  res.cookie('child-name-A', 'child-value-A', {
    path: '/parent/childA'
  })
  res.send('<h1>子路徑A!</h1>')
})

app.get('/parent/childB', (req, res) => {
  res.cookie('child-name-B', 'child-value-B', {
    path: '/parent/childB'
  })
  res.send('<h1>子路徑B!</h1>')
})
下面這裏的 「域」 應該改成路徑



參考文章

相關文章
相關標籤/搜索