我們到底能走多遠系列(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;
看了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中的實現,用提供出去的幾個方法來模擬了整個機制。
另外,還有一些不足的地方,和遺留的漏洞。
讓我們繼續前行
----------------------------------------------------------------------
努力不一定成功,但不努力肯定不會成功。