Koa-session源码学习——程序执行流程

最近在研究 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 的实现流程。

文章中如果有写的不恰当的地方,欢迎大家交流指正。

原文地址:https://www.cnblogs.com/Fcode-/p/13068186.html