Cookie的誕生
由於HTTP協議是無狀態的,而服務器端的業務必須是要有狀態的。Cookie誕生的最初目的是為了存儲web中的狀態信息,以方便服務器端使用。比如判斷用戶是否是第一次訪問網站。目前最新的規范是RFC 6265,它是一個由瀏覽器服務器共同協作實現的規范。
Cookie的處理分為:
- 服務器像客戶端發送cookie
- 瀏覽器將cookie保存
- 之后每次http請求瀏覽器都會將cookie發送給服務器端
服務器端的發送與解析
發送cookie
服務器端像客戶端發送Cookie是通過HTTP響應報文實現的,在Set-Cookie
中設置需要像客戶端發送的cookie,cookie格式如下:
Set-Cookie: "name=value;domain=.domain.com;path=/;expires=Sat, 11 Jun 2016 11:29:42 GMT;HttpOnly;secure"
其中name=value
是必選項,其它都是可選項。Cookie的主要構成如下:
- name:一個唯一確定的cookie名稱。通常來講cookie的名稱是不區分大小寫的。
- value:存儲在cookie中的字符串值。
最好為cookie的name和value進行url編碼
- domain:cookie對於哪個域是有效的。所有向該域發送的請求中都會包含這個cookie信息。這個值可以包含子域(如:yq.aliyun.com),也可以不包含它(如:.aliyun.com,則對於aliyun.com的所有子域都有效).
- path: 表示這個cookie影響到的路徑,瀏覽器跟會根據這項配置,像指定域中匹配的路徑發送cookie。
- expires:失效時間,表示cookie何時應該被刪除的時間戳(也就是,何時應該停止向服務器發送這個cookie)。如果不設置這個時間戳,瀏覽器會在頁面關閉時即將刪除所有cookie;不過也可以自己設置刪除時間。這個值是GMT時間格式,
如果客戶端和服務器端時間不一致,使用expires就會存在偏差
。 - max-age: 與expires作用相同,用來告訴瀏覽器此cookie多久過期(單位是秒),而不是一個固定的時間點。正常情況下,max-age的優先級高於expires。
- HttpOnly: 告知瀏覽器不允許通過腳本
document.cookie
去更改這個值,同樣這個值在document.cookie中也不可見。但在http請求張仍然會攜帶這個cookie。注意這個值雖然在腳本中不可獲取,但仍然在瀏覽器安裝目錄中以文件形式存在。這項設置通常在服務器端設置。 - secure: 安全標志,指定后,只有在使用SSL鏈接時候才能發送到服務器,如果是http鏈接則不會傳遞該信息。
就算設置了secure 屬性也並不代表他人不能看到你機器本地保存的 cookie 信息,所以不要把重要信息放cookie就對了
服務器端設置cookie示例如下:
var http = require('http');
var fs = require('fs');
http.createServer(function(req, res) {
res.setHeader('status', '200 OK');
res.setHeader('Set-Cookie', 'isVisit=true;domain=.yourdomain.com;path=/;max-age=1000');
res.write('Hello World');
res.end();
}).listen(8888);
console.log('running localhost:8888')
直接設置Set-Cookie
過於原始,我們可以對cookie的設置過程做如下封裝:
var serilize = function(name, val, options) {
if (!name) {
throw new Error("coolie must have name");
}
var enc = encodeURIComponent;
var parts = [];
val = (val !== null && val !== undefined) ? val.toString() : "";
options = options || {};
parts.push(enc(name) + "=" + enc(val));
// domain中必須包含兩個點號
if (options.domain) {
parts.push("domain=" + options.domain);
}
if (options.path) {
parts.push("path=" + options.path);
}
// 如果不設置expires和max-age瀏覽器會在頁面關閉時清空cookie
if (options.expires) {
parts.push("expires=" + options.expires.toGMTString());
}
if (options.maxAge && typeof options.maxAge === "number") {
parts.push("max-age=" + options.maxAge);
}
if (options.httpOnly) {
parts.push("HTTPOnly");
}
if (options.secure) {
parts.push("secure");
}
return parts.join(";");
}
需要注意的是,如果給cookie設置一個過去的時間,瀏覽器會立即刪除該cookie
;此外domain項必須有兩個點,因此不能設置為localhost
:
something that wasn't made clear to me here and totally confused me for a while was that domain names must contain at least two dots (.),hence 'localhost' is invalid and the browser will refuse to set the cookie!
服務器端解析cookie
cookie可以設置不同的域與路徑,所以對於同一個name value
,在不同域不同路徑下是可以重復的,瀏覽器會按照與當前請求url或頁面地址最佳匹配的順序來排定先后順序
所以當前端傳遞到服務器端的cookie有多個重復name value
時,我們只需要最匹配的那個,也就是第一個。服務器端解析代碼如下:
var parse = function(cstr) {
if (!cstr) {
return null;
}
var dec = decodeURIComponent;
var cookies = {};
var parts = cstr.split(/\s*;\s*/g);
parts.forEach(function(p){
var pos = p.indexOf('=');
// name 與value存入cookie之前,必須經過編碼
var name = pos > -1 ? dec(p.substr(0, pos)) : p;
var val = pos > -1 ? dec(p.substr(pos + 1)) : null;
//只需要拿到最匹配的那個
if (!cookies.hasOwnProperty(name)) {
cookies[name] = val;
}/* else if (!cookies[name] instanceof Array) {
cookies[name] = [cookies[name]].push(val);
} else {
cookies[name].push(val);
}*/
});
return cookies;
}
客戶端的存取
瀏覽器將后台傳遞過來的cookie進行管理,並且允許開發者在JavaScript中使用document.cookie
來存取cookie。但是這個接口使用起來非常蹩腳。它會因為使用它的方式不同而表現出不同的行為
。
- 當用來獲取屬性值時,document.cookie返回當前頁面可用的(根據cookie的域、路徑、失效時間和安全設置)所有的字符串,字符串的格式如下:
"name1=value1;name2=value2;name3=value3";
- 當用來設置值的時候,document.cookie屬性可設置為一個新的cookie字符串。這個字符串會被解釋並添加到現有的cookie集合中。如:
document.cookie = "_fa=aaaffffasdsf;domain=.dojotoolkit.org;path=/"
設置document.cookie並不會覆蓋cookie,除非設置的name value domain path
都與一個已存在cookie重復。
由於cookie的讀寫非常不方便,我們可以自己封裝一些函數來處理cookie,主要是針對cookie的添加、修改、刪除。
var cookieUtils = {
get: function(name){
var cookieName=encodeURIComponent(name) + "=";
//只取得最匹配的name,value
var cookieStart = document.cookie.indexOf(cookieName);
var cookieValue = null;
if (cookieStart > -1) {
// 從cookieStart算起
var cookieEnd = document.cookie.indexOf(';', cookieStart);
//從=后面開始
if (cookieEnd > -1) {
cookieValue = decodeURIComponent(document.cookie.substring(cookieStart + cookieName.length, cookieEnd));
} else {
cookieValue = decodeURIComponent(document.cookie.substring(cookieStart + cookieName.length, document.cookie.length));
}
}
return cookieValue;
},
set: function(name, val, options) {
if (!name) {
throw new Error("coolie must have name");
}
var enc = encodeURIComponent;
var parts = [];
val = (val !== null && val !== undefined) ? val.toString() : "";
options = options || {};
parts.push(enc(name) + "=" + enc(val));
// domain中必須包含兩個點號
if (options.domain) {
parts.push("domain=" + options.domain);
}
if (options.path) {
parts.push("path=" + options.path);
}
// 如果不設置expires和max-age瀏覽器會在頁面關閉時清空cookie
if (options.expires) {
parts.push("expires=" + options.expires.toGMTString());
}
if (options.maxAge && typeof options.maxAge === "number") {
parts.push("max-age=" + options.maxAge);
}
if (options.httpOnly) {
parts.push("HTTPOnly");
}
if (options.secure) {
parts.push("secure");
}
document.cookie = parts.join(";");
},
delete: function(name, options) {
options.expires = new Date(0);// 設置為過去日期
this.set(name, null, options);
}
}