Express 프레임워크의express-session 플러그인 공방전

59716 단어
첫 번째 단계: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] 
     }
  }

두 번째 단계: 세션을 저장하는 흔한 메모리 스토어를 살펴보자. 세션을 어떻게 조직하는지 살펴보자. 그러나 공부하기 전에 유니버설 스토어를 먼저 알아야 한다.
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
};
이것은 통용되는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 방법에 대해 말씀드렸으니 Memory Store에서 어떻게 이 destroy 방법을 실현했는지 살펴보겠습니다.
 //destroy         sessionId session,    MemoryStore   sessions      sessionId   session  
MemoryStore.prototype.destroy = function destroy(sessionId, callback) {
  delete this.sessions[sessionId]
  callback && defer(callback)
}
분명히 Memory Store는 지정한 세션을 세션 집합에서 삭제하면 됩니다!
일반적인store는 지정한sessionID를 불러오는 데 사용할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 방법이 하나 있다. 우선 그의 코드를 보고 나서 이야기하자.
//   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 실례를 만들기 전에 쿠키를 만들어야 합니다. 이것은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]   
     }  
  }  
쿠키를 만든 후에 세션을 어떻게 만들었는지 코드가 있습니다. 세션이 어떻게 만들었는지 봅시다.
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 옵션은session ID를 표시하는 동시에 두 번째 파라미터의 모든 속성도 이session에 봉인되어 있다. 최종적으로session 아래에 쿠키 필드가 하나 더 생겼다. 그러면 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 방법을 지정하지 않았다. Memory Store가 어떻게 실현되었는지 살펴보자.
 //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는 세션에서 지정한 세션 ID를 다시 읽는 세션에만 사용됩니다!우리는 통용되는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;
};
세 번째 단계: 방금 통용되는 스토어에 대해 얘기했는데 이제 Memory Store라는 저장된 세션 대상을 분석해 봅시다.
 //  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 방법은 지정한 세션 ID에 대응하는 세션 업데이트에 사용
//    sessionId     session    ,       session!
MemoryStore.prototype.set = function set(sessionId, session, callback) {
  this.sessions[sessionId] = JSON.stringify(session)
  callback && defer(callback)
}
터치 방법은 현재session의 쿠키를 새로운 쿠키로 설정하고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
}
우리는 Memory Store의 코드를 붙여서 연구할 만한 흥미가 있다.
'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
}
네 번째 단계: 쿠키를 연구해 보자. 이 대상은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.세션에 저장된 것은 하나의 세션 대상이지만 세션 대상에 봉인된 것은 하나의 쿠키 대상입니다. 쿠키 대상이 어떻게 만들어졌는지 봅시다.
//   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에서 쿠키의 생성은 new 쿠키(sess.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의 값입니다!우리는 쿠키의 모든 코드를 붙여서 관심 있는 것은 자세히 연구할 수 있다.
'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;
  }
};
5단계: 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;
});
분명히 이 방법은 쿠키의 maxAge 속성을 쿠키의originalMaxAge의 값으로 다시 설정할 것이다
두 번째 방법 터치 방법
//  ".cookie.maxAge"   session       cookie     
defineMethod(Session.prototype, 'touch', function touch() {
  return this.resetMaxAge();
});
내부에서 restMaxAge를 호출하여session이 살아남지 못하게 할 때 쿠키는 이미 지나갔습니다.
세 번째 방법. save 방법.
// session    save  ,          fn(err)  
defineMethod(Session.prototype, 'save', function save(fn) {
  this.req.sessionStore.set(this.id, this, fn || function(){});
  return this;
});
맨 위에 있는 인쇄 결과에서 리q를 먼저 얻을 수 있습니다.세션 스토어의 대상을 지정한 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;
});

6단계: 우리는 우리의 마지막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
' + 'designed for a production environment, as it will leak
' + '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
};
쿠키의 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로 판단하기;마지막으로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.플러그인 쿠키-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);
    });

set Cookie가 Req를 어떻게 설정하는지 봅시다.세션 ID의 응답 헤더 설정임을 기억하십시오
// 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.데이터에sessionID라는 쿠키를 제외한 다른 쿠키의 값을 설정해야 합니다.위의 함수는 shouldSetCookie입니다. 쿠키를 브라우저에 저장해야 하는지 판단하는 방법을 살펴보겠습니다.
     //                   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);
    }
다음은 이 플러그인의 몇 가지 방법을 살펴보겠습니다.
세션이 저장되었는지 판단하는 방법
 // check if session has been saved
    //      session        ,originalId = req.sessionID;
    function isSaved(sess) {
      return originalId === sess.id && savedHash === hash(sess);
    }
hash방법을 살펴보면 순환 불필요한 검사 알고리즘을 통해 완성되지만session의 쿠키를 제외하고는
//  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.sesison ID와 요청 중의 쿠키 ID가 일치하지 않으면 req.세션이 수정되어야만 저장할 수 있습니다.그렇지 않으면 저장이 되면 저장이 안 돼요.
issaved 방법은 다음과 같습니다.
   //      session        ,originalId = req.sessionID;
    function isSaved(sess) {
      return originalId === sess.id && savedHash === hash(sess);
    }
다음은 Should Touch 방법을 살펴보겠습니다.
   // 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.session ID가 일치하고 shouldSave가false로 돌아오면 shouldTouch가true로 돌아갑니다!기타 내용 분석, 개인 공간 참조

좋은 웹페이지 즐겨찾기