一、概述
Cookie 是服務(wù)器保存在瀏覽器的一小段文本信息,一般大小不能超過4KB。瀏覽器每次向服務(wù)器發(fā)出請求,就會(huì)自動(dòng)附上這段信息。
Cookie 主要保存狀態(tài)信息,以下是一些主要用途。
- 對話(session)管理:保存登錄、購物車等需要記錄的信息。
- 個(gè)性化信息:保存用戶的偏好,比如網(wǎng)頁的字體大小、背景色等等。
- 追蹤用戶:記錄和分析用戶行為。
Cookie 不是一種理想的客戶端儲(chǔ)存機(jī)制。它的容量很小(4KB),缺乏數(shù)據(jù)操作接口,而且會(huì)影響性能??蛻舳藘?chǔ)存應(yīng)該使用 Web storage API 和 IndexedDB。只有那些每次請求都需要讓服務(wù)器知道的信息,才應(yīng)該放在 Cookie 里面。
有關(guān)對Web storage的介紹,可以看這篇博客:sessionStorage 和 localStorage 的使用
每個(gè) Cookie 都有以下幾方面的元數(shù)據(jù)。
- Cookie 的名字
- Cookie 的值(真正的數(shù)據(jù)寫在這里面)
- 到期時(shí)間(超過這個(gè)時(shí)間會(huì)失效)
- 所屬域名(默認(rèn)為當(dāng)前域名)
- 生效的路徑(默認(rèn)為當(dāng)前網(wǎng)址)
舉例來說,用戶訪問網(wǎng)址 ?www.example.com
?,服務(wù)器在瀏覽器寫入一個(gè) Cookie。這個(gè) Cookie 的所屬域名為 ?www.example.com
?,生效路徑為根路徑 ?/
?。
如果 Cookie 的生效路徑設(shè)為? /user
?,那么這個(gè) Cookie 只有在訪問? www.example.com/use
?r 及其子路徑時(shí)才有效。以后,瀏覽器訪問某個(gè)路徑之前,就會(huì)找出對該域名和路徑有效,并且還沒有到期的 Cookie,一起發(fā)送給服務(wù)器。
用戶可以設(shè)置瀏覽器不接受 Cookie,也可以設(shè)置不向服務(wù)器發(fā)送 Cookie。 window.navigator.cookieEnabled 屬性返回一個(gè)布爾值,表示瀏覽器是否打開 Cookie 功能。
window.navigator.cookieEnabled // true
?document.cookie
? 屬性返回當(dāng)前網(wǎng)頁的 Cookie。
不同瀏覽器對 Cookie 數(shù)量和大小的限制,是不一樣的。一般來說,單個(gè)域名設(shè)置的 Cookie 不應(yīng)超過30個(gè),每個(gè) Cookie 的大小不能超過4KB。超過限制以后,Cookie 將被忽略,不會(huì)被設(shè)置。
兩個(gè)網(wǎng)址只要域名相同,就可以共享 Cookie。 注意,這里不要求協(xié)議相同。也就是說,?http://example.com
?設(shè)置的 Cookie,可以被 ?https://example.com
? 讀取。
二、Cookie 與 HTTP 協(xié)議
Cookie 由 HTTP 協(xié)議生成,也主要是供 HTTP 協(xié)議使用。
1、HTTP 回應(yīng):Cookie 的生成
服務(wù)器如果希望在瀏覽器保存 Cookie,就要在 HTTP 回應(yīng)的頭信息里面,放置一個(gè) ?Set-Cookie
? 字段。
Set-Cookie:foo=bar
上面代碼會(huì)在瀏覽器保存一個(gè)名為 foo 的 Cookie,它的值為 bar。
HTTP 回應(yīng)可以包含多個(gè) ?Set-Cookie
? 字段,即在瀏覽器生成多個(gè) Cookie。
HTTP/1.0 200 OK
Content-type: text/html
Set-Cookie: yummy_cookie=choco
Set-Cookie: tasty_cookie=strawberry
除了 Cookie 的值,Set-Cookie字段還可以附加 Cookie 的屬性。一個(gè)Set-Cookie字段里面,可以同時(shí)包括多個(gè)屬性,沒有次序的要求。
Set-Cookie: <cookie-name>=<cookie-value>; Expires=<date>
Set-Cookie: <cookie-name>=<cookie-value>; Max-Age=<non-zero-digit>
Set-Cookie: <cookie-name>=<cookie-value>; Domain=<domain-value>
Set-Cookie: <cookie-name>=<cookie-value>; Path=<path-value>
Set-Cookie: <cookie-name>=<cookie-value>; Secure
Set-Cookie: <cookie-name>=<cookie-value>; HttpOnly
2、HTTP 請求:Cookie 的發(fā)送
瀏覽器向服務(wù)器發(fā)送 HTTP 請求時(shí),每個(gè)請求都會(huì)帶上相應(yīng)的 Cookie。也就是說,把服務(wù)器早前保存在瀏覽器的這段信息,再發(fā)回服務(wù)器。這時(shí)要使用 HTTP 頭信息的 ?Cookie
? 字段。
// 會(huì)向服務(wù)器發(fā)送名為 foo的 Cookie,值為 bar。
Cookie: foo=bar
// Cookie字段可以包含多個(gè) Cookie,使用分號(;)分隔。
Cookie: name=value; name2=value2; name3=value3
下面是一個(gè)例子。
GET /sample_page.html HTTP/1.1
Host: www.example.org
Cookie: yummy_cookie=choco; tasty_cookie=strawberry
三、Cookie 的屬性
1、Expires,Max-Age
?Expires
? 屬性指定一個(gè)具體的到期時(shí)間,到了指定時(shí)間以后,瀏覽器就不再保留這個(gè) Cookie。它的值是 UTC 格式,可以使用?Date.prototype.toUTCString()
? 進(jìn)行格式轉(zhuǎn)換。
如果不設(shè)置該屬性,或者設(shè)為null,Cookie 只在當(dāng)前會(huì)話(session)有效,瀏覽器窗口一旦關(guān)閉,當(dāng)前會(huì)話結(jié)束,該 Cookie 就會(huì)被刪除。另外,瀏覽器根據(jù)本地時(shí)間,決定 Cookie 是否過期,由于本地時(shí)間是不精確的,所以沒有辦法保證 Cookie 一定會(huì)在服務(wù)器指定的時(shí)間過期。
?Max-Age
? 屬性指定從現(xiàn)在開始 Cookie 存在的秒數(shù),比如 60 * 60 * 24 * 365(即一年)。過了這個(gè)時(shí)間以后,瀏覽器就不再保留這個(gè) Cookie。
如果同時(shí)指定了 ?Expires
? 和 ?Max-Age
?,那么? Max-Age
? 的值將優(yōu)先生效。
如果 ?Set-Cookie
? 字段沒有指定?Expires
?或?Max-Age
?屬性,那么這個(gè) Cookie 就是 Session Cookie,即它只在本次對話存在,一旦用戶關(guān)閉瀏覽器,瀏覽器就不會(huì)再保留這個(gè) Cookie。
使用 Node 創(chuàng)建一個(gè)服務(wù)器來模擬演示:
const http = require('http')
const fs = require('fs')
http.createServer(function (request, response) {
console.log('request come', request.url)
const html = fs.readFileSync('test.html', 'utf8')
response.writeHead(200, {
'Content-Type': 'text/html',
'Set-Cookie': ['id=123;max-age=2', 'abc=456;HttpOnly']
})
response.end(html)
}).listen(8888)
console.log('http://127.0.0.1:8888')
2、Domain,Path
?Domain
?屬性指定瀏覽器發(fā)出 HTTP 請求時(shí),哪些域名要附帶這個(gè) Cookie。
- 如果沒有指定該屬性,瀏覽器會(huì)默認(rèn)將其設(shè)為當(dāng)前域名,這時(shí)子域名將不會(huì)附帶這個(gè) Cookie。比如,?
example.com
? 不設(shè)置 Cookie 的domain屬性,那么 sub.example.com 將不會(huì)附帶這個(gè) Cookie。 - 如果指定了domain屬性,那么子域名也會(huì)附帶這個(gè) Cookie。如果服務(wù)器指定的域名不屬于當(dāng)前域名,瀏覽器會(huì)拒絕這個(gè) Cookie。
- 一句話總結(jié):Domain標(biāo)識指定了哪些主機(jī)可以接受Cookie。如果不指定,默認(rèn)為當(dāng)前主機(jī)(不包含子域名)。如果指定了Domain ,則一般包含子域名。
?Path
?屬性指定瀏覽器發(fā)出 HTTP 請求時(shí),哪些路徑要附帶這個(gè) Cookie。只要瀏覽器發(fā)現(xiàn),Path屬性是 HTTP 請求路徑的開頭一部分,就會(huì)在頭信息里面帶上這個(gè) Cookie。比如,PATH屬性是 ?/
?,那么請求 ?/docs
?路徑也會(huì)包含該 Cookie。當(dāng)然,前提是域名必須一致。
3、Secure,HttpOnly
?Secure
?屬性指定瀏覽器只有在加密協(xié)議 HTTPS 下,才能將這個(gè) Cookie 發(fā)送到服務(wù)器。另一方面,如果當(dāng)前協(xié)議是 HTTP,瀏覽器會(huì)自動(dòng)忽略服務(wù)器發(fā)來的Secure屬性。該屬性只是一個(gè)開關(guān),不需要指定值。如果通信是 HTTPS 協(xié)議,該開關(guān)自動(dòng)打開。
?HttpOnly
? 屬性指定該 Cookie 無法通過 JavaScript 腳本拿到,主要是 ?document.cookie
? 屬性、?XMLHttpRequest
? 對象和 ?Request API
? 都拿不到該屬性。這樣就防止了該 Cookie 被腳本讀到,只有瀏覽器發(fā)出 HTTP 請求時(shí),才會(huì)帶上該 Cookie。出于安全考慮。
四、document.cookie
?document.cookie
? 屬性用于讀寫當(dāng)前網(wǎng)頁的 Cookie。讀取的時(shí)候,它會(huì)返回當(dāng)前網(wǎng)頁的所有 Cookie,前提是該 Cookie 不能有HTTPOnly屬性。
document.cookie // "foo=bar;baz=bar"
上面代碼從? document.cookie
?一次性讀出兩個(gè) Cookie,它們之間使用分號分隔。必須手動(dòng)還原,才能取出每一個(gè) Cookie 的值。這就是cookie存取數(shù)據(jù)不方便的地方,它沒有完善的存取數(shù)據(jù)的api讓我們用,我們必須手動(dòng)的從中提取自己需要的數(shù)據(jù)。
?document.cookie
? 屬性是可寫的,可以通過它為當(dāng)前網(wǎng)站添加 Cookie。寫入的時(shí)候,Cookie 的值必須寫成?key=value
?的形式。注意,等號兩邊不能有空格。? document.cookie
?一次只能寫入一個(gè) Cookie,而且寫入并不是覆蓋,而是添加。
document.cookie = 'fontSize=14';
// 最終只會(huì)有 test1=456 被寫進(jìn)去
document.cookie = 'test1=456;hahah=123'
?document.cookie
?讀寫行為的差異(一次可以讀出全部 Cookie,但是只能寫入一個(gè) Cookie),與 HTTP 協(xié)議的 Cookie 通信格式有關(guān)。
- 瀏覽器向服務(wù)器發(fā)送 Cookie 的時(shí)候,Cookie字段是使用一行將所有 Cookie 全部發(fā)送;
- 服務(wù)器向?yàn)g覽器設(shè)置 Cookie 的時(shí)候,?
Set-Cookie
?字段是一行設(shè)置一個(gè) Cookie。
刪除一個(gè)現(xiàn)存 Cookie 的唯一方法,是設(shè)置它的? expires
? 屬性為一個(gè)過去的日期。
document.cookie = 'fontSize=;expires=Thu, 01-Jan-1970 00:00:01 GMT';