node.js中express-session配置项详解


Posted in Javascript onMay 31, 2017

官方地址:阅读

作用:用指定的参数创建一个session中间件,sesison数据不是保存在cookie中,仅仅sessionID保存到cookie中,session的数据仅仅保存在服务器端

警告:默认的服务器端的session存储,MemoryStore不是为了生产环境创建的,大多数情况下会内存泄露,主要用于测试和开发环境

接受的参数:

cookie:也就是session ID的cookie,默认是{ path: '/', httpOnly: true, secure: false, maxAge: null }.

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那么就是用户指定的值 
};

genid:产生一个新的sessionID的函数,一个返回值是string类型的函数会被作为sessionID.这个函数第一个参数是req,所以如果你想要req中的参数产生sessionID还是很不错的

默认函数是使用uid-safe这个库产生id值(产生一个算法上安全的UID,可以用于cookie也可以用于URL。和rand-token和uid2相比,后者由于使用了%导致UID产生偏态,同时可能对UID产生不必要的截断。我们的uid-safe使用的是base64算法,其函数uid(byteLength, callback)中第一个参数是比特长度而不是字符串长度)

app.use(session({ 
  genid: function(req) { 
   return genuuid() // use UUIDs for session IDs  
  }, 
  secret: 'keyboard cat' 
 })

源码片段:

function generateSessionId(sess) { 
 return uid(24); 
} 
 var generateId = options.genid || generateSessionId;

//如果用户没有传入genid参数那么就是默认使用generateSessionId函数来完成

name:在response中sessionID这个cookie的名称。也可以通过这个name读取,默认是connect.sid。如果一台机器上有多个app运行在同样的hostname+port, 那么你需要对这个sessin的cookie进行切割,所以最好的方法还是通过name设置不同的值

name = options.name || options.key || 'connect.sid'
  //很显然cookie的name默认是connect.sid,而且首先获取到的name而不是key 
r cookieId = req.sessionID = getcookie(req, name, secrets);

resave:强制session保存到session store中。即使在请求中这个session没有被修改。但是这个并不一定是必须的,如果客户端有两个并行的请求到你的客户端,一个请求对session的修改可能被另外一个请求覆盖掉,即使第二个请求并没有修改sesion。默认是true,但是默认值已经过时,因此以后default可能会被修改。因此好好研究你的需求选择一个最适用的。大多数情况下你可能需要false 最好的知道你的store是否需要设置resave的方法是通过查看你的store是否实现了touch方法(删除那些空闲的session。同时这个方法也会通知session store指定的session是活动态的),如果实现了那么你可以用resave:false,如果没有实现touch方法,同时你的store对保存的session设置了一个过期的时间,那么建议你用resave:true

var resaveSession = options.resave; 
 if (resaveSession === undefined) { 
  deprecate('undefined resave option; provide resave option'); 
  resaveSession = true;//如果用户没有指定resavedSession那么默认就是true 
 }

我们再来看看其他的逻辑

store.get(req.sessionID, function(err, sess){ 
   // error handling 
   //如果报错那么也会创建一个session 
   if (err) { 
    debug('error %j', err); 
    if (err.code !== 'ENOENT') { 
     next(err); 
     return; 
    } 
    generate(); 
   // no session那么就会创建一个session 
   } else if (!sess) { 
    debug('no session found'); 
    generate(); 
   // populate req.session 
   //如果找到了这个session处理的代码逻辑 
   } else { 
    debug('session found'); 
    store.createSession(req, sess); 
    originalId = req.sessionID; 
    originalHash = hash(sess); 
    //originalHash保存的是找到的这个session的hash结果,如果明确指定了resave为false那么savedHash就是原来的session的结果 
    if (!resaveSession) { 
     savedHash = originalHash 
    } 
    wrapmethods(req.session); 
   } 
   next(); 
  }); 
 }; 
};

其中经过了前面的if语句后我们的savedHash就是originalHash,我们看看这个逻辑在判断这个session是否已经保存的时候再次用到了

function isSaved(sess) { 
   return originalId === sess.id && savedHash === hash(sess); 
  }

rolling:强制在每一个response中都发送session标识符的cookie。如果把expiration设置为一个过去的时间那么 那么过期时间设置为默认的值。roling默认是false。如果把这个值设置为true但是saveUnitialized设置为false,那么cookie不会被包含在响应中(没有初始化的session)

rollingSessions = options.rolling || false;//默认为false

我们看看rolling用于了什么环境了:

//这个方法用户判断是否需要在请求头中设置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  
   ? saveUninitializedSession || isModified(req.session) 
   //rollingSessions = options.rolling || false,其中rolling表示sessionCookie在每一个响应中都应该被发送。也就是说如果用户设置了rolling即使sessionID没有被修改 
   //也依然会把session的cookie发送到浏览器 
   : rollingSessions || req.session.cookie.expires != null && isModified(req.session); 
 }

很显然,如果客户端发送的sessionID和服务器的sessionID一致,如果你指定了rolling为true,那么还是会发送这个session的cookie到客户端,但是如果你设置了rolling为false,那么这时候如果同时设置了req.session.cookie.expires,而且这个req.session被修改了这时候还是会把session的cookie发送到客户端!

saveUninitialized:强制没有“初始化”的session保存到storage中,没有初始化的session指的是:刚被创建没有被修改,如果是要实现登陆的session那么最好设置为false(reducing server storage usage, or complying with laws that require permission before setting a cookie) 而且设置为false还有一个好处,当客户端没有session的情况下并行发送多个请求时。默认是true,但是不建议使用默认值。

var saveUninitializedSession = options.saveUninitialized; 
/如果用户不指定saveUninitializedSession那么提示用户并设置saveUninitializedSession为true 
if (saveUninitializedSession === undefined) { 
 deprecate('undefined saveUninitialized option; provide saveUninitialized option'); 
 saveUninitializedSession = true; 
}

我们来看看这个参数用于做什么判断,首先看看shouldSave方法

// determine if session should be saved to store 
  //判断是否需要把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); 
   return !saveUninitializedSession && cookieId !== req.sessionID 
    ? isModified(req.session) 
    : !isSaved(req.session) 
  }

如果用户指明了不能保存未初始化的session,同时服务器的req.sessionID和浏览器发送过来的不一致,这时候只有在服务器的session修改的时候会保存。如果前面的前提不满足那么就需要看是否已经保存过了,如果没有保存过那么才会保存!

这个参数还被用于决定是否需要把session的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  
    ? saveUninitializedSession || isModified(req.session) 
    //rollingSessions = options.rolling || false,其中rolling表示sessionCookie在每一个响应中都应该被发送。也就是说如果用户设置了rolling即使sessionID没有被修改 
    //也依然会把session的cookie发送到浏览器 
    : rollingSessions || req.session.cookie.expires != null && isModified(req.session); 
  }

如果客户端和服务器端的sessionID不一致的前提下,如果用户指定了保存未初始化的session那么就需要发送,否则就只有在修改的时候才发送

secret:用于对sessionID的cookie进行签名,可以是一个string(一个secret)或者数组(多个secret)。如果指定了一个数组那么只会用 第一个元素对sessionID的cookie进行签名,其他的用于验证请求中的签名。

var secret = options.secret; 
 //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'); 
 }

我们看看这个secret参数用于什么情景:

//作用:用于从请求对象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; 
}

getcookie方法用于从请求中获取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) 
}

用于setcookie方法,该方法用于对sessionID用指定的秘钥进行签名。

store:保存session的地方,默认是一个MemoryStore实例

store = options.store || new MemoryStore 
// 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); 
} 
// 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); 
 } 
}; 
//查看store是否实现了touch方法 
var storeImplementsTouch = typeof store.touch === 'function'; 
//为store注册disconnect事件,在该事件中吧storeReady设置为false 
store.on('disconnect', function(){ storeReady = false; }); 
//为stroe注册connect事件,把storeReady设置为true 
store.on('connect', function(){ storeReady = true; }); 
 // expose store 
 req.sessionStore = store;

我们知道这个store是用于保存session的地方,默认是一个MemoryStore,但是在生产环境下不建议使用MemoryStore,同时store有很多自定义的方法,如这里就为他添加了generate,connect,disconnect,当然也包含destroy方法。如果你对store感兴趣,可以看看下面这个通用的store具有的所有的方法:

'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; 
};

unset:对没有设置的req.session进行控制,通过delete或者设置为null。默认是keep,destory表示当回应结束后会销毁session,keep表示session会被保存。但是在请求中对session的修改会被忽略,也不会保存

//如果用户指定了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'; 
  // determine if session should be destroyed 
  //sessionID还存在,但是req.session已经被销毁了 
  function shouldDestroy(req) { 
   // var unsetDestroy = options.unset === 'destroy'; 
   return req.sessionID && unsetDestroy && req.session == null; 
  }

我们可以看到unset只能是默认的destroy或者keep,其用于判断是否应该销毁session,如果指定了unset方法为destrory,那么就会销毁session,也就是把req.session设置为null

在版本1.5.0后,cookie-parser这个中间件已经不是express-session工作必须的了。这个模块可以直接对req/res中的cookie进行读写,使用cookie-parser可能导致一些问题,特别是当secret在两个模块之间存在不一致的时候。

请把secure设置为true,这是明智的。但是这需要网站的支持,因为secure需要HTTPS的协议。如果设置了secure,但是你使用HTTP访问,那么cookie不会被设置,如果Node.js运行在代理上,同时使用了secure:true那么在express中需要设置”信任代理“。

var app = express() 
app.set('trust proxy', 1) // trust first proxy  
app.use(session({ 
 secret: 'keyboard cat', 
 resave: false, 
 saveUninitialized: true, 
 cookie: { secure: true } 
}))

如果在生产环境下需要使用安全的cookit,同时在测试环境也要能够使用。那么可以使用express中的NODE_ENV参数

var app = express() 
var sess = { 
 secret: 'keyboard cat', 
 cookie: {} 
} 
if (app.get('env') === 'production') { 
 app.set('trust proxy', 1) // trust first proxy  
 sess.cookie.secure = true // serve secure cookies  
} 
app.use(session(sess))

cookie的secure属性可以设置为auto,那么会按照请求的方式来判断,如果是安全的就是secure。但是如果网站同时支持HTTP和HTTPS,这时候通过HTTPS设置的cookie

对于HTTP是不可见的。这在express的”trust proxy“(简化开发和生产环境)正确设置的情况下特别有用。默认下:cookie.maxAge为null

这意味着,浏览器关闭了这个cookie也就过期了。

req.session:

// Use the session middleware  
app.use(session({ secret: 'keyboard cat', cookie: { maxAge: 60000 }})) 
// Access the session as req.session  
app.get('/', function(req, res, next) { 
 var sess = req.session//用这个属性获取session中保存的数据,而且返回的JSON数据 
 if (sess.views) { 
  sess.views++ 
  res.setHeader('Content-Type', 'text/html') 
  res.write('<p>views: ' + sess.views + '</p>') 
  res.write('<p>expires in: ' + (sess.cookie.maxAge / 1000) + 's</p>') 
  res.end() 
 } else { 
  sess.views = 1 
  res.end('welcome to the session demo. refresh!') 
 } 
})

其中req.session是一个session对象,格式如下:

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.regenerate():

产生一个session,调用这个方法那么一个新的SID和Session实例就会被创建,同时放置在req.session中。但是第一步是销毁指定的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方法,但是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方法

session.destory():

销毁session,同时在req.session中被移除,但是在下一次请求的时候又会被创建

req.session.destroy(function(err) { 
 // cannot access session here  
})

session.reload():

重新装载session中的数据

req.session.reload(function(err) { 
 // session updated  
})

session.save():

把session中的数据重新保存到store中,用内存的内容去替换掉store中的内容。这个方法在HTTP的响应后自动被调用。如果session中的数据被改变了(这个行为可以通过中间件的很多的配置来改变),正因为如此这个方法一般不用显示调用。但是在长连接的websocket中这个方法一般需要手动调用

req.session.save(function(err) { 
 // session saved  
})

session.touch():

更新maxAge属性,一般不需要手动调用,因为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] 
   } 
  } 
 } 
} 
//重置".cookie.maxAge"防止在session仍然存活的时候cookie已经过期了 
defineMethod(Session.prototype, 'touch', function touch() { 
 return this.resetMaxAge(); 
}); 
//resetMaxAge方法,用于为cookie的maxAge指定为cookie的originalMaxAge 
defineMethod(Session.prototype, 'resetMaxAge', function resetMaxAge() { 
 this.cookie.maxAge = this.cookie.originalMaxAge; 
 return this; 
});

也就是把session的maxAge设置为构造Session对象的时候的初始值。

req.session.id:

唯一的,而且不会被改变。我们看看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] 
   } 
  } 
 } 
}

其中defineProperty方法如下:

//重写了Object对象的defineProperty,其中defineProperty用于为这个对象指定一个函数,其中第二个参数是函数的名称,第三个是函数本身 
function defineMethod(obj, name, fn) { 
 Object.defineProperty(obj, name, { 
  configurable: true, 
  enumerable: false, 
  value: fn, 
  writable: true 
 }); 
};

其中session的id值就是req.sessionID属性而且enumerable为false,所以在控制台是打印不出来的
req.session.cookie:

每一个session都有一个cookie对象,因此在每一次请求的时候你都可以改变session的cookie。如我们可以通过req.session.cookie.expires设置为false,这时候浏览器关闭cookie就不存在了

Cookie.maxAge:

req.session.cookie.maxAge返回这个cookie剩余的毫秒数,当然我们也可以通过设置expires来完成

var hour = 3600000 
 req.session.cookie.expires = new Date(Date.now() + hour) 
 req.session.cookie.maxAge = hour//和上面的expires等价

当maxAge设置为60000,也就是一分钟,这时候如果已经过去了30s,那么maxAge就会返回30000(不过要等到当前请求结束)。如果这时候我们调用req.session.touch(),那么req.session.maxAge就成了初始值了60000了

req.sessionID:

只读的属性。每一个session store必须是一个EventEmitter对象,同时要实现特定的方法。我们看看MemoryStore把:

function MemoryStore() { 
 Store.call(this) 
 this.sessions = Object.create(null) 
} 
//继承了Store中的所有的原型属性 
util.inherits(MemoryStore, Store)

也就是说MemoryStore继承了通用的Store的所有的属性和方法,如regenerate,load,createSession,当然也实现了很多自己的方法如all,clear,destroy,get,length,set,touch等

下面讨论的是一些其他的方法:

required方法表示:在这个store上一定会调用的方法

Recommended方法表示如果有这个方法那么在这个store上就会调用。Optional方法表示不会调用,但是为了给用户一个统一的store!

store.destroy(sid, callback)

必须的方法。通过sessionID来销毁session,如果session已经被销毁,那么回调函数被调用,同时传入一个error对象

store.get(sid, callback)

必须的方法。通过sessionID从store中获取session。回调函数是callback(err,session)。如果session存在那么第二个参数就是session,否则第二个参数就是null/undefined。如果error.code==="ENOENT"那么回调为callback(null,null)

store.set(sid, session, callback)

必须的方法。如果被成功设置了那么回调为callback(error)

store.touch(sid, session, callback)

推荐的方法。通过一个指定的sid和session对象去”接触“这个session.如果接触到了那么回调为callback(error)。session store用这个方法去删除那些空闲的session。同时这个方法也会通知session store指定的session是活动态的。MemoryStore实现了这个方法:

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

store.length(callback)

可选的方法。获取store中所有的session的个数,回调函数为callback(error,length)

store.clear(callback)

可选的方法,从store中吧所有的session都删除,回调函数为callback(err)

store.all(callback)

可选的方法。以一个数组的方法获取store中的sessions。callback(error,sessions)

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 
  name:"qinliang",//name的优先级比key要高,如果同时设置了那么就是按照name来制定的 
  //没有name时候response中为:set-cookie:blog=s%3A6OJEWycwVMmTGXcZqawrW0HNLOTJkYKm.0Slax72TMfW%2B4Tiit3Ox7NAj5S6rPWvMUr6sY02l0DE; Path=/; Expires=Thu, 28 Apr 2016 10:47:13 GMT; HttpOnly 
  //当有name的时候resopnse中:set-cookie:qinliang=s%3ABDOjujVhV0DH9Atax_gl4DgZ4-1RGvjQ.OeUddoRalzB4iSmUHcE8oMziad4Ig7jUT1REzGcYcdg; Path=/; Expires=Thu, 28 Apr 2016 10:48:26 GMT; HttpOnly 
  resave:true,//没有实现touch方法,同时也设置了session的过期时间为30天 
  rolling:true,//如果设置了rolling为true,同时saveUninitialized为true,那么每一个请求都会发送没有初始化的session! 
  saveUninitialized:false,//设置为true,存储空间浪费,不允许权限管理 
  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 
  }) 
})

从源码的角度来分析配置项:

(1)这里面的secret到底有什么用呢?

我们看看这个express-session到底是如何做的?

function unsigncookie(val, secrets) { 
 for (var i = 0; i < secrets.length; i++) { 
  var result = signature.unsign(val, secrets[i]); 
  if (result !== false) { 
   return result; 
  } 
 } 
 return false; 
}

这里是通过cookie-signature进行的解密操作

// var cookieId = req.sessionID = getcookie(req, name, secrets); 
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 
 //如果从req.headers.cookie中没有读取到session ID的数据,那么就去cookie parser的req.signedCookies中读取 
 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 
 //如果req.signedCookies中也没有获取到数据那么直接从req.cookies中获取 
 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; 
}

通过这里我们很容易看到对于session ID的获取就是通过上面的secret进行签名的,如果获取到的sessionID已经被修改过,那么表示这个session已经无效了。首先是从req.headers.cookie中获取,然后从req.signedCookies中获取,最后从req.cookies中进行获取!

(2)cookie字段有什么用的?

var Session = require('./session/session') 
 , MemoryStore = require('./session/memory') 
 , Cookie = require('./session/cookie') 
 , Store = require('./session/store') 
 var cookieOptions = options.cookie || {}; 
function generateSessionId(sess) { 
 return uid(24); 
} 
 // generates the new session 
 store.generate = function(req){ 
  req.sessionID = generateId(req);//产生一个sessionID 
  req.session = new Session(req);//产生一个Session 
  req.session.cookie = new Cookie(cookieOptions);//在req.session对象的cookie域下面保存的是一个Cookie对象 
  if (cookieOptions.secure === 'auto') { 
   req.session.cookie.secure = issecure(req, trustProxy); 
  } 
 };

我们看看cookie字段在哪里被处理了:

var Cookie = module.exports = function Cookie(options) { 
 this.path = '/'; 
 this.maxAge = null; 
 this.httpOnly = true; 
 //最终的this就是这个新创建的Cookie具有这些默认的属性,同时还具有用户自己传入的options参数,如用户传入的var cookieOptions = options.cookie || {}; 
 //也就是用户传入的options.cookie属性 
 if (options) merge(this, options); 
 /*这个utils.merge的源码只有一句话: 
 exports = module.exports = function(a, b){ 
 if (a && b) { 
  for (var key in b) { 
   a[key] = b[key]; 
  } 
 } 
 return a; 
};*/ 
 this.originalMaxAge = undefined == this.originalMaxAge 
  ? this.maxAge 
  : this.originalMaxAge; 
 //默认的originalMaxAge就是this.maxAge也就是null,如果指定了originalMaxAge那么就是用户指定的值 
};

也就是说我们在session中传入的cookie参数也成为新创建的cookie的一个属性了,而且这个这个新创建的cookie被保存到req.session.cookie下。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
Javascript操作select方法大全[新增、修改、删除、选中、清空、判断存在等]
Sep 26 Javascript
一款基jquery超炫的动画导航菜单可响应单击事件
Nov 02 Javascript
纯JavaScript实现获取onclick、onchange等事件的值
Dec 29 Javascript
js实现延时加载Flash的方法
Nov 26 Javascript
Angular中$compile源码分析
Jan 28 Javascript
JS中Eval解析JSON字符串的一个小问题
Feb 21 Javascript
在Html中使用Requirejs进行模块化开发实例详解
Apr 15 Javascript
JavaScript中数组slice和splice的对比小结
Sep 22 Javascript
详解Node.js实现301、302重定向服务
Apr 07 Javascript
Vue注册组件命名时不能用大写的原因浅析
Apr 25 Javascript
JS开发 富文本编辑器TinyMCE详解
Jul 19 Javascript
vue实现在线翻译功能
Sep 27 Javascript
详解angularjs中如何实现控制器和指令之间交互
May 31 #Javascript
详解angularjs中的隔离作用域理解以及绑定策略
May 31 #Javascript
JS简单实现自定义右键菜单实例
May 31 #Javascript
页面间固定参数,通过cookie传值的实现方法
May 31 #Javascript
基于react框架使用的一些细节要点的思考
May 31 #Javascript
Angular 通过注入 $location 获取与修改当前页面URL的实例
May 31 #Javascript
使用原生js写ajax实例(推荐)
May 31 #Javascript
You might like
PHP制作图型计数器的例子
2006/10/09 PHP
基于PHP Socket配置以及实例的详细介绍
2013/06/13 PHP
php中filter函数验证、过滤用户输入的数据
2014/01/13 PHP
PHP高级编程实例:编写守护进程
2014/09/02 PHP
Codeigniter中集成smarty和adodb的方法
2016/03/04 PHP
PHP实践教程之过滤、验证、转义与密码详解
2017/07/24 PHP
PHP单文件上传原理及上传函数的封装操作示例
2019/09/02 PHP
javascript Select标记中options操作方法集合
2008/10/22 Javascript
基于jquery实现的类似百度搜索的输入框自动完成功能
2011/08/23 Javascript
jquery解决图片路径不存在执行替换路径
2013/02/06 Javascript
Javascript学习笔记之 对象篇(一) : 对象的使用和属性
2014/06/24 Javascript
JavaScript的各种常见函数定义方法
2014/09/16 Javascript
javascript实现点击按钮弹出一个可关闭层窗口同时网页背景变灰的方法
2015/05/13 Javascript
jquery+正则实现统一的表单验证
2015/09/20 Javascript
jQuery-1.9.1源码分析系列(十一)DOM操作续之克隆节点
2015/12/01 Javascript
jQuery实现区域打印功能代码详解
2016/06/17 Javascript
基于JS快速实现导航下拉菜单动画效果附源码下载
2016/10/27 Javascript
bootstrap table服务端实现分页效果
2017/08/10 Javascript
微信小程序实现日历效果
2018/12/28 Javascript
jQuery实现经典的网页3D轮播图封装功能【附源码下载】
2019/02/15 jQuery
jQuery实现移动端下拉展现新的内容回弹动画
2020/06/24 jQuery
超实用的 30 段 Python 案例
2019/10/10 Python
Python基于yaml文件配置logging日志过程解析
2020/06/23 Python
Michael Kors美国官网:美式奢侈生活风格的代表
2016/11/25 全球购物
印尼最大的网上书店:Gramedia.com
2018/09/13 全球购物
西班牙著名的珠宝首饰品牌:P D PAOLA
2018/09/15 全球购物
ASICS印度官方网站:日本专业运动品牌
2020/06/20 全球购物
幼儿教师思想汇报
2014/01/10 职场文书
青安岗事迹材料
2014/05/14 职场文书
反对四风自我剖析材料
2014/10/07 职场文书
组织生活会表态发言材料
2014/10/17 职场文书
检讨书模板大全
2015/05/07 职场文书
小学班主任心得体会
2016/01/07 职场文书
2019最新婚庆对联集锦!
2019/07/10 职场文书
一篇文章弄清楚Ajax请求的五个步骤
2022/03/17 Javascript
Python 中面向接口编程
2022/05/20 Python