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
原创粉丝点击