第一步:我们看看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!其他内容分析,参见个人空间