Express框架之express-session的插件的攻坚战

第一步:我们看看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.OeUddoRalzB4iSmUHcE8oMziad4Ig
7jUT1REzGcYcdg',
     blog: 's:-ZkSm8urr8KsXAKsZbSTCp8EWOu7zq2o.Axjo6YmD2dLPGQK9aD1mR8FcpOzyHaGG6
cfGUWUVK00' },
  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方法用于产生session
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
};
因为这是一个通用的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。所以说l oad仅仅用来重新从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方法用于产生session
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
};

//通过指定的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:

//该方法用户获取所有活动态的Session
MemoryStore.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)
//该方法用户获取所有活动态的Session
MemoryStore.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的originalMaxAge
defineMethod(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')

// environment
var 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!其他内容分析,参见个人空间

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值