node.js操作Cookie


通過node.js建立了一個完整的網站不是一件容易的事,這涉及讀取頁面模板,從數據庫中抽出數據構建成新的頁面返回給客戶端。但光是這樣還不行,我們還要設置首部,在chrome中如果CSS沒有設置正確的Content-Type,會不起作用的。此處理還要考慮訪問量,要設置緩存,緩存不單單是把東西從內存中讀入讀出就行,這樣會撐爆電腦內存的,這用LRU算法(最近最少用的數據會清空出內存)。基於Cookie與數據庫與URL重寫,我們發展出一個session機制用於在多個action中通信。對於不同的請求交由不同的action來處理,就要發展出路由機制與MVC系統,等等。我信后寫這些東西一點點寫出來,揭示newland.js中遇到的種種問題與解決方案。如果什么都貪圖方便,直接上框架,對我們語言學習是非常不利的。

本文正如標題所說,是操作Cookie。下面是一個完整的例子:

var http = require('http');
http.createServer(function (req, res) {
    // 獲得客戶端的Cookie
    var Cookies = {};
    req.headers.cookie && req.headers.cookie.split(';').forEach(function( Cookie ) {
        var parts = Cookie.split('=');
        Cookies[ parts[ 0 ].trim() ] = ( parts[ 1 ] || '' ).trim();
    });
    console.log(Cookies)
    // 向客戶端設置一個Cookie
    res.writeHead(200, {
        'Set-Cookie': 'myCookie=test',
        'Content-Type': 'text/plain'
    });
    res.end('Hello World\n');
}).listen(8000);

console.log('Server running at http://127.0.0.1:8000/');

如果去掉其中幾句,就是官方給出的例子,除了表明返回一個頁面多簡單外,一點用也沒有。


var http = require('http');

http.createServer(function (req, res) {
  res.writeHead(200, {'Content-Type': 'text/plain'});
  res.end('Hello World\n');
}).listen(8000);

console.log('Server running at http://127.0.0.1:8000/');

我們通過http.createServer的回調來處理所有請求與響應,因此什么有用的東西都在它們上面。Cookie位於req對象的headers對象上,為一個字符串,通常為了方便我們將它們轉換成一個對象。

寫入一個Cookie其實就是在首部設置一個鍵值對,上面是簡單方式,它實際上可以這樣:

   res.writeHead(200, {
        'Set-Cookie': ["aaa=bbb","ccc=ddd","eee=fff"],
        'Content-Type': 'text/plain'
    });

但真正使用時,我們的Cookie並非這樣簡單的的格式:

    Set-Cookie: =[; =] 
  [; expires=][; domain=] 
  [; path=][; secure][; HttpOnly]

HttpOnly 屬性: 這是微軟對Cookie做的擴展。如果在Cookie中設置了"HttpOnly"屬性,那么通過程序(JS腳本、Applet等)將無法讀取到Cookie信息,這樣能有效的防止XSS攻擊。

var http = require('http');
http.createServer(function (req, res) {
    // 獲得客戶端的Cookie
    var Cookies = {};
    req.headers.cookie && req.headers.cookie.split(';').forEach(function( Cookie ) {
        var parts = Cookie.split('=');
        Cookies[ parts[ 0 ].trim() ] = ( parts[ 1 ] || '' ).trim();
    });
    console.log(Cookies)
    // 向客戶端設置一個Cookie
    res.writeHead(200, {
        'Set-Cookie': 'SSID=Ap4GTEq; Expires=Wed, 13-Jan-2021 22:23:01 GMT;HttpOnly ',
        'Content-Type': 'text/html'
    });
    res.end('Hello World\n');
}).listen(8000);

console.log('Server running at http://127.0.0.1:8000/');

然后多刷幾次頁面,我們發現我們還能在控制台看到SSID=Ap4GTEq這個屬性,但在前端我們看不到它(當然在firebug中能看到)。

Secure屬性: 當設置為true時,表示創建的 Cookie 會被以安全的形式向服務器傳輸,也就是只能在 HTTPS 連接中被瀏覽器傳遞到服務器端進行會話驗證,如果是 HTTP 連接則不會傳遞該信息,所以不會被竊取到Cookie 的具體內容。同上,在客戶端我們也無法在document.Cookie找到被設置了Secure=true的Cookie鍵值對。Secure屬性是防止信息在傳遞的過程中被監聽捕獲后信息泄漏,HttpOnly屬性的目的是防止程序獲取Cookie后進行攻擊。我們可以把Secure=true看成比HttpOnly更嚴格的訪問控制。

path屬性: 指定可訪問Cookie的目錄。例如:"userId=320; path=/shop";就表示當前Cookie僅能在shop目錄下使用。

domain屬性: 指定可訪問Cookie的主機名.主機名是指同一個域下的不同主機,例如:www.google.com和gmail.google.com就是兩個不同的主機名。默認情況下,一個主機中創建的Cookie在另一個主機下是不能被訪問的, 但可以通過domain參數來實現對其的控制,其語法格式為:"name=value; domain=CookieDomain";以google為例,要實現跨主機訪問,可以寫為: "name=value;domain=.google.com";這樣,所有google.com下的主機都可以訪問該Cookie。

Expires屬性:指定過期時間,格式為"name=value;; expires=GMT_String"; 其中GMT_String是以GMT格式表示的時間字符串,超過這個時間,Cookie將消失,不可訪問。例如:如果要將Cookie設置為10天后過期,可以這樣實現:

//獲取當前時間
var date=new Date();
var expireDays=10;
//將date設置為10天以后的時間
date.setTime(date.getTime()+expireDays*24*3600*1000);
//將userId和userName兩個Cookie設置為10天后過期
 res.writeHead(200, {
        'Set-Cookie': "userId=828; userName=hulk; expire="+date.toGMTString();
        'Content-Type': 'text/html'
   });

Max-Age屬性: 個人感覺這個東西比Expires更好用,本來就是用於代替Expires,由於市面上的書你抄我,我抄你,都在抄舊知識,導致Expires還在使用。Max-Age的值 可以為正數,表示此Cookie從創建到過期所能存在的時間,以秒為單位,此Cookie會存儲到客戶端電腦,以Cookie文件形式保存,不論關閉瀏覽器或關閉電腦,直到時間到才會過期。 可以為負數,表示此Cookie只是存儲在瀏覽器內存里,只要關閉瀏覽器,此Cookie就會消失。maxAge默認值為-1。 還可以為0,表示從客戶端電腦或瀏覽器內存中刪除此Cookie。

Cookie面向的主要是服務器,localstorage面向的是頁面端js。頁面所需的業務數據可以放在localstorage里,但是認證相關的信息還是需要放在Cookie里的。


Cookie的限制

一、瀏覽器允許每個域名所包含的 Cookie 數:

  1. Microsoft 指出 Internet Explorer 8 增加 Cookie 限制為每個域名 50 個,但 IE7 似乎也允許每個域名 50 個 Cookie(《Update to Internet Explorer’s Cookie Jar》)。
  2. Firefox 每個域名 Cookie 限制為 50 個。
  3. Opera 每個域名 Cookie 限制為 30 個。
  4. Safari/WebKit 貌似沒有 Cookie 限制。但是如果 Cookie 很多,則會使 header 大小超過服務器的處理的限制,會導致錯誤發生。

二、當很多的 Cookie 被設置,瀏覽器如何去響應。除 Safari(可以設置全部Cookie,不管數量多少),有兩個方法:

  1. 最少最近使用(least recently used (LRU))的方法:當 Cookie 已達到限額,自動踢除最老的 Cookie ,以使給最新的 Cookie 一些空間。 Internet Explorer 和 Opera 使用此方法。
  2. Firefox 很獨特:雖然最后的設置的 Cookie 始終保留,但似乎隨機決定哪些 Cookie 被保留。似乎沒有任何計划(建議:在 Firefox 中不要超過 Cookie 限制)。

三、不同瀏覽器間 Cookie 總大小也不同:

  1. Firefox 和 Safari 允許 Cookie 多達 4097 個字節, 包括名(name)、值(value)和等號。
  2. Opera 允許 Cookie 多達 4096 個字節, 包括:名(name)、值(value)和等號。
  3. Internet Explorer 允許 Cookie 多達 4095 個字節, 包括:名(name)、值(value)和等號。

注:多字節字符計算為兩個字節。在所有瀏覽器中,任何 Cookie 大小超過限制都被忽略,且永遠不會被設置。

最后讓我們看看newland.js是怎么處理cookie的。

newland.js有個重要的對象叫httpflow,其實就是我的操作流flow的子類,它劫持了所有清求與響應。當一個請求過來時,框架就會new一個httpflow去處理它們。它有個patch方法,用於為操作流添加一些有用屬性與方法,而不像express.js那樣直接在原生對象上改。實現express.js現在的做法有點像Prototype.js,加之node.js的版本現在還沒有到1.0,因此API改動還很頻繁的。express.js的行為無異走鋼線。而把操作移到一個自定義對象就安全多了。

// 源馬見https://github.com/RubyLouvre/newland/blob/master/system/mvc.js
 http.createServer(function(req, res) {
            var flow = new Flow()//創建一個流程對象,處理所有異步操作,如視圖文件的讀取、數據庫連接
            flow.patch(req, res)
            services.forEach(function(fn){
                fn(flow);//將攔截器綁到流程對象上
            });
       //...
})

此外,httpflow還劫持res.writeHead,res.setHeader,目的為實現多次調用setCookie時而不相互覆蓋。

// 源馬見https://github.com/RubyLouvre/newland/blob/master/system/httpflow.js
 patch: function(req, res){
            this.res =  res;
            this.req =  req;
            this.originalUrl = req.url;
            this.params = {};
            this.session = new Store(this)
            this.flash =  function(type, msg){
                  //。。。。。
            }
            var flow = this;
            var writeHead = res.writeHead;
            var setHeader = res.setHeader;
            flow._setHeader = setHeader;
            res.writeHead = function(){
                flow.fire('header');
                writeHead.apply(this, arguments);
                this.writeHead = writeHead;//還原
            }
            res.setHeader = function(field, val){
                var key = field.toLowerCase()
                if ( 'set-cookie' == key ) {
                    var array = typeof val == "string" ? [val] : val;
                    array.forEach(function(str){
                        var arr =  str.split("=");
                        flow.addCookie(arr[0], arr[1])
                    })
                } else{
                    if ('content-type' == key && this.charset) {
                        val += '; charset=' + this.charset;
                    }
                    setHeader.call(this, field, val);
                }
            }
        }

此外操作流還有兩個有用的方法來添加或移除Cookie。

// 源馬見https://github.com/RubyLouvre/newland/blob/master/system/httpflow.js
        addCookie: function(name, val, opt){
            if(!this.resCookies){
                this.resCookies = {};
                this.resCookies[name] = [val, opt]
                this.bind("header", function(){
                    var array = []
                    for(var i in this.resCookies){
                        var arr = this.resCookies[i];
                        array.push( Cookie.stringify(i, arr[0], arr[1] ) )
                    }
                    this._setHeader.call(this.res, "Set-Cookie",array)
                })
            }else{
                this.resCookies[name] = [val, opt]
            }
            return this;
        },
        removeCookie: function(name){
            var cookies = Array.isArray(name) ? name : [ name ];
            cookies.forEach(function(cookie){
                this.addCookie(cookie,"", 0)
            },this);
            return this;
        },

實質上,經過上面的代碼,我們就好方便多次添加或刪除Cookie。個人認為用setHeader來操作(即使它已經被偷龍轉鳳還是不怎么好用),大家還是用addCookie, removeCookie來干吧。這些操作會在用戶第一次調用當前的res.whireHead生效!

 flow.addCookie("ACookie","xxxxxxxxxx");
 flow.addCookie("BCookie","yyyyyyyyy");
 flow.addCookie('rememberme', 'yes', { expires: 0, httpOnly: true })

 //鏈式寫法,同名cookie前者會覆蓋后者的,前端只生成“aaa=2; bbb=1”
 flow.addCookie("aaa",1).addCookie("aaa",2).addCookie("bbb",1).addCookie("bbb",1)

 flow.res.setHeader("Set-Cookie","user=aaa")

 flow.removeCookie("oldCookie")
 //傳入一個字符串數組,同時刪除多個cookie
 flow.removeCookie(["myCookie","uuer","newCookie"])

如果你想查看從客戶端來的cookie,那么直接看flow.cookie好了,它會在途中調用一個get_cookie的服務,將原始的字符始形式轉換為一個對象。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM