• koa-session源码学习——session处理流程


    上一篇文章中,我们以 ctx.session.views=2 这一行代码为线索,探讨了示例代码以及 koa-session 源码的整体执行流程,sess.session 为追踪重点,今天我们进一步完善源码中关于 session 的处理流程。

    我们接着上篇的内容向下进行,上一篇中的 this.session 打印如下:

    Session {_sessCtx: ContextSession, _ctx: Object, isNew: true, views: 2}

    我们从 Index.js 的 commit() 方法说起:

    return async function session(ctx, next) {
        const sess = ctx[CONTEXT_SESSION];
        if (sess.store) await sess.initFromExternal();
        try {
          await next();
        } catch (err) {
          throw err;
        } finally {
          if (opts.autoCommit) {
            await sess.commit();
          }
        }
      };

    sess.commit() 调用的是 ContextSession 里的 commit 方法,首先来看该方法的开头部分:

    const session = this.session;
    const opts = this.opts;
    const ctx = this.ctx;

    我们将这里的 session 打印出来,结果如下:

    Session {_sessCtx: ContextSession, _ctx: Object, isNew: true, views: 2}

    发现到这里还没有加密,我们继续向下看, _shouldSaveSession() 方法里的:

    const json = session.toJSON();

    此时打印出 json :

    Object {views: 2}

    可以看到上面的 session 只剩下了 views 属性,来看看 .JSON() 方法的代码:

    toJSON() {
        const obj = {};
    
        Object.keys(this).forEach(key => {
          if (key === 'isNew') return;
          if (key[0] === '_') return;           //key[0]代表key的第一位
          obj[key] = this[key];
        });
    
        return obj;
      }

    可以理解,是将 session 里的  isNew 属性 和 以 ‘ _ ’ 开头的属性都去掉了。

    继续看 _shouldSaveSession() 方法里的:

    const changed = prevHash !== util.hash(json);
    if (changed) return 'changed';

    prevHash 为 create() 方法里生成的,假设我们是第一次执行 ctx.session.views=2,ctx.cookies.get  为空,因此这里的 prevHash 为 undefined,我们再将 util.hash( json ) ,打印出来,查看一下:

    552152158

    你会发现原来的 json ,由 Object {views: 2} 变为了 552152158 。 因此这段代码返回的是  ‘ changed ’

    我们来看 util.hash() 方法:

    hash(sess) {
        return crc(JSON.stringify(sess));
      },

    很简单的代码!用的crc插件进行处理,其中 JSON.stringify(sess) 的作用是,将 json 从 Object {views:  2} 变为:{"views": 2},由 object 格式变为了 JSON 格式。

    最后到 save() 方法,先将待保存的 session 对象,添加了 _expire 属性和 _maxAge 属性:

    // set expire for check
    json._expire = maxAge + Date.now();
    json._maxAge = maxAge;

    我们再将 json 打印出来:

    Object {views: 2, _expire: 1592550308718, _maxAge: 86400000}

    然后对整个 json 也就是 session 进行 base64 编码加密:

    json = opts.encode(json);

    加密后的 json 再次打印出来为:

    eyJ2aWV3cyI6MiwiX2V4cGlyZSI6MTU5MjU1MDM3MjI0MiwiX21heEFnZSI6ODY0MDAwMDB9

    此时在因特网上传输起来,已经具有了一定得保密性。

    base64 编码方法如下:

    encode(body) {
      body = JSON.stringify(body);
      return Buffer.from(body).toString('base64');    //base64编码实际是对二进制数进行编码,Buffer.from()为Node.js处理二进制数据时使用。
    }

    对应的 base64 解码方法:

    decode(string) {
      let json = Buffer.from(string,'base64').toString('utf-8');
      json = JSON.parse(json);
      return json;
    }

    最后将 base64 编码的 json ,保存到 cookie 中:

    this.ctx.cookies.set(key, json, opts);

    由此我们可以看到,session 实际上是以 cookie 的形式保存的。

    最后附上 base64 加密、解密源码,感兴趣的朋友可以研究下:

    const _keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="
    
    // private method for UTF-8 encoding
    function _utf8_encode(string) {
      string = string.replace(/
    /g, "
    ");
      var utftext = "";
      for (var n = 0; n < string.length; n++) {
        var c = string.charCodeAt(n);
        if (c < 128) {
          utftext += String.fromCharCode(c);
        } else if ((c > 127) && (c < 2048)) {
          utftext += String.fromCharCode((c >> 6) | 192);
          utftext += String.fromCharCode((c & 63) | 128);
        } else {
          utftext += String.fromCharCode((c >> 12) | 224);
          utftext += String.fromCharCode(((c >> 6) & 63) | 128);
          utftext += String.fromCharCode((c & 63) | 128);
        }
    
      }
      return utftext;
    }
    
    function _utf8_decode(utftext) {
      var string = "";
      var i = 0;
      var c = 0;
      var c3 = 0;
      var c2 = 0;
      while (i < utftext.length) {
        c = utftext.charCodeAt(i);
        if (c < 128) {
          string += String.fromCharCode(c);
          i++;
        } else if ((c > 191) && (c < 224)) {
          c2 = utftext.charCodeAt(i + 1);
          string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
          i += 2;
        } else {
          c2 = utftext.charCodeAt(i + 1);
          c3 = utftext.charCodeAt(i + 2);
          string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
          i += 3;
        }
      }
      return string;
    }
    
    export default {
      // public method for encoding
      encode: function (input) {
        var output = "";
        var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
        var i = 0;
        input = _utf8_encode(input);
        while (i < input.length) {
          chr1 = input.charCodeAt(i++);
          chr2 = input.charCodeAt(i++);
          chr3 = input.charCodeAt(i++);
          enc1 = chr1 >> 2;
          enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
          enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
          enc4 = chr3 & 63;
          if (isNaN(chr2)) {
            enc3 = enc4 = 64;
          } else if (isNaN(chr3)) {
            enc4 = 64;
          }
          output = output +
            _keyStr.charAt(enc1) + _keyStr.charAt(enc2) +
            _keyStr.charAt(enc3) + _keyStr.charAt(enc4);
        }
        return output;
      },
    
      // public method for decoding
      decode: function (input) {
        var output = "";
        var chr1, chr2, chr3;
        var enc1, enc2, enc3, enc4;
        var i = 0;
        input = input.replace(/[^A-Za-z0-9+/=]/g, "");
        while (i < input.length) {
          enc1 = _keyStr.indexOf(input.charAt(i++));
          enc2 = _keyStr.indexOf(input.charAt(i++));
          enc3 = _keyStr.indexOf(input.charAt(i++));
          enc4 = _keyStr.indexOf(input.charAt(i++));
          chr1 = (enc1 << 2) | (enc2 >> 4);
          chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
          chr3 = ((enc3 & 3) << 6) | enc4;
          output = output + String.fromCharCode(chr1);
          if (enc3 != 64) {
            output = output + String.fromCharCode(chr2);
          }
          if (enc4 != 64) {
            output = output + String.fromCharCode(chr3);
          }
        }
        output = _utf8_decode(output);
        return output;
      }
    }

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

  • 相关阅读:
    ios设备new Date('2019-07-26 11:00:00')报错
    vue图片压缩(不失真)
    ios微信端网页点击右上角复制链接或在浏览器打开,得不到当前页地址(动态路由)
    ios打开网页,输入框获取焦点键盘弹起,关闭键盘,界面下方空白不回弹
    vue-cli打包后vendor.js文件太大怎么办
    ios点击有300毫秒延迟,输入框必须重压或长按才能获取焦点唤起软键盘
    ios微信端上下滑动页面,若触摸的是input区域,页面内不滚动,整个页面被拖动了
    vue-cli打包优化之分析工具webpack-bundle-analyzer
    vue引入fastclick设置输入框type="number"报错Failed to execute 'setSelectionRange' on 'HTMLInputElement': The input element's type ('number') does not support selection.的解决办法
    js/vue图片压缩
  • 原文地址:https://www.cnblogs.com/Fcode-/p/13155743.html
Copyright © 2020-2023  润新知