Express框架之express-session的插件的攻坚战
来源:互联网 发布:开源自定义表单php 编辑:程序博客网 时间:2024/05/01 19:17
第一步:我们看看req对象在Express中被封装了那些内容(简易版):
httpVersionMajor: 1, httpVersionMinor: 1, httpVersion: '1.1', complete: true, headers:{}, rawHeaders:[], trailers: {}, rawTrailers: [], upgrade: false, url: '/', method: 'GET', statusCode: null, statusMessage: null, baseUrl: '', originalUrl: '/', params: {}, //req.params对象 query: { page: 1, limit: 10 }, //req.query参数 body: {}, files: {}, secret: undefined, cookies: { qinliang: 's:BDOjujVhV0DH9Atax_gl4DgZ4-1RGvjQ.OeUddoRalzB4iSmUHcE8oMziad4Ig7jUT1REzGcYcdg', blog: 's:-ZkSm8urr8KsXAKsZbSTCp8EWOu7zq2o.Axjo6YmD2dLPGQK9aD1mR8FcpOzyHaGG6cfGUWUVK00' }, signedCookies: {}, //sessionStore是一个MongoStore实例 sessionStore: MongoStore { db: Db { domain: null, recordQueryStats: false, retryMiliSeconds: 1000, numberOfRetries: 60, readPreference: undefined }, db_collection_name: 'sessions', defaultExpirationTime: 1209600000, generate: [Function], collection: Collection { db: [Object], collectionName: 'sessions', internalHint: null, opts: {}, slaveOk: false, serializeFunctions: false, raw: false, readPreference: 'primary', pkFactory: [Object], serverCapabilities: undefined } }, sessionID: '-ZkSm8urr8KsXAKsZbSTCp8EWOu7zq2o', //req.sessionID是一个32为的字符串 session: //req.session域下面保存的是一个Session实例,其中有cookie表示是一个对象 Session { //这里是req.session.cookie是一个Cookie实例 cookie: { path: '/', _expires: Fri May 06 2016 15:44:48 GMT+0800 (中国标准时间), originalMaxAge: 2591999960, httpOnly: true }, flash: { error: [Object] } }
第二步:我们看看一个常见的保存Session的MemoryStore,我们看看他是如何对session进行组织的,但是学习之前我们必须首先了解一下通用的store:
var Store = module.exports = function Store(options){};//这个Store实例是一个EventEmitter实例,也就是说Store实例最后还是一个EventEmitter实例对象Store.prototype.__proto__ = EventEmitter.prototype;很显然我们可以看到store对象是一个EventEmitter对象
//每一个store有一个默认的regenerate方法用于产生sessionStore.prototype.regenerate = function(req, fn){ var self = this; //regenerate底层调用的是destroy方法,第一个参数是req.sessionID,至于回调中的self.generate必须是对容器进行指定的 this.destroy(req.sessionID, function(err){ self.generate(req); fn(err);//最后回调fn }); //调用这个store的destory方法,销毁req.sessionID,销毁成功后通过刚才的store的generate方法产生一个sessionID};因为这是一个通用的store对象,所以他提供了一个regenerate方法,这个方法首先调用destroy方法销毁指定的sesison,然后回调中通过generate产生一个新的session对象并保存,至于这个generate函数是一般在用的时候动态绑定的:
store.generate = function(req){ req.sessionID = generateId(req); req.session = new Session(req); req.session.cookie = new Cookie(cookieOptions); //用户指定的secure参数如果是auto,那么修改req.session.cookie的secure参数,并通过issecure来判断 if (cookieOptions.secure === 'auto') { req.session.cookie.secure = issecure(req, trustProxy); }这是在express-session中为store指定的一个generate函数。刚才说到了destroy方法,那么我们看看MemoryStore中是如何实现这个destroy方法的:
//destroy函数用于销毁指定的sessionId的session,首先要从MemoryStore对象的sessions集合中把这个sessionId对应的session删除MemoryStore.prototype.destroy = function destroy(sessionId, callback) { delete this.sessions[sessionId] callback && defer(callback)}很显然MemoryStore是通过把指定的session从sessions集合中删除就可以了!
通用的store还提供了一个load方法用于加载指定sessionID的session:
//通过指定的sid加载一个Session实例,然后触发函数fn(err,sess)Store.prototype.load = function(sid, fn){ var self = this; //最后调用的是Store的get方法 this.get(sid, function(err, sess){ if (err) return fn(err); if (!sess) return fn(); //如果sess为空那么调用fn()方法 var req = { sessionID: sid, sessionStore: self }; //调用createSession来完成的 sess = self.createSession(req, sess); fn(null, sess); });};很显然这里有一个createSession方法,我们先看看他的代码再说:
//从一个JSON格式的sess中创建一个session实例,如sess={cookie:{expires:xx,originalMaxAge:xxx}}Store.prototype.createSession = function(req, sess){ var expires = sess.cookie.expires , orig = sess.cookie.originalMaxAge; //创建session时候获取其中的cookie域下面的expires,originalMaxAge参数 sess.cookie = new Cookie(sess.cookie); //更新session.cookie为一个Cookie实例而不再是一个{}对象了 if ('string' == typeof expires) sess.cookie.expires = new Date(expires); sess.cookie.originalMaxAge = orig; //为新构建的cookie添加originalMaxAge属性 req.session = new Session(req, sess); //创建一个session实例,其中传入的第一个参数是req,第二个参数是sess也就是我们刚才创建的那个Cookie实例,签名为sess={cookie:cookie对象} return req.session;};很显然,这个方法是用于创建一个session实例的,但是在创建session实例之前必须创建一个cookie,这也是为什么express的内部数据是这样的:
session: //req.session域下面保存的是一个Session实例,其中有cookie表示是一个对象 Session { //这里是req.session.cookie是一个Cookie实例 cookie: { path: '/', _expires: Fri May 06 2016 15:44:48 GMT+0800 (中国标准时间), originalMaxAge: 2591999960, httpOnly: true }, flash: { error: [Object] } }还有一句代码也就是创建cookie后如何创建了session,我们看看session是如何创建的:
function Session(req, data) { Object.defineProperty(this, 'req', { value: req }); Object.defineProperty(this, 'id', { value: req.sessionID }); if (typeof data === 'object' && data !== null) { // merge data into this, ignoring prototype properties for (var prop in data) { if (!(prop in this)) { this[prop] = data[prop] } } }}很显然我们创建的session实例有一个req属性表示请求对象,还有一个id选项表示sessionID,同时第二个参数所有属性也封装到了这个session上,最终session下面就多了一个cookie域,那么你可能会问,既然有req和id属性为什么一开始没有打印出来呢,其实是因为session重写了defineProperty方法:
function defineMethod(obj, name, fn) { Object.defineProperty(obj, name, { configurable: true, enumerable: false, value: fn, writable: true });};也就是把enumberale设置为false了。我们继续上面的说load方法,load方法中其实调用了get方法,但是我们通用的store没有指定get方法,我们看看MemoryStore是如何实现的:
//get用于获取指定的sessionId的session,延迟执行callback回调,传入的参数第一个为null,第二个就是指定的session对象!MemoryStore.prototype.get = function get(sessionId, callback) { defer(callback, null, getSession.call(this, sessionId))}通过get方法我们知道load函数回调中的sess就是通过指定的sessionID查询到的session对象,便于分析我们把load方法代码在写一遍:
//通过指定的sid加载一个Session实例,然后触发函数fn(err,sess)Store.prototype.load = function(sid, fn){ var self = this; //最后调用的是Store的get方法 this.get(sid, function(err, sess){ if (err) return fn(err); if (!sess) return fn(); //如果sess为空那么调用fn()方法 var req = { sessionID: sid, sessionStore: self }; //调用createSession来完成的 sess = self.createSession(req, sess); fn(null, sess); });};看到这里有回到了方才的createSession方法,传入的一个参数就是{sessionID:sid,sessionStore:self}而第二个参数就是我们根据指定的sessionId查询出来的session:
function Session(req, data) { Object.defineProperty(this, 'req', { value: req }); Object.defineProperty(this, 'id', { value: req.sessionID }); if (typeof data === 'object' && data !== null) { // merge data into this, ignoring prototype properties for (var prop in data) { if (!(prop in this)) { this[prop] = data[prop] } } }}因此load方法调用后其session的req和id属性是没有变化的,还是原来的值。至于load的回调函数来说其第二个参数是真正的session。所以说load仅仅用来重新从session中读取指定sessionID的session!我们把通用的session的代码贴出来,有兴趣可以查看一下:
'use strict';var EventEmitter = require('events').EventEmitter , Session = require('./session') , Cookie = require('./cookie')var Store = module.exports = function Store(options){};//这个Store实例是一个EventEmitter实例,也就是说Store实例最后还是一个EventEmitter实例对象Store.prototype.__proto__ = EventEmitter.prototype; //每一个store有一个默认的regenerate方法用于产生sessionStore.prototype.regenerate = function(req, fn){ var self = this; //regenerate底层调用的是destroy方法,第一个参数是req.sessionID,至于回调中的self.generate必须是对容器进行指定的 this.destroy(req.sessionID, function(err){ self.generate(req); fn(err);//最后回调fn }); //调用这个store的destory方法,销毁req.sessionID,销毁成功后通过刚才的store的generate方法产生一个sessionID};//通过指定的sid加载一个Session实例,然后触发函数fn(err,sess)Store.prototype.load = function(sid, fn){ var self = this; //最后调用的是Store的get方法 this.get(sid, function(err, sess){ if (err) return fn(err); if (!sess) return fn(); //如果sess为空那么调用fn()方法 var req = { sessionID: sid, sessionStore: self }; //调用createSession来完成的 sess = self.createSession(req, sess); fn(null, sess); });};//从一个JSON格式的sess中创建一个session实例,如sess={cookie:{expires:xx,originalMaxAge:xxx}}Store.prototype.createSession = function(req, sess){ var expires = sess.cookie.expires , orig = sess.cookie.originalMaxAge; //创建session时候获取其中的cookie域下面的expires,originalMaxAge参数 sess.cookie = new Cookie(sess.cookie); //更新session.cookie为一个Cookie实例而不再是一个{}对象了 if ('string' == typeof expires) sess.cookie.expires = new Date(expires); sess.cookie.originalMaxAge = orig; //为新构建的cookie添加originalMaxAge属性 req.session = new Session(req, sess); //创建一个session实例,其中传入的第一个参数是req,第二个参数是sess也就是我们刚才创建的那个Cookie实例,签名为sess={cookie:cookie对象} return req.session;};第三步:刚才说到了通用的store,现在我们分析一下MemoryStore这个保存session的对象:
//指定defer函数,默认是setImeditate,如果不存在那么就自定义。这个setImmediate在node.js中用于延迟执行一个函数var defer = typeof setImmediate === 'function' ? setImmediate : function(fn){ process.nextTick(fn.bind.apply(fn, arguments)) }module.exports = MemoryStore//保存到内存中的MemoryStore,同时创建一个session为空对象{},同时继承了Store的//保存到内存中的MemoryStore,同时创建一个session为空对象{},同时继承了Store的,load,createSession方法function MemoryStore() { Store.call(this) this.sessions = Object.create(null)}//继承了Store中的所有的原型属性util.inherits(MemoryStore, Store)这个库继承了上面的通用的store,同时又一个封装的集合sessions用于保存所有的session!贴出上面的代码之前,一定要看清楚下面这种调用方式:
function test(fn){//这种方式既然把后面多于的参数也就是"qinlang",klfang""传入了第一个函数作为参数了 setTimeout(fn.bind.apply(fn, arguments),1000)}test(function(){console.log(this);console.log(arguments);},"qinliang","klfang");//打印[Function]//{ '0': 'qinliang', '1': 'klfang' }MemoryStore提供的第一个参数all:
//该方法用户获取所有活动态的SessionMemoryStore.prototype.all = function all(callback) { //MemoryStore对象有一个sessions属性用于保存所有的session集合 var sessionIds = Object.keys(this.sessions) var sessions = Object.create(null) for (var i = 0; i < sessionIds.length; i++) { var sessionId = sessionIds[i] //调用getSession方法,这个方法的this指向了MemoryStore对象,第一个参数传入了sesisonID var session = getSession.call(this, sessionId) //如果存在session那么保存到结果数组中 if (session) { sessions[sessionId] = session; } } //最后调用defer函数把sessions对象传入到这个函数中 callback && defer(callback, null, sessions)}这个方法获取所有的session集合,回调函数第一个参数是错误信息,第二个参数是获取到的所有的session集合
clear方法清除所有的session:
//清除所有的session对象,如果指定了callback也就是清除所有的session时候的回调函数那么我们就调用callback,而且是延迟调用的!MemoryStore.prototype.clear = function clear(callback) { this.sessions = Object.create(null) callback && defer(callback)}destroy方法用于清除指定的session
//destroy函数用于销毁指定的sessionId的session,首先要从MemoryStore对象的sessions集合中把这个sessionId对应的session删除MemoryStore.prototype.destroy = function destroy(sessionId, callback) { delete this.sessions[sessionId] callback && defer(callback)}get方法获取指定的sessionID对应的sesison
//get用于获取指定的sessionId的session,延迟执行callback回调,传入的参数第一个为null,第二个就是指定的session对象!MemoryStore.prototype.get = function get(sessionId, callback) { defer(callback, null, getSession.call(this, sessionId))}length方法获取活动的session的数量
//获取活动的session的数量,调用MemoryStore的all方法,传入一个回调函数,回调函数第一个参数是活动session的数量MemoryStore.prototype.length = function length(callback) { this.all(function (err, sessions) { if (err) return callback(err) callback(null, Object.keys(sessions).length) })}set方法用于对指定的sessionID对应的session进行更新
//把指定的sessionId的值对应的session重新赋值,也就是更新这个session!MemoryStore.prototype.set = function set(sessionId, session, callback) { this.sessions[sessionId] = JSON.stringify(session) callback && defer(callback)}touch方法用于把当前session的cookie设置为一个新的cookie,同时更新sessions集合!
//通过指定的sessionId获取当前的session对象,然后把这个对象的cookie更新为新的session对应的cookie,同时sessions中的当前session也进行更新(包括过期时间等)MemoryStore.prototype.touch = function touch(sessionId, session, callback) { var currentSession = getSession.call(this, sessionId) if (currentSession) { // update expiration currentSession.cookie = session.cookie this.sessions[sessionId] = JSON.stringify(currentSession) } callback && defer(callback)}最后提供了一个工具方法getSession方法用于获取指定的sessionid对应的sesison,然后把这个session转化为JSON对象,如果这个session已经过期那么直接删除这个session并返回,否则返回获取到的session对象
//通过sessionId来获取指定的session对象function getSession(sessionId) { var sess = this.sessions[sessionId] if (!sess) { return } // parse sess = JSON.parse(sess) //解析这个session对象 var expires = typeof sess.cookie.expires === 'string' ? new Date(sess.cookie.expires) : sess.cookie.expires //把指定的session.cookie.expires字符串设置为Date类型(如果是string类型,因为调用了JSON.parse方法) // destroy expired session if (expires && expires <= Date.now()) { delete this.sessions[sessionId] return } //如果这个session已经过期了那么直接删除了 return sess}我们把MemoryStore的代码贴出来,有兴趣的可以研究研究:
'use strict';var Store = require('./store')var util = require('util') //指定defer函数,默认是setImeditate,如果不存在那么就自定义。这个setImmediate在node.js中用于延迟执行一个函数var defer = typeof setImmediate === 'function' ? setImmediate : function(fn){ process.nextTick(fn.bind.apply(fn, arguments)) }module.exports = MemoryStore//保存到内存中的MemoryStore,同时创建一个session为空对象{},同时继承了Store的//保存到内存中的MemoryStore,同时创建一个session为空对象{},同时继承了Store的,load,createSession方法function MemoryStore() { Store.call(this) this.sessions = Object.create(null)}//继承了Store中的所有的原型属性util.inherits(MemoryStore, Store)//该方法用户获取所有活动态的SessionMemoryStore.prototype.all = function all(callback) { //MemoryStore对象有一个sessions属性用于保存所有的session集合 var sessionIds = Object.keys(this.sessions) var sessions = Object.create(null) for (var i = 0; i < sessionIds.length; i++) { var sessionId = sessionIds[i] //调用getSession方法,这个方法的this指向了MemoryStore对象,第一个参数传入了sesisonID var session = getSession.call(this, sessionId) //如果存在session那么保存到结果数组中 if (session) { sessions[sessionId] = session; } } //最后调用defer函数把sessions对象传入到这个函数中 callback && defer(callback, null, sessions)}//清除所有的session对象,如果指定了callback也就是清除所有的session时候的回调函数那么我们就调用callback,而且是延迟调用的!MemoryStore.prototype.clear = function clear(callback) { this.sessions = Object.create(null) callback && defer(callback)} //destroy函数用于销毁指定的sessionId的session,首先要从MemoryStore对象的sessions集合中把这个sessionId对应的session删除MemoryStore.prototype.destroy = function destroy(sessionId, callback) { delete this.sessions[sessionId] callback && defer(callback)} //get用于获取指定的sessionId的session,延迟执行callback回调,传入的参数第一个为null,第二个就是指定的session对象!MemoryStore.prototype.get = function get(sessionId, callback) { defer(callback, null, getSession.call(this, sessionId))}//获取活动的session的数量,调用MemoryStore的all方法,传入一个回调函数,回调函数第一个参数是活动session的数量MemoryStore.prototype.length = function length(callback) { this.all(function (err, sessions) { if (err) return callback(err) callback(null, Object.keys(sessions).length) })}//把指定的sessionId的值对应的session重新赋值,也就是更新这个session!MemoryStore.prototype.set = function set(sessionId, session, callback) { this.sessions[sessionId] = JSON.stringify(session) callback && defer(callback)}//通过指定的sessionId获取当前的session对象,然后把这个对象的cookie更新为新的session对应的cookie,同时sessions中的当前session也进行更新(包括过期时间等)MemoryStore.prototype.touch = function touch(sessionId, session, callback) { var currentSession = getSession.call(this, sessionId) if (currentSession) { // update expiration currentSession.cookie = session.cookie this.sessions[sessionId] = JSON.stringify(currentSession) } callback && defer(callback)}//通过sessionId来获取指定的session对象function getSession(sessionId) { var sess = this.sessions[sessionId] if (!sess) { return } // parse sess = JSON.parse(sess) //解析这个session对象 var expires = typeof sess.cookie.expires === 'string' ? new Date(sess.cookie.expires) : sess.cookie.expires //把指定的session.cookie.expires字符串设置为Date类型(如果是string类型,因为调用了JSON.parse方法) // destroy expired session if (expires && expires <= Date.now()) { delete this.sessions[sessionId] return } //如果这个session已经过期了那么直接删除了 return sess}第四步:我们研究一下Cookie,这个对象在store中被广泛使用
session: //req.session域下面保存的是一个Session实例,其中有cookie表示是一个对象 Session { //这里是req.session.cookie是一个Cookie实例 cookie: { path: '/', _expires: Fri May 06 2016 15:44:48 GMT+0800 (中国标准时间), originalMaxAge: 2591999960, httpOnly: true }, flash: { error: [Object] } }这里很显然,req.session中保存的是一个Session对象,但是session对象里面封装的是一个cookie对象,我们看看cookie对象是如何创建的吧:
//从一个JSON格式的sess中创建一个session实例,如sess={cookie:{expires:xx,originalMaxAge:xxx}}Store.prototype.createSession = function(req, sess){ var expires = sess.cookie.expires , orig = sess.cookie.originalMaxAge; //创建session时候获取其中的cookie域下面的expires,originalMaxAge参数 sess.cookie = new Cookie(sess.cookie); //更新session.cookie为一个Cookie实例而不再是一个{}对象了 if ('string' == typeof expires) sess.cookie.expires = new Date(expires); sess.cookie.originalMaxAge = orig; //为新构建的cookie添加originalMaxAge属性 req.session = new Session(req, sess); //创建一个session实例,其中传入的第一个参数是req,第二个参数是sess也就是我们刚才创建的那个Cookie实例,签名为sess={cookie:cookie对象} return req.session;};其中Session中cookie的创建是通过new Cookie(sess.cookie)完成的,其中参数会被全部封装到Cookie的所有默认的属性之上,作为用户自定义的属性来设置:
//new Cookie(sess.cookie);其中sess={cookie:{expires:xx,originalMaxAge:xxx}}var Cookie = module.exports = function Cookie(options) { this.path = '/'; this.maxAge = null; this.httpOnly = true; if (options) merge(this, options); this.originalMaxAge = undefined == this.originalMaxAge ? this.maxAge : this.originalMaxAge; //默认的originalMaxAge就是this.maxAge也就是null,如果指定了originalMaxAge那么就是用户指定的值};
默认的path为/,默认的maxAge为null,默认的httponly为true,默认的originalMaxAge为maxAge的值!我们把Cookie所有的代码贴出来,有兴趣的可以仔细研读:
'use strict';var merge = require('utils-merge') , cookie = require('cookie'); //new Cookie(sess.cookie);其中sess={cookie:{expires:xx,originalMaxAge:xxx}}var Cookie = module.exports = function Cookie(options) { this.path = '/'; this.maxAge = null; this.httpOnly = true; if (options) merge(this, options); this.originalMaxAge = undefined == this.originalMaxAge ? this.maxAge : this.originalMaxAge; //默认的originalMaxAge就是this.maxAge也就是null,如果指定了originalMaxAge那么就是用户指定的值};Cookie.prototype = { set expires(date) { this._expires = date; this.originalMaxAge = this.maxAge; }, get expires() { return this._expires; }, set maxAge(ms) { this.expires = 'number' == typeof ms ? new Date(Date.now() + ms) : ms; }, get maxAge() { return this.expires instanceof Date ? this.expires.valueOf() - Date.now() : this.expires; }, get data() { return { originalMaxAge: this.originalMaxAge , expires: this._expires , secure: this.secure , httpOnly: this.httpOnly , domain: this.domain , path: this.path } }, serialize: function(name, val){ return cookie.serialize(name, val, this.data); }, toJSON: function(){ return this.data; }};第五步:我们来仔细研读一下Session的部分:
function Session(req, data) { Object.defineProperty(this, 'req', { value: req }); Object.defineProperty(this, 'id', { value: req.sessionID }); if (typeof data === 'object' && data !== null) { // merge data into this, ignoring prototype properties for (var prop in data) { if (!(prop in this)) { this[prop] = data[prop] } } }}我们看到Session的构造部分会给session提供一个req属性,其值就是我们传入的第一个参数,同时id就是我们传入的第一个参数的sesisonID属性。第二个参数会全部原封不动的封装到我们创建的session对象上面,也就是第二个参数是我们的附加的额外的数据。有一点要注意:这个库重写了defineProperty方法,所有id.req等无法迭代出来:
//重写了Object对象的defineProperty,其中defineProperty用于为这个对象指定一个函数,其中第二个参数是函数的名称,第三个是函数本身function defineMethod(obj, name, fn) { Object.defineProperty(obj, name, { configurable: true, enumerable: false, value: fn, writable: true });};第一个方法resetMaxAge方法
//resetMaxAge方法,用于为cookie的maxAge指定为cookie的originalMaxAgedefineMethod(Session.prototype, 'resetMaxAge', function resetMaxAge() { this.cookie.maxAge = this.cookie.originalMaxAge; return this;});很显然这个方法会把cookie的maxAge属性重新设置为cookie的originalMaxAge的值
第二个方法touch方法
//重置".cookie.maxAge"防止在session仍然存活的时候cookie已经过期了defineMethod(Session.prototype, 'touch', function touch() { return this.resetMaxAge();});内部调用了restMaxAge防止session仍然存活的时候cookie已经过去了
第三个方法save方法
//为session指定一个save方法,这个方法有一个可选的fn(err)方法defineMethod(Session.prototype, 'save', function save(fn) { this.req.sessionStore.set(this.id, this, fn || function(){}); return this;});我们从最上面的打印结果可以看到这里首先获取到req.sessionStore中的对象,然后把指定的id的sesison设置为当前的session,也就是保存session的功能了。
第四个方法reload用于重新装载session
defineMethod(Session.prototype, 'reload', function reload(fn) { var req = this.req , store = this.req.sessionStore; store.get(this.id, function(err, sess){ if (err) return fn(err); if (!sess) return fn(new Error('failed to load session')); store.createSession(req, sess); fn(); }); return this;});第五个方法destroy用于销毁session
//销毁session中的数据,首先删除req中的session对象,第二步就是调用sessionStore对象的destroy方法defineMethod(Session.prototype, 'destroy', function destroy(fn) { delete this.req.session; this.req.sessionStore.destroy(this.id, fn); return this;});第六个方法regenerate用于产生一个新的session
//重新为这个请求产生一个session对象defineMethod(Session.prototype, 'regenerate', function regenerate(fn) { this.req.sessionStore.regenerate(this.req, fn); return this;});
第六步:我们研究我们最后的express-session库
var Session = require('./session/session') , MemoryStore = require('./session/memory') , Cookie = require('./session/cookie') , Store = require('./session/store')// environmentvar env = process.env.NODE_ENV;//获取当前的环境exports = module.exports = session;exports.Store = Store;exports.Cookie = Cookie;exports.Session = Session;exports.MemoryStore = MemoryStore;var warning = 'Warning: connect.session() MemoryStore is not\n' + 'designed for a production environment, as it will leak\n' + 'memory, and will not scale past a single process.';var defer = typeof setImmediate === 'function' ? setImmediate : function(fn){ process.nextTick(fn.bind.apply(fn, arguments)) }大部分都是上面讨论过的内容
var options = options || {} // name - previously "options.key" , name = options.name || options.key || 'connect.sid' //其中name保存的就是用户传入的name,或者key,默认是name是"connect.sid" , store = options.store || new MemoryStore //store默认是一个新创建的MemoryStore对象 , trustProxy = options.proxy //保存proxy属性 , storeReady = true //storeReady默认为true , rollingSessions = options.rolling || false; //rolling默认为false var cookieOptions = options.cookie || {}; //cookieOptions保存的options参数中的cookie对象 var resaveSession = options.resave; //resave参数 var saveUninitializedSession = options.saveUninitialized; //saveUninitialized参数 var secret = options.secret; //用户传入的secret参数 var generateId = options.genid || generateSessionId; //用户传入的genid用于产生sessionID,默认是generateSessionId if (typeof generateId !== 'function') { throw new TypeError('genid option must be a function'); } //如果用户不指定resaveSession那么提示用户并设置resaveSession为true if (resaveSession === undefined) { deprecate('undefined resave option; provide resave option'); resaveSession = true; } //如果用户不指定saveUninitializedSession那么提示用户并设置saveUninitializedSession为true if (saveUninitializedSession === undefined) { deprecate('undefined saveUninitialized option; provide saveUninitialized option'); saveUninitializedSession = true; } //如果用户指定了unset,但是unset不是destroy/keep,那么保存 if (options.unset && options.unset !== 'destroy' && options.unset !== 'keep') { throw new TypeError('unset option must be "destroy" or "keep"'); } // TODO: switch to "destroy" on next major var unsetDestroy = options.unset === 'destroy'; //unsetDestroy表示用户是否指定了unset参数是destroy,是布尔值 if (Array.isArray(secret) && secret.length === 0) { throw new TypeError('secret option array must contain one or more strings'); } //保证secret保存的是一个数组,即使用户传入的仅仅是一个string if (secret && !Array.isArray(secret)) { secret = [secret]; } //必须提供secret参数 if (!secret) { deprecate('req.secret; provide secret option'); } // notify user that this store is not // meant for a production environment //如果在生产环境下,同时store也就是用户传入的store(默认为MemoryStore)是MemoryStore那么给出警告 if ('production' == env && store instanceof MemoryStore) { console.warn(warning); }保存用户自定义的内容,然后对内容进行校验,如unset必须为keep或者destroy等,如果是生产环境不能用MemoryStore
// generates the new session //为用于指定的store添加一个方法generate,同时为这个方法传入req对象,在这个generate方法中为req指定了sessionID,session,session.cookie //如果用户传入的secure为auto, store.generate = function(req){ req.sessionID = generateId(req); req.session = new Session(req); req.session.cookie = new Cookie(cookieOptions); //用户指定的secure参数如果是auto,那么修改req.session.cookie的secure参数,并通过issecure来判断 if (cookieOptions.secure === 'auto') { req.session.cookie.secure = issecure(req, trustProxy); } };这个generate方法在通用的store的regenerate方法中被调用
Store.prototype.regenerate = function(req, fn){ var self = this; //regenerate底层调用的是destroy方法,第一个参数是req.sessionID,至于回调中的self.generate必须是对容器进行指定的 this.destroy(req.sessionID, function(err){ self.generate(req); fn(err);//最后回调fn }); //调用这个store的destory方法,销毁req.sessionID,销毁成功后通过刚才的store的generate方法产生一个sessionID};如果设置了cookie的secure属性为auto我们使用下面的方法来判断是否是安全的请求
function issecure(req, trustProxy) { // socket is https server //如果是https服务器那么返回true if (req.connection && req.connection.encrypted) { return true; } // do not trust proxy //如果不是https服务器,同时为trust-proxy指定了false那么表示不需要安全传输,这时候返回false if (trustProxy === false) { return false; } //如果trustProxy不是true,获取请求中的secure属性,如果secure是布尔值那么按照布尔值返回 // no explicit trust; try req.secure from express if (trustProxy !== true) { var secure = req.secure; return typeof secure === 'boolean' ? secure : false; } // read the proto from x-forwarded-proto header var header = req.headers['x-forwarded-proto'] || ''; var index = header.indexOf(','); var proto = index !== -1 //获取x-forwarded-proto头,如果是有逗号分开的字符串,那么获取逗号前的部分,否则获取整个字符串 ? header.substr(0, index).toLowerCase().trim() : header.toLowerCase().trim() //如果获取到的x-forwarded-proto头前一部分是https那么返回true否则返回false return proto === 'https';}首先判断req.connection.encrypted如果为true那么返回true;如果明确指定了proxy为false那么返回false,否则从req.secure进行判断;最后从req.headers['x-forwarded-proto']判断。其中req.connection属性保存的是一个Socket实例:
Socket { _connecting: false, _hadError: false, _handle: TCP { _externalStream: {}, fd: -1, reading: true, owner: [Circular], onread: [Function: onread], onconnection: null, writeQueueSize: 0 }, _parent: null, _host: null, _readableState: ReadableState { objectMode: false, highWaterMark: 16384, buffer: [], length: 0, pipes: null, pipesCount: 0, flowing: true, ended: false, endEmitted: false, reading: true, sync: false, needReadable: true, emittedReadable: false, readableListening: false, resumeScheduled: false, defaultEncoding: 'utf8', ranOut: false, awaitDrain: 0, readingMore: false, decoder: null, encoding: null }, readable: true, domain: null, _events: { end: [ [Object], [Function: socketOnEnd] ], finish: [Function: onSocketFinish], _socketEnd: [Function: onSocketEnd], drain: [ [Function: ondrain], [Function: socketOnDrain] ], timeout: [Function], error: [ [Function: socketOnError], [Function: onevent] ], close: [ [Function: serverSocketCloseListener], [Function: onServerResponseClose], [Function: onevent] ], data: [Function: socketOnData], resume: [Function: onSocketResume], pause: [Function: onSocketPause] }, _eventsCount: 10, _maxListeners: undefined, _writableState: WritableState { objectMode: false, highWaterMark: 16384, needDrain: false, ending: false, ended: false, finished: false, decodeStrings: false, defaultEncoding: 'utf8', length: 0, writing: false, corked: 0, sync: true, bufferProcessing: false, onwrite: [Function], writecb: null, writelen: 0, bufferedRequest: null, lastBufferedRequest: null, pendingcb: 0, prefinished: false, errorEmitted: false, bufferedRequestCount: 0, corkedRequestsFree: CorkedRequest { next: [Object], entry: null, finish: [Function] } }, writable: true, allowHalfOpen: true, destroyed: false, bytesRead: 0, _bytesDispatched: 0, _sockname: null, _pendingData: null, _pendingEncoding: '', server: Server { domain: null, _events: { request: [Object], connection: [Function: connectionListener], clientError: [Function] }, _eventsCount: 3, _maxListeners: undefined, _connections: 3, _handle: TCP { _externalStream: {}, fd: -1, reading: false, owner: [Circular], onread: null, onconnection: [Function: onconnection], writeQueueSize: 0 }, _usingSlaves: false, _slaves: [], _unref: false, allowHalfOpen: true, pauseOnConnect: false, httpAllowHalfOpen: false, timeout: 120000, _pendingResponseData: 0, _connectionKey: '6::::3000' }, _server: Server { domain: null, _events: { request: [Object], connection: [Function: connectionListener], clientError: [Function] }, _eventsCount: 3, _maxListeners: undefined, _connections: 3, _handle: TCP { _externalStream: {}, fd: -1, reading: false, owner: [Circular], onread: null, onconnection: [Function: onconnection], writeQueueSize: 0 }, _usingSlaves: false, _slaves: [], _unref: false, allowHalfOpen: true, pauseOnConnect: false, httpAllowHalfOpen: false, timeout: 120000, _pendingResponseData: 0, _connectionKey: '6::::3000' }, _idleTimeout: 120000, _idleNext: { _idleNext: Socket { _connecting: false, _hadError: false, _handle: [Object], _parent: null, _host: null, _readableState: [Object], readable: true, domain: null, _events: [Object], _eventsCount: 10, _maxListeners: undefined, _writableState: [Object], writable: true, allowHalfOpen: true, destroyed: false, bytesRead: 0, _bytesDispatched: 0, _sockname: null, _pendingData: null, _pendingEncoding: '', server: [Object], _server: [Object], _idleTimeout: 120000, _idleNext: [Object], _idlePrev: [Circular], _idleStart: 5397, parser: [Object], on: [Function: socketOnWrap], _paused: false, read: [Function], _consuming: true }, _idlePrev: [Circular] }, _idlePrev: Socket { _connecting: false, _hadError: false, _handle: TCP { _externalStream: {}, fd: -1, reading: true, owner: [Circular], onread: [Function: onread], onconnection: null, writeQueueSize: 0 }, _parent: null, _host: null, _readableState: ReadableState { objectMode: false, highWaterMark: 16384, buffer: [], length: 0, pipes: null, pipesCount: 0, flowing: true, ended: false, endEmitted: false, reading: true, sync: false, needReadable: true, emittedReadable: false, readableListening: false, resumeScheduled: false, defaultEncoding: 'utf8', ranOut: false, awaitDrain: 0, readingMore: false, decoder: null, encoding: null }, readable: true, domain: null, _events: { end: [Object], finish: [Function: onSocketFinish], _socketEnd: [Function: onSocketEnd], drain: [Object], timeout: [Function], error: [Function: socketOnError], close: [Function: serverSocketCloseListener], data: [Function: socketOnData], resume: [Function: onSocketResume], pause: [Function: onSocketPause] }, _eventsCount: 10, _maxListeners: undefined, _writableState: WritableState { objectMode: false, highWaterMark: 16384, needDrain: false, ending: false, ended: false, finished: false, decodeStrings: false, defaultEncoding: 'utf8', length: 0, writing: false, corked: 0, sync: true, bufferProcessing: false, onwrite: [Function], writecb: null, writelen: 0, bufferedRequest: null, lastBufferedRequest: null, pendingcb: 0, prefinished: false, errorEmitted: false, bufferedRequestCount: 0, corkedRequestsFree: [Object] }, writable: true, allowHalfOpen: true, destroyed: false, bytesRead: 0, _bytesDispatched: 0, _sockname: null, _pendingData: null, _pendingEncoding: '', server: Server { domain: null, _events: [Object], _eventsCount: 3, _maxListeners: undefined, _connections: 3, _handle: [Object], _usingSlaves: false, _slaves: [], _unref: false, allowHalfOpen: true, pauseOnConnect: false, httpAllowHalfOpen: false, timeout: 120000, _pendingResponseData: 0, _connectionKey: '6::::3000' }, _server: Server { domain: null, _events: [Object], _eventsCount: 3, _maxListeners: undefined, _connections: 3, _handle: [Object], _usingSlaves: false, _slaves: [], _unref: false, allowHalfOpen: true, pauseOnConnect: false, httpAllowHalfOpen: false, timeout: 120000, _pendingResponseData: 0, _connectionKey: '6::::3000' }, _idleTimeout: 120000, _idleNext: [Circular], _idlePrev: Socket { _connecting: false, _hadError: false, _handle: [Object], _parent: null, _host: null, _readableState: [Object], readable: true, domain: null, _events: [Object], _eventsCount: 10, _maxListeners: undefined, _writableState: [Object], writable: true, allowHalfOpen: true, destroyed: false, bytesRead: 0, _bytesDispatched: 0, _sockname: null, _pendingData: null, _pendingEncoding: '', server: [Object], _server: [Object], _idleTimeout: 120000, _idleNext: [Circular], _idlePrev: [Object], _idleStart: 5397, parser: [Object], on: [Function: socketOnWrap], _paused: false, read: [Function], _consuming: true }, _idleStart: 5396, parser: HTTPParser { '0': [Function: parserOnHeaders], '1': [Function: parserOnHeadersComplete], '2': [Function: parserOnBody], '3': [Function: parserOnMessageComplete], '4': [Function: onParserExecute], _headers: [], _url: '', _consumed: true, socket: [Circular], incoming: null, outgoing: null, maxHeaderPairs: 2000, onIncoming: [Function: parserOnIncoming] }, on: [Function: socketOnWrap], _paused: false, read: [Function], _consuming: true }, _idleStart: 5392, parser: HTTPParser { '0': [Function: parserOnHeaders], '1': [Function: parserOnHeadersComplete], '2': [Function: parserOnBody], '3': [Function: parserOnMessageComplete], '4': [Function: onParserExecute], _headers: [], _url: '', _consumed: true, socket: [Circular], incoming: IncomingMessage { _readableState: [Object], readable: true, domain: null, _events: {}, _eventsCount: 0, _maxListeners: undefined, socket: [Circular], connection: [Circular], httpVersionMajor: 1, httpVersionMinor: 1, httpVersion: '1.1', complete: true, headers: [Object], rawHeaders: [Object], trailers: {}, rawTrailers: [], upgrade: false, url: '/', method: 'GET', statusCode: null, statusMessage: null, client: [Circular], _consuming: false, _dumped: false, next: [Function: next], baseUrl: '', originalUrl: '/', _parsedUrl: [Object], params: {}, query: [Object], res: [Object], _startAt: [Object], _startTime: Thu Apr 07 2016 09:51:10 GMT+0800 (中国标准时间), _remoteAddress: '::1', body: {}, files: {}, secret: undefined, cookies: [Object], signedCookies: {}, _parsedOriginalUrl: [Object], sessionStore: [Object], sessionID: '-ZkSm8urr8KsXAKsZbSTCp8EWOu7zq2o', session: [Object], flash: [Function: _flash], offset: 0, skip: 0, route: [Object] }, outgoing: null, maxHeaderPairs: 2000, onIncoming: [Function: parserOnIncoming] }, on: [Function: socketOnWrap], _paused: false, read: [Function], _consuming: true, _httpMessage: ServerResponse { domain: null, _events: { finish: [Object], end: [Function: onevent] }, _eventsCount: 2, _maxListeners: undefined, output: [], outputEncodings: [], outputCallbacks: [], outputSize: 0, writable: true, _last: false, chunkedEncoding: false, shouldKeepAlive: true, useChunkedEncodingByDefault: true, sendDate: true, _removedHeader: {}, _contentLength: null, _hasBody: true, _trailer: '', finished: false, _headerSent: false, socket: [Circular], connection: [Circular], _header: null, _headers: { 'x-powered-by': 'Express' }, _headerNames: { 'x-powered-by': 'X-Powered-By' }, _onPendingData: [Function: updateOutgoingData], req: IncomingMessage { _readableState: [Object], readable: true, domain: null, _events: {}, _eventsCount: 0, _maxListeners: undefined, socket: [Circular], connection: [Circular], httpVersionMajor: 1, httpVersionMinor: 1, httpVersion: '1.1', complete: true, headers: [Object], rawHeaders: [Object], trailers: {}, rawTrailers: [], upgrade: false, url: '/', method: 'GET', statusCode: null, statusMessage: null, client: [Circular], _consuming: false, _dumped: false, next: [Function: next], baseUrl: '', originalUrl: '/', _parsedUrl: [Object], params: {}, query: [Object], res: [Circular], _startAt: [Object], _startTime: Thu Apr 07 2016 09:51:10 GMT+0800 (中国标准时间), _remoteAddress: '::1', body: {}, files: {}, secret: undefined, cookies: [Object], signedCookies: {}, _parsedOriginalUrl: [Object], sessionStore: [Object], sessionID: '-ZkSm8urr8KsXAKsZbSTCp8EWOu7zq2o', session: [Object], flash: [Function: _flash], offset: 0, skip: 0, route: [Object] }, locals: { paginate: [Object] }, __onFinished: { [Function: listener] queue: [Object] }, writeHead: [Function: writeHead], end: [Function: end] }, _peername: { address: '::1', family: 'IPv6', port: 51294 } }
我们必须弄清楚在Express是如何调用这个插件的:
app.use(session({ secret: settings.cookieSecret, //blog=s%3AisA3_M-Vso0L_gHvUnPb8Kw9DohpCCBJ.OV7p42pL91uM3jueaJATpZdlIj%2BilgxWoD8HmBSLUSo //其中secret如果是一个string,那么就是用这个string对sessionID对应的cookie进行签名,如果是一个数组那么只有第一个用于签名,其他用于浏览器请求后的验证 key: settings.db, //设置的cookie的名字,从上面可以看到这里指定的是blog,所以浏览器的请求中可以看到这里的sessionID已经不是sessionID了,而是这里的blog cookie: { maxAge: 1000 * 60 * 60 * 24 * 30 }, //cookie里面全部的设置都是对于sessionID的属性的设置,默认的属性为{ path: '/', httpOnly: true, secure: false, maxAge: null }. //所以最后我们保存到数据库里面的信息就是:{"cookie":{"originalMaxAge":2592000000,"expires":"2016-04-27T02:30:51.713Z","httpOnly":true,"path":"/"},"flash":{}} store: new MongoStore({ db: settings.db, host: settings.host, port: settings.port })}));因此返回的应该是一个中间件:通过下面的代码就能够知道
//返回的是一个中间件,用于Express调用 return function session(req, res, next) { // self-awareness //如果req中包含了session属性那么直接调用下一个中间件,不用对session进行任何处理 if (req.session) return next(); //如果storeReady为false那么给出debug信息,同时调用下一个中间件 // Handle connection as if there is no session if // the store has temporarily disconnected etc if (!storeReady) return debug('store is disconnected'), next(); // pathname mismatch //parseurl模块用于解析特定请求对象的URL,像req.url属性一样。返回的对象和Node.js为我们提供的url.parse一样。在一个req对象中多次调用这个方法 //而且req.url没有变化,这时候就会返回一个缓存的对象,而不是重新解析。如果是字符串,parseUrl.original(req)用于解析req.originalUrl,否则用于解析req.url //解析结果和Node.js核心模块url.parse一样,而且多次调用req.originalUrl如果不变那么会返回缓存对象 var originalPath = parseUrl.original(req).pathname; if (originalPath.indexOf(cookieOptions.path || '/') !== 0) return next(); //如果来源的URL和我们自己指定的cookie的path不匹配那么直接调用下一个中间件 // ensure a secret is available or bail if (!secret && !req.secret) { next(new Error('secret option required for sessions')); return; } // backwards compatibility for signed cookies // req.secret is passed from the cookie parser middleware var secrets = secret || [req.secret]; //如果secret不存在那么从req.secret中获取,这时候可能是由cookie parser中间件来获取到的signed cookie var originalHash; var originalId; var savedHash; // expose store req.sessionStore = store; //为请求对象指定sessionStore属性为上面的store对象,也就是我们在options中传入的store对象,默认是一个MemoryStore对象 // get the session ID from the cookie var cookieId = req.sessionID = getcookie(req, name, secrets); //为req对象设置sessionID属性,这个属性通过getcookie方法来获取,其中name为:name = options.name || options.key || 'connect.sid'其中sessionID就是获取到的值 // set-cookie //onHeaders(res, listener)其中listener方法当headers被添加到res中将会被触发,其中listener将会成为res对象的this对象。headers只会被调用一次,也就是当头被发送到客户端之前触发的 //如果在res上多次调用onHeaders方法,那么listener会按照他们添加的逆顺序触发 onHeaders(res, function(){ //如果没有session那么返回 if (!req.session) { debug('no session'); return; } //获取session中的cookie var cookie = req.session.cookie; //如果cookie设置了secure但是不是https连接,直接返回 // only send secure cookies via https if (cookie.secure && !issecure(req, trustProxy)) { debug('not secured'); return; } //如果不需要设置cookie那么直接返回 if (!shouldSetCookie(req)) { return; } //设置cookie,把所有的 setcookie(res, name, req.sessionID, secrets[0], cookie.data); }); // proxy end() to commit the session var _end = res.end; var _write = res.write; var ended = false; //重写res.end方法,接受两个参数chunk和encoding res.end = function end(chunk, encoding) { if (ended) { return false; } //这时候ended为true了 ended = true; var ret; var sync = true; //writeend方法,内部调用end方法,其中end中this为res对象,第一个参数是chunk,第二个参数为encoding function writeend() { if (sync) { ret = _end.call(res, chunk, encoding); sync = false; return; } //如果sync是false那么直接执行end方法 _end.call(res); } //writetop方法 function writetop() { if (!sync) { return ret; } if (chunk == null) { ret = true; return ret; } //获取Content-Length响应头 var contentLength = Number(res.getHeader('Content-Length')); //响应头为数字 if (!isNaN(contentLength) && contentLength > 0) { // measure chunk chunk = !Buffer.isBuffer(chunk) ? new Buffer(chunk, encoding) : chunk; //给chunk设置为Buffer对象 encoding = undefined; if (chunk.length !== 0) { debug('split response'); //调用write方法把我们的数据写到客户端去 ret = _write.call(res, chunk.slice(0, chunk.length - 1)); //重置chunk chunk = chunk.slice(chunk.length - 1, chunk.length); return ret; } } //通过write把数据写出到浏览器端 ret = _write.call(res, chunk, encoding); sync = false; return ret; } if (shouldDestroy(req)) { // destroy session debug('destroying'); //如果需要销毁session那么调用store的destroy方法,传入destrory方法第一个参数是req.sessionID store.destroy(req.sessionID, function ondestroy(err) { if (err) { //var defer = typeof setImmediate === 'function'? setImmediate: function(fn){ process.nextTick(fn.bind.apply(fn, arguments)) } //defer默认是setImmediate函数,如果不存在setImmediate那么调用下一个中间件。这是在报错的情况下处理方式 defer(next, err); } debug('destroyed'); writeend();//调用writeEnd }); //调用writetop return writetop(); } // no session to save //没有session需要保存 if (!req.session) { debug('no session'); return _end.call(res, chunk, encoding); } //调用req.session的touch方法 // touch session req.session.touch(); //如果需要保存那么重新保存 if (shouldSave(req)) { req.session.save(function onsave(err) { if (err) { defer(next, err); } writeend(); }); return writetop(); } else if (storeImplementsTouch && shouldTouch(req)) { // store implements touch method debug('touching'); //触发touch事件 store.touch(req.sessionID, req.session, function ontouch(err) { if (err) { defer(next, err); } debug('touched'); writeend(); }); return writetop(); } return _end.call(res, chunk, encoding); };我们仔细分析一下上面的代码:
// self-awareness //如果req中包含了session属性那么直接调用下一个中间件,不用对session进行任何处理 if (req.session) return next(); //如果storeReady为false那么给出debug信息,同时调用下一个中间件 // Handle connection as if there is no session if // the store has temporarily disconnected etc if (!storeReady) return debug('store is disconnected'), next(); // pathname mismatch //parseurl模块用于解析特定请求对象的URL,像req.url属性一样。返回的对象和Node.js为我们提供的url.parse一样。在一个req对象中多次调用这个方法 //而且req.url没有变化,这时候就会返回一个缓存的对象,而不是重新解析。如果是字符串,parseUrl.original(req)用于解析req.originalUrl,否则用于解析req.url //解析结果和Node.js核心模块url.parse一样,而且多次调用req.originalUrl如果不变那么会返回缓存对象 var originalPath = parseUrl.original(req).pathname; if (originalPath.indexOf(cookieOptions.path || '/') !== 0) return next(); //如果来源的URL和我们自己指定的cookie的path不匹配那么直接调用下一个中间件如果含有session那么直接调用下一个中间件,如果express-sesison停止工作了调用下一个中间件,或者路径不匹配调用下一个中间件
// ensure a secret is available or bail if (!secret && !req.secret) { next(new Error('secret option required for sessions')); return; } // backwards compatibility for signed cookies // req.secret is passed from the cookie parser middleware var secrets = secret || [req.secret]; //如果secret不存在那么从req.secret中获取,这时候可能是由cookie parser中间件来获取到的signed cookie保存用户的秘钥
//如果secret不存在那么从req.secret中获取,这时候可能是由cookie parser中间件来获取到的signed cookie var originalHash; var originalId; var savedHash; // expose store req.sessionStore = store; //为请求对象指定sessionStore属性为上面的store对象,也就是我们在options中传入的store对象,默认是一个MemoryStore对象 // get the session ID from the cookie var cookieId = req.sessionID = getcookie(req, name, secrets); //为req对象设置sessionID属性,这个属性通过getcookie方法来获取,其中name为:name = options.name || options.key || 'connect.sid'其中sessionID就是获取到的值 // set-cookie为req对象指定sessionStore保存session,同时指定sessionID,其中sessionID是浏览器发送过来的
// var cookieId = req.sessionID = getcookie(req, name, secrets);//作用:用于从请求对象request中获取session ID值,其中name就是我们在options中指定的,首先从req.headers.cookie获取,接着从req.signedCookies中获取,最后从req.cookies获取function getcookie(req, name, secrets) { var header = req.headers.cookie; var raw; var val; // read from cookie header if (header) { var cookies = cookie.parse(header); raw = cookies[name]; if (raw) { if (raw.substr(0, 2) === 's:') { //切割掉前面的字符"s:"! val = unsigncookie(raw.slice(2), secrets); //val表示false意味着客户端传递过来的cookie被篡改了! if (val === false) { debug('cookie signature invalid'); val = undefined; } } else { debug('cookie unsigned') } } } // back-compat read from cookieParser() signedCookies data if (!val && req.signedCookies) { val = req.signedCookies[name]; if (val) { deprecate('cookie should be available in req.headers.cookie'); } } // back-compat read from cookieParser() cookies data if (!val && req.cookies) { raw = req.cookies[name]; if (raw) { if (raw.substr(0, 2) === 's:') { val = unsigncookie(raw.slice(2), secrets); if (val) { deprecate('cookie should be available in req.headers.cookie'); } if (val === false) { debug('cookie signature invalid'); val = undefined; } } else { debug('cookie unsigned') } } } return val;}首先从req.headers.cookie中获取,如果使用了插件cookie-parser那么从req.signedCookies获取,否则直接从req.cookies获取
//onHeaders(res, listener)其中listener方法当headers被添加到res中将会被触发,其中listener将会成为res对象的this对象。headers只会被调用一次,也就是当头被发送到客户端之前触发的 //如果在res上多次调用onHeaders方法,那么listener会按照他们添加的逆顺序触发 onHeaders(res, function(){ //如果没有session那么返回 if (!req.session) { debug('no session'); return; } //获取session中的cookie var cookie = req.session.cookie; //如果cookie设置了secure但是不是https连接,直接返回 // only send secure cookies via https if (cookie.secure && !issecure(req, trustProxy)) { debug('not secured'); return; } //如果不需要设置cookie那么直接返回 if (!shouldSetCookie(req)) { return; } //设置cookie,把所有的 setcookie(res, name, req.sessionID, secrets[0], cookie.data); });
我们看看setCookie如何设置req.sessionID的,记住这里是响应头设置
// setcookie(res, name, req.sessionID, secrets[0], cookie.data);//方法作用:为HTTP响应设置cookie,设置的cookie是把req.sessionID进行加密过后的cookie,其中name用于保存到客户端的sessionID的cookie的名称function setcookie(res, name, val, secret, options) { var signed = 's:' + signature.sign(val, secret); //对要发送的cookie进行加密,密钥为secret var data = cookie.serialize(name, signed, options); //其中options中可能有decode函数,返回序列化的cookie debug('set-cookie %s', data); var prev = res.getHeader('set-cookie') || []; //获取set-cookie头,默认是一个空数组 var header = Array.isArray(prev) ? prev.concat(data) : Array.isArray(data) ? [prev].concat(data) : [prev, data]; //通过set-cookie,发送到客户端 res.setHeader('set-cookie', header)}我们很清楚的看到在res中对req.sessionID进行加密了,同时req.session.cookie.data中保存的是需要设置除了sessionID这个cookie以外的其他的cookie的值。上面有一个函数为shouldSetCookie,我们看看他是如何判断是否需要把cookie保存到浏览器的:
//这个方法用户判断是否需要在请求头中设置cookie // determine if cookie should be set on response function shouldSetCookie(req) { // cannot set cookie without a session ID //如果没有sessionID直接返回,这时候不用设置cookie if (typeof req.sessionID !== 'string') { return false; } //var cookieId = req.sessionID = getcookie(req, name, secrets); return cookieId != req.sessionID //如果服务器端的sesisonID被修改了,这时候如果用户设置了保存未初始化的session或者req.session已经被修改了,那么需要shouldSetCookie为true //如果服务器端的sesionID没有被修改, ? saveUninitializedSession || isModified(req.session) //rollingSessions = options.rolling || false,其中rolling表示sessionCookie在每一个响应中都应该被发送。也就是说如果用户设置了rolling即使sessionID没有被修改 //也依然会把session的cookie发送到浏览器 : rollingSessions || req.session.cookie.expires != null && isModified(req.session); }我们看看isModified是如何判断session被修改了,其实还是通过session中的id属性来判断的,不过这个id的enumerable为false
// check if session has been modified //判断session是否被修改 originalId = req.sessionID;而且 originalHash = hash(req.session); function isModified(sess) { return originalId !== sess.id || originalHash !== hash(sess); }接下来我们在看看这个插件中的一些方法:
如何判断session是否已经保存了
// check if session has been saved //用于判断这个session是否已经被保存了,originalId = req.sessionID; function isSaved(sess) { return originalId === sess.id && savedHash === hash(sess); }我们看看hash方法,是通过循环冗余检验算法来完成的,但是除了对session中的cookie进行检验
//这个hash方法对key不是cookie的部分进行处理,如果不是cookie那么才会通过crc进行处理!//然后经过循环冗余检验算法处理var crc = require('crc');crc.crc32('hello').toString(16);function hash(sess) { return crc(JSON.stringify(sess, function (key, val) { if (key !== 'cookie') { return val; } }));}是否应该销毁一个session
//用于判断是否要销毁session值,如果req.sessionID存在,同时指定了unset方法为destrory而且req.session为null那么销毁 function shouldDestroy(req) { // var unsetDestroy = options.unset === 'destroy'; return req.sessionID && unsetDestroy && req.session == null; }如果这个req的sessionID存在,同时设置了unset为destroy,但是req.session已经为null那么返回true
下面方法用户判断是否应该保存一个session:
//判断是否需要把session保存到到store中 function shouldSave(req) { // cannot set cookie without a session ID if (typeof req.sessionID !== 'string') { debug('session ignored because of bogus req.sessionID %o', req.sessionID); return false; } // var saveUninitializedSession = options.saveUninitialized; // var cookieId = req.sessionID = getcookie(req, name, secrets); //如果指定了saveUninitialized为false,同时cookieId和req.sessionID不相等,那么就判断是否被修改了,修改了就应该保存,否则判断是否已经被保存过了 return !saveUninitializedSession && cookieId !== req.sessionID ? isModified(req.session) : !isSaved(req.session) }如果用户指定了未初始化的session不需要保存,同时req.sesisonID和请求中的cookieID不一致,那么只有当req.session修改了才会保存。否则如果已经保存那么就不会保存
issaved方法如下:
//用于判断这个session是否已经被保存了,originalId = req.sessionID; function isSaved(sess) { return originalId === sess.id && savedHash === hash(sess); }下面我们看看shouldTouch方法
// determine if session should be touched function shouldTouch(req) { // cannot set cookie without a session ID if (typeof req.sessionID !== 'string') { debug('session ignored because of bogus req.sessionID %o', req.sessionID); return false; } return cookieId === req.sessionID && !shouldSave(req); }如果请求中的sessionId和req.sessionID一致,同时shouldSave返回false那么shouldTouch返回true!其他内容分析,参见个人空间
0 0
- Express框架之express-session的插件的攻坚战
- express框架之session
- express框架中,session的使用方法
- express 框架之session(express框架session 内存存储)
- express-session的设置
- express框架的路由
- express框架的理解
- nodejs之express框架的运用
- nodejs express的session验证
- express-session的简单使用说明
- express-session遇到session失效的问题
- express-session(express4.0与express 3.0的区别)
- Express 4.x中间件express-session的详细解析
- node的express框架安装
- 搭建nodejs的express框架
- Node.js的express框架
- express框架的注册登陆
- express 框架之session (cookie和session介绍)
- js的for..in语句的用法详解
- Qt实现企业信息管理系统(1)
- eclipse 集成 jetty服务器 run-jetty-run插件
- https://linux.cn/article-6645-1.html
- 笔记-Java代码中的小工具类
- Express框架之express-session的插件的攻坚战
- jquery实现图片无缝轮播显示(个人随笔)
- Android 扩大点击区域
- jvm性能监控工具的使用
- leetcode——19——Remove Nth Node From End of List
- HTML5 Audio/Video 标签,属性,方法,事件汇总
- Linux学习笔记--ps命令(显示当前进程的命令)
- KMP算法(通俗易懂的字符串比较算法)
- lua与c的交互