内容简介:Cookie 是网站为了辨别用户身份、进行 Session 跟踪而储存在用户本地终端上的数据。Cookie有如下属性:Cookie 也有一些不足:一次请求的流程大概如下:
Cookie 是网站为了辨别用户身份、进行 Session 跟踪而储存在用户本地终端上的数据。Cookie有如下属性:
- Cookie-name & Cookie-value :想要存储的键值对,比如
SessionId:xxx
。 - Expires :Cookie 存储在浏览器的最大时间,需要注意的是,这里的时间是相对于客户端时间而不是服务端时间。
- Max-age :等待 Cookie 过期的秒数。与 Expires 同时存在的时候,优先级高于 Expires。
- Domain :属性定义可访问该 Cookie 的域名,对一些大的网站,如果希望 Cookie 可以在子网站中共享,可以使用该属性。例如设置 Domain 为
.bigsite.com
,则sub1.bigsite.com
和sub2.bigsite.com
都可以访问已保存在客户端的cookie
,这时还需要将 Path 设置为/
。 - Path :可以访问 Cookie的页面的路径,缺省状态下 Path 为产生 Cookie 时的路径,此时 Cookie。 可以被该路径以及其子路径下的页面访问;可以将 Path 设置为
/
,使 Cookie 可以被网站下所有页面访问。 - Secure :Secure 只是一个标记而没有值。只有当一个请求通过 SSL 或 HTTPS 创建时,包含 Secure 选项的 Cookie 才能被发送至服务器。
- HttpOnly :只允许 Cookie 通过 Http 方式来访问,防止脚本攻击。
Cookie 也有一些不足:
- Http 请求的 Cookie 是明文传递的,所以安全性会有问题。
- Cookie 会附加在 Http 请求中,加大了请求的流量。
- Cookie 有大小限制,无法满足复杂的存储。
cookie 与 session 交互
一次请求的流程大概如下:
set-cookie set-cookie
源码笔记
express-session 包主要由index.js、cookie.js、memory.js、session.js、store.js组成。
cookie.js
// cookie构造函数,默认 path、maxAge、httpOnly 的值,如果有传入的 Options ,则覆盖默认配置 const 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.maxAgemaxAge : this.originalMaxAge; }; //封装了 cookie 的方法:set expires、get expires 、set maxAge、get maxAge、get data、serialize、toJSON Cookie.prototype = { ······ }; 复制代码
store.js
// store 对象用于顾名思义与 session 存储有关 // store 对象是一个抽象类,封装了一些抽象函数,需要子类去具体实现。 // 重新获取 store ,先销毁再获取,子类需要实现 destroy 销毁函数。 Store.prototype.regenerate = function (req, fn) { const self = this; this.destroy(req.sessionID, (err) => { self.generate(req); fn(err); }); }; // 根据 sid 加载 session Store.prototype.load = function (sid, fn) { const self = this; this.get(sid, (err, sess) => { if (err) return fn(err); if (!sess) return fn(); const req = { sessionID: sid, sessionStore: self }; fn(null, self.createSession(req, sess)); }); }; //该函数用于创建session //调用 Session() 在 request 对象上构造 session //为什么创建 session 的函数要放在 store 里? Store.prototype.createSession = function (req, sess) { let expires = sess.cookie.expires , orig = sess.cookie.originalMaxAge; sess.cookie = new Cookie(sess.cookie); if (typeof expires === 'string') sess.cookie.expires = new Date(expires); sess.cookie.originalMaxAge = orig; req.session = new Session(req, sess); return req.session; }; 复制代码
session.js
module.exports = Session; // Session构造函数,根据 request 与 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 (const prop in data) { if (!(prop in this)) { this[prop] = data[prop]; } } } } 复制代码
memory.js
module.exports = MemoryStore; // 继承了 store 的内存仓库 function MemoryStore() { Store.call(this); this.sessions = Object.create(null); } util.inherits(MemoryStore, Store); // 获取内存中的所有 session 记录 MemoryStore.prototype.all = function all(callback) { const sessionIds = Object.keys(this.sessions); const sessions = Object.create(null); for (let i = 0; i < sessionIds.length; i++) { const sessionId = sessionIds[i]; const session = getSession.call(this, sessionId); if (session) { sessions[sessionId] = session; } } callback && defer(callback, null, sessions); }; // 清空内存记录 MemoryStore.prototype.clear = function clear(callback) { this.sessions = Object.create(null); callback && defer(callback); }; // 根据 sessionId 销毁对应的 session 信息 MemoryStore.prototype.destroy = function destroy(sessionId, callback) { delete this.sessions[sessionId]; callback && defer(callback); }; // 根据 sessionId 返回 session MemoryStore.prototype.get = function get(sessionId, callback) { defer(callback, null, getSession.call(this, sessionId)); }; // 写入 session MemoryStore.prototype.set = function set(sessionId, session, callback) { this.sessions[sessionId] = JSON.stringify(session); callback && defer(callback); }; // 获取有效的 session MemoryStore.prototype.length = function length(callback) { this.all((err, sessions) => { if (err) return callback(err); callback(null, Object.keys(sessions).length); }); }; // 更新 session 的 cookie 信息 MemoryStore.prototype.touch = function touch(sessionId, session, callback) { const currentSession = getSession.call(this, sessionId); if (currentSession) { // update expiration currentSession.cookie = session.cookie; this.sessions[sessionId] = JSON.stringify(currentSession); } callback && defer(callback); }; 复制代码
index.js
// index 文件为了读起来清晰通顺,我只提取了 session 中间件的主要逻辑大部分的函数定义我都去除了,具体某个函数不了解可以自己看详细函数实现。 exports = module.exports = session; exports.Store = Store; exports.Cookie = Cookie; exports.Session = Session; exports.MemoryStore = MemoryStore; function session(options) { //根据 option 赋值 const opts = options || {}; const cookieOptions = opts.cookie || {}; const generateId = opts.genid || generateSessionId; const name = opts.name || opts.key || 'connect.sid'; const store = opts.store || new MemoryStore(); const trustProxy = opts.proxy; let resaveSession = opts.resave; const rollingSessions = Boolean(opts.rolling); let saveUninitializedSession = opts.saveUninitialized; let secret = opts.secret; // 定义 store的 generate 函数(原来 store.regenerate 的 generate()在这里定义。。为啥不在 store 文件里定义呢?) // request 对象下挂载 sessionId 与 cookie 对象 store.generate = function (req) { req.sessionID = generateId(req); req.session = new Session(req); req.session.cookie = new Cookie(cookieOptions); if (cookieOptions.secure === 'auto') { req.session.cookie.secure = issecure(req, trustProxy); } }; const storeImplementsTouch = typeof store.touch === 'function'; //注册 session store 的监听 let storeReady = true; store.on('disconnect', () => { storeReady = false; }); store.on('connect', () => { storeReady = true; }); return function session(req, res, next) { // self-awareness if (req.session) { next(); return; } // Handle connection as if there is no session if // the store has temporarily disconnected etc if (!storeReady) { debug('store is disconnected'); next(); return; } // pathname mismatch const originalPath = parseUrl.original(req).pathname; if (originalPath.indexOf(cookieOptions.path || '/') !== 0) return next(); // 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 const secrets = secret || [req.secret]; let originalHash; let originalId; let savedHash; let touched = false; // expose store req.sessionStore = store; // get the session ID from the cookie const cookieId = req.sessionID = getcookie(req, name, secrets); // 绑定监听事件,程序改写 res.header 时写入 set-cookie onHeaders(res, () => { if (!req.session) { debug('no session'); return; } if (!shouldSetCookie(req)) { return; } // only send secure cookies via https if (req.session.cookie.secure && !issecure(req, trustProxy)) { debug('not secured'); return; } if (!touched) { // 重新设置 cookie 的 maxAge req.session.touch(); touched = true; } //将 set-cookie 写入 header setcookie(res, name, req.sessionID, secrets[0], req.session.cookie.data); }); // 代理 res.end 来提交 session 到 session store // 覆写了 res.end 也解决了我最开始提出的为什么在请求的最后更新 session 的疑问。 const _end = res.end; const _write = res.write; let ended = false; res.end = function end(chunk, encoding) { if (ended) { return false; } ended = true; let ret; let sync = true; //判断是否需要销毁库存中的对应 session 信息 if (shouldDestroy(req)) { // destroy session debug('destroying'); store.destroy(req.sessionID, (err) => { if (err) { defer(next, err); } debug('destroyed'); writeend(); }); return writetop(); } // no session to save if (!req.session) { debug('no session'); return _end.call(res, chunk, encoding); } if (!touched) { // touch session req.session.touch(); touched = true; } //判断应该将 req.session 存入 store 中 if (shouldSave(req)) { req.session.save((err) => { if (err) { defer(next, err); } writeend(); }); return writetop(); } else if (storeImplementsTouch && shouldTouch(req)) { //刷新 store 内的 session 信息 debug('touching'); store.touch(req.sessionID, req.session, (err) => { if (err) { defer(next, err); } debug('touched'); writeend(); }); return writetop(); } return _end.call(res, chunk, encoding); }; // session 不存在重新获取 session if (!req.sessionID) { debug('no SID sent, generating session'); generate(); next(); return; } // 获取 store 中的 session 对象 debug('fetching %s', req.sessionID); store.get(req.sessionID, (err, sess) => { // error handling if (err) { debug('error %j', err); if (err.code !== 'ENOENT') { next(err); return; } generate(); } else if (!sess) { debug('no session found'); generate(); } else { debug('session found'); store.createSession(req, sess); originalId = req.sessionID; originalHash = hash(sess); if (!resaveSession) { savedHash = originalHash; } //重写res.session的 load() 与 save() wrapmethods(req.session); } next(); }); }; } 复制代码
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。