node模擬http服務器session機制-我們到底能走多遠系列(36)


我們到底能走多遠系列(36)

扯淡:

  年關將至,總是會在一些時間節點上才感覺時光飛逝,在平時渾渾噩噩的歲月里都浪費掉了太多的寶貴。請珍惜!

  

主題:

  
     我們在編寫http請求處理和響應的代碼的時候,經常會處理到session,這里的session是指服務器和客戶端交互時把一些信息存在服務器上,下一次請求是,可以在服務器上繼續使用這些信息,我們都知道http是無狀態的,在服務端維持一個session就是為了解決一些多個請求需要狀態維持的問題。
     它的工作原理,我的理解是,在第一次http請求時,服務端在自己內存里創建出一個對應這個客戶端的session,往這個session中放好信息后,把標識這個session的唯一字段在響應的時候帶給客戶端,客戶端將這個字段放入cookie,下一次請求的時候客戶端就會把這個cookie信息帶上來,服務端就可以找出這個客戶端對應的session了,也就可以重新使用原來保存的信息。
     這個工作是web容器完成的,像tomcat,jetty, weblogic 都實現了對session相關的接口。
     比如說遇到這樣的問題:多個容器分布部署的時候,web容器中的session無法共享,這樣可以不把session的信息部存在內存中,而是存在類似redis,memcache這樣的數據庫中。這樣就需要重寫處理session的邏輯。修改web容器的源碼,或者自己實現以下存取session,和傳輸解析cookie的方法來模擬session。

java編碼中的使用:
 HttpSession session = request. getSession();
        session.setAttribute ("uid", 1) ;
        session.getAttribute ("uid") ;
  
從原理上來看,實現的流程很清晰,在用node實現web應用的時候,現在流行用express.js。不使用框架的session接口的話,我們自己也可以粗糙的實現一下:
 
// 獲得客戶端的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.info(Cookies);


// 設置cookie
    res.setHeader(
        'Set-Cookie', 'myCookie=test'
    );
  
  利用上面的獲取cookie和設置cookie,還不能完全完成session維護的流程。還需要服務端存取session值。
     實際上我們在開發普通的web項目的時候,可以了解到,對每一個客戶端,服務端都會維護一個session,這些session中存着一些鍵值對,就像前面jsva代碼中看到的, "uid"對應 1,然后來查的時候就利用cookies中帶上來的id,找到session,再從這個session中查有沒有'uid'對應的1
  
  那么,下面就簡單用js來實現一個登錄進入一個下載頁面,來模擬http session的機制。
 
流程類似如下:
 
首先,先來看一下SessionsManage這個核心模塊
var Session = require('./Session');

var SessionsManage = function(expires, clean_time ){
    this.def_expires = expires||100;   // 過期時間
    this.def_clean_time = clean_time||1000;  // 執行清除操作間隔時間
    this.sessions = {}; // session 放置處
    //啟動定時任務,就是說不停的會檢查去除sessions中過期的的session
    setInterval(this.cleanup, this.def_clean_time, this.sessions);
};

var init = function(expires, clean_time){
    return new SessionsManage(expires, clean_time);
};
module.exports = SessionsManage;
/**
* 模擬取session
* @param req
* @param name
* @returns {null, session}
*/
SessionsManage.prototype.getSession = function(req, name){
    var id = getIdFromCookies(req);
    if(id){
        // 現貨器session對象
        var session = this.sessions[id];
        if(session && session[name]){
            // 再從session對象中找對應的值
            return session[name];
        }else {
            return null;
        }
    }else{
        return null;
    }
};

/**
* 模擬存session
* @param req
* @param res
* @param opts
* @returns {boolean}
*/
SessionsManage.prototype.setSession = function(req, res, opts){
    if(!opts){
        return false;
    }else{
        // 從cookie中獲取id
        var id = getIdFromCookies(req) || randomString(36);
        var name = opts.name;
        var value = opts.value;
        var expires = opts.expires || this.def_expires;
        if(id && value && name){
            // 新創一個session
            var session = new Session();
            session[name] = value;
            session["id"] = id;
            session["overLifeTime"] = (+new Date) + expires*1000;
            // 放置進sessions
            this.sessions[id] = session;
            // 寫入返回客戶端的cookie中
            this.setCookieId(res, id, expires);
        }
    }
};

SessionsManage.prototype.setCookieId = function(res, id, expires){
    // config cookie
    var d = new Date();
    d.setTime(d.getTime() + expires*1000); // in milliseconds
    res.setHeader(
        'Set-Cookie', 'D_SID='+ id +';expires='+d.toGMTString()+';'
    );
};

SessionsManage.prototype.cleanup = function(sessions){
    var now = new Date().getTime();
    for(var id in sessions){
        var session = sessions[id];
        if(session.overLifeTime < now){
            delete sessions[session.id];
        }
    }
};

var getIdFromCookies = function(req){
    // client's 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.info(Cookies);
    console.info(Cookies["D_SID"]);
    if(Cookies["D_SID"]){
        return Cookies["D_SID"];
    }else{
        return null;
    }
};

function randomString(bits){
    return new Date().getTime();
}

 

注:這里使用了時間戳作為相互傳遞的ID,也可以自己產生隨機的ID來代替。
 
Session對象:
var Session = function(opt){
    if(opt){
        this.id = opt.id;
        this.overLifeTime = opt.expires;
    }
};
module.exports = Session;
View Code

 

  看了SessionManage的代碼,只要在登錄的時候調用一下setSession,然后再過濾器上調用下getSession,就可以完成上面的流程了。使用express,雖然它自帶了session機制,沒有使用,模擬過濾器的代碼實現 在app.js中的
 
// 正則匹配全部請求
app.get(/^\/*/,function(req, res, next){
        if(req.path == "/upload" || req.path == "/doupload"){
        var user = SessionsManage.getSession(req,"user");
        if(!user){
            res.render('login', {});
            return;
        }
    }
    next();
});

 

登錄的時候:
 
exports.dologin = function(db, sessions){
    return function(req, res) {
        console.log("dologin");
        var username = req.body.username;
        var password = req.body.password;
        console.log("username:" + username + "password:" + password);
        var collection = db.get('usercollection');
        collection.find({},{'username':username,'password':password},function(e,docs){
            if(docs){
                console.log("username and password is valid");
                var opts = {
                    name : "user",
                    value : username,
                    expires : 500
                };
               // 調用了setSession
                sessions.setSession(req,res,opts);
                res.render('upload', {});
            }else{
                console.log("username and password is invalid");
            }
        });
    };
};

 

  

  以上就基本走完了流程,只要使用express進行一些請求上的配置,就可以了。主要是sessionManage中的實現,用提供出去的幾個方法來模擬了整個機制。
  另外,還有一些不足的地方,和遺留的漏洞。
 
 
 
 
 

讓我們繼續前行

----------------------------------------------------------------------

努力不一定成功,但不努力肯定不會成功。

 


免責聲明!

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



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