HTTP-服務器端CooKie與瀏覽器端Cookie

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);
        }
      }
發佈了82 篇原創文章 · 獲贊 82 · 訪問量 44萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章