用慣了框架中的插件,最近在重溫node基礎模塊時也不禁在想:什麼是Cookie?什麼是Session?兩者的區別和聯繫有哪些?Node.js是否提供了相應的模塊來管理存儲Session?如果沒有提供相應模塊,我們應該如何實現一個類似Session管理的模塊呢?
Cookie和Session
Session和Cookie都是基於Web服務器的,不同的是Cookie存儲在客戶端,而Session存儲在服務器端。
當用戶在瀏覽網站的時候,Web服務器會在瀏覽器上存儲一些當前用戶的相關信息,而在本地Web客戶端存儲的就是Cookie數據。這樣當下次用戶再瀏覽同一個網站時,Web服務器就會先查看並讀取本地的cookie資料,如果有cookie就會依據cookie裏面的內容並判斷其過期時間,從而給用戶特殊的數據返回。
cookie的使用很普遍 —— 許多支持個性化服務的網站,大多都是用cookie來辨認使用者,以方便送出爲使用者量身定做的內容,像web接口的免費email。再比如:大多網站支持的“7天免登錄”。
具體來說,cookie機制採用的是在客戶端保持狀態的方案,而session機制採用的是在服務器端保持狀態的方案。同時我們也可以看到:由於服務器端保持狀態的方案在客戶端也需要保存一個標識,所以session機制可能需要藉助於cookie機制來達到保存標識的目的。但實際上它還有其他選擇。正統的cookie分發是通過擴展HTTP協議來達到的,服務器通過在http的響應頭上加上一行特殊的標識,以提示瀏覽器按照指示生成相應的cookie。然而,純粹的客戶端腳本如JavaScript或者VBScript也可以生成cookie —— document.cookie='xxx=xxx; expires=xxx'
。
cookie是基於session的
cookie的使用卻是由瀏覽器按照一定的原則在後臺自動發送給服務器的。瀏覽器檢查所有存儲的cookie,如果某個cookie所聲明的作用範圍大於等於將要請求的資源所在的位置,則可把該cookie附在去請求資源的http請求頭上發送給服務器。
Cookie的內容主要包括:名字、值、過期時間、路徑和域。路徑與域一起構成Cookie的作用範圍。若不設置過期時間,則表示這個Cookie的生命期爲瀏覽器會話期間,關閉瀏覽器窗口,Cookie 就消失。這種生命期爲瀏覽器會話期的Cookie,被稱爲會話Cookie。會話Cookie一般不存儲在硬盤上,而是保存在內存裏,當然這種行爲並不是規範的。若設置了過期時間,瀏覽器就會把Cookie保存到硬盤上,關閉後再次打開瀏覽器,這些Cookie仍然有效,直到超過設定的過期時間。
而對於保存在內存裏的Cookie, 不同的瀏覽器有不同的處理方式。Session機制是一種服務器端的機制,服務器使用類似於散列表的結構(也可能真的使用散列表)來保存信息。當程序需要爲某個客戶端的請求創建一個 Session 時,服務器首先檢查這個客戶端的請求裏是否已包含了一個Session標識(稱爲Session id),如果已包含則說明以前已經爲此客戶端創建過Session,服務器就按照Session id把這個Session檢索出來使用(檢索不到,會新建一個), 如果客戶端請求不包含Session id,則爲此客戶端創建一個Session並且生成-一個與此Session相關聯的Session id, Session id的值應該是一個既不會重複,又不容易被發現其生成規律的字符串,這個Session id將在本次響應中被返回給客戶端保存。保存這個Session id的方式可以採用Cookie,這樣在交互過程中瀏覽器可以自動按照規則把這個標識發送給服務器。一般這個Cookie的名字都類似於SESSID。
Session模塊的實現
既然session如此“重要”,我們不妨來看看session模塊:
PHP中內置了session方法可供調用,例如session_start
以及$_SESSION
等。但是原生的Node.js中卻沒有提供任何session管理的模塊,因此我們可以自己實現一個:
根據上面對session和cookie的介紹,我們不難得出其中的邏輯
(其實,服務端檢查的是session在瀏覽器的cookie中有沒有對應的session id)
如上圖所示,客戶端首先會請求Session,而當服務端檢查客戶端中的Cookie沒有相應的Session id時,會通過-一定方式爲其生成一個新的Session id, 而如果Cookie中存在該Session id並且沒有過期時,則直接返回Session數據。
那麼根據如上流程示意圖以及介紹,可以爲我們需要實現的模塊先創建3種方法,分別是start、 newSession和cleanSessions。 start方法主要是啓動Session管理,newSession主要是爲客戶端創建一個新的Session id, 而cleanSessions則是清除Session數據。
本模塊應用一個Session數組來存儲系統所有的Session, 當有Session id存在時,無需新建Sessionid,而是直接讀取返回Session數據;而當Sessionid不存在時,需要創建Session id,並且將Sessionid存儲在該客戶端的Cookie中。筆者做了一個簡單的session校驗start,代碼如下:
var start = function(req,res){
var conn = { res: res, req: req };
var cookies = {};
if(typeof conn.req.headers.cookie !== "undefined"){
//session存在時,對session進行解析,獲取其中的session id
conn.req.headers.cookie.split(';').forEach(function(cookie){
var parts=cookie.split('=');
cookies[ parts[0].trim() ] = (parts[1] || '').trim();
});
}else{
cookies.SESSID = 0;
}
var SESSID = cookies.SESSID;
if(typeof sessions[SESSID] !== "undefined"){ //判斷服務器中是否存在該session值
session=sessions[SESSID];
if(session.expires < Date()){ //如果session過期
delete sessions[SESSID];
//session過期或者不存在(下面那個)時,新生成一個session
return newSession(conn.res);
}else{
var dt = new Date();
dt.setMinutes(dt.getMinutes() + 30);
session.expires = dt; //重置session過期時間
return sessions[SESSID];
}
}else{
return newSession(conn.res);
}
};
以上就是一個session簡單的校驗過程,主要思路就是通過req對象中的headers獲取cookie,並對cookie進行解析獲取session id,進而判斷是否存在該id值,從而返回或生成新的session。下面我們來看下主要方法newSession的實現:
function newSession(res){
var chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
var SESSID = '';
for(var i = 0; i < 40; i++){
var rnum = Math.floor(Math.random()*chars.length);
SESSID += chars.substring(rnum,rnum+1);
}
if(typeof sessions[SESSID] !== "undefined"){
return newSession(res); //避免重複session
}
var dt = new Date();
dt.setMinutes(dt.getMinutes() + 30);
var session = {
SESSID,
expires: dt
};
sessions[SESSID] = session;
//爲客戶端新增cookie數據(在客戶端cookie中保存sessid)
res.setHeader('Set-Cookie','SESSID=' + SESSID);
return session;
}
當然,最後就是將整個模塊暴露出去:
exports.start=start;
Session模塊的應用
我們可以在入口文件(例如app.js)中require該模塊,並在HTTP的createServer函數中調用session.start,並將session.start返回的對象作爲一個全局對象存儲,代碼如下:
var app=http.createServer(function(req,res){
global.sessionLib = session.start(res,req);
});
//調用時
if(!sessionLib['username']){
sessionLib['username'] = 'mxc';
}
node框架express中session插件的應用
介紹完基礎模塊,拿筆者的一個項目來說下框架中相關插件的基本用法 —— 其實其實現原理與本文所說不差一二。
const cookieSession=require('cookie-session');
(function (){
var keys=[];
for(var i=0;i<100000;i++){
keys[i]='a_'+Math.random();
}
server.use(cookieSession({
name: 'sess_id',
keys: keys,
maxAge: 20*60*1000 //20min
}));
})();
使用時判斷:
//檢查登錄狀態
router.use((req, res, next)=>{
if(!req.session['admin_id'] && req.url!='/login'){ //沒有登錄且當前不是登錄頁(避免redirect黑洞)
res.redirect('/admin/login');
}else{
next();
}
});
登錄後:
req.session['admin_id']=data[0].ID;