最近在研究 koa2 ,感觉 koa-session 插件用起来特别顺手,再加上自己一直对 cookie、session 感兴趣,索性研究起了 koa-session 源码,过程有点小艰辛,不过研究过后,感觉还是收货满满,很开心。现将研究成果分享给大家,希望对大家有帮助。
首先来看一个简单的例子,实现的是当浏览器访问 localhost:3000,进行 ctx.session.views = 2,创建 session 。
示例代码:
const session = require('koa-session'); const Koa = require('koa'); const app = new Koa(); app.keys = ['some secret hurr']; //若CONFIG里,signed为true,则需要app.keys生成签名 const CONFIG = { key: 'koa:sess', //到源码阶段就会理解(session以cookie形式存储),这里的key相当于ctx.cookies.set(key,val)里的key,可以设置为任意值,默认为koa:sess maxAge: 86400000, overwrite: true, /** (boolean) can overwrite or not (default true) */ httpOnly: true, /** (boolean) httpOnly or not (default true) */ signed: true, /** (boolean) signed or not (default true) */ rolling: false, /** (boolean) Force a session identifier cookie to be set on every response. The expiration is reset to the original maxAge, resetting the expiration countdown. default is false **/ }; app.use(session(CONFIG, app)); app.use((ctx) => { ctx.session.views = 2; ctx.body =' views' +'miews'; }) app.listen(3000);
我们的目标是搞清楚 ctx.session.views = 2,这一句代码,在 koa-session 里具体经过了哪些处理流程。
OK ,下面就要正式研究 koa-session 的源码了,打开源码目录,你会发现目录结构很简单,主要的 4 个 js 文件如下:
|--index.js |-- lib |-- context.js |-- session.js |--utils.js
首先,当然是从 index.js 说起:
module.exports = function(opts, app) { }
对应示例代码里:
const session = require('koa-session');
app.use(session(CONFIG, app));
我们来看 session(CONFIG, app),也就是 module.exports 导出的模块,这里也就是 koa-session 源码的入口。
接着继续看 index.js:
opts = formatOpts(opts); //对opts进行处理,设置默认值等,该方法里的opts.store是session存储于数据库的情况。 extendContext(app.context, opts); //index.js里非常重要的一个方法,对app.context也就是ctx,进行扩展,新增了属性session
继续i ndex.js ,这段代码是 index.js 的重点, await next() ,代表程序的执行从 index.js 转移到了 我们开头的示例代码,也就是 app.use((ctx) => {}) 里的代码,执行完毕后,又回到了 finally 里的 await sess.commit() 。我们可以看到 sess.commit() 应该是一个提交操作。
return async function sessions(ctx, next) { const sess = ctx[CONTEXT_SESSION]; console.log(sess.session); if (sess.store) await sess.initFromExternal(); try { await next(); console.log(sess.session); } catch (err) { throw err; } finally { if (opts.autoCommit) { await sess.commit(); } } };
我们接着看 index.js ,结合示例代码里的 ctx.session.views = 2 语句。首先,ctx.session.views 里的 ctx.session 触发了 function extendContext(context, opts) {} 里 ,session 属性的 getter :
session: { get() { return this[CONTEXT_SESSION].get(); }, .... }
返回的是 ctx[CONTEXT_SESSION] 的 get 方法的返回值,其中 ctx[CONTEXT_SESSION] 属性,则是创建了一个新的类:
this[_CONTEXT_SESSION] = new ContextSession(this, opts); //class ContextSession 位于./lib/context.js
我们来看 class ContextSession 的 get 方法:( this.session 也就是 index.js 中 return async function sessions(ctx, next) {} 里的 sess.session )
get() { const session = this.session; // already retrieved if (session) return session; // unset if (session === false) return null; // create an empty session or init from cookie this.store ? this.create() : this.initFromCookie(); return this.session; }
我们先不考虑 this.store ,于是程序执行了 this.initFromCookie() ,并将 this.session 返回给 ctx.session , this.initFromCookie() 也就是从 cookie 中初始 session ,initFromCookie() 方法中调用了 create() 方法,创建 session,我们来看 create() 方法:
create(val, externalKey) { debug('create session with val: %j externalKey: %s', val, externalKey); if (this.store) this.externalKey = externalKey || this.opts.genid && this.opts.genid(this.ctx); this.session = new Session(this, val); }
由于我们示例代码中的 ctx.session.views 是第一次执行,所以 initFromCookie() {}方法里的 const cookie = ctx.cookies.get(opts.key, opts) ; 为空,因此 create 里的 val 参数也为空,此时 ContextSession 的get 方法返回的 this.session 在这里初始化,我们将t his.session 打印出来,如下:
Session {_sessCtx: ContextSession, _ctx: Object, isNew: true}
this.session 也就是 ctx.session.views = 2中的 “ctx.session” 的返回值,我们再来看一遍 ctx.session 属性的 get():
session: { get() { return this[CONTEXT_SESSION].get(); }, .... }
而 ctx.session.views 相当于给 ctx.session 又新增了一个 views 属性,并赋值 2 ,也就等于给 this.session 新增了 views 属性,于是 this.session 变成了:
Session {_sessCtx: ContextSession, _ctx: Object, views: 2,isNew: true}
而 this.session 也就是 index.js 中的 return async function sessions(ctx, next) {} 中的 sess.session ,也就相当于 sess.session 变为了:
Session {_sessCtx: ContextSession, _ctx: Object, views: 2,isNew: true}
这里是整个 koa-session 源码的核心部分,需要你细品。
然后,context.js 中的 async commit() {}方法里,将 this.session 保存到了 cookie 中,到此也就实现了 ctx.session.views = 2 的实现流程。
文章中如果有写的不恰当的地方,欢迎大家交流指正。