• 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 的实现流程。

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

  • 相关阅读:
    React在componentDidMount里面发送请求
    React 术语词汇表
    React里受控与非受控组件
    React和Vue等框架什么时候操作DOM
    【LeetCode】79. Word Search
    【LeetCode】91. Decode Ways
    【LeetCode】80. Remove Duplicates from Sorted Array II (2 solutions)
    【LeetCode】1. Two Sum
    【LeetCode】141. Linked List Cycle (2 solutions)
    【LeetCode】120. Triangle (3 solutions)
  • 原文地址:https://www.cnblogs.com/Fcode-/p/13068186.html
Copyright © 2020-2023  润新知