当前位置: 首页 > 后端技术 > Node.js

探索koa-session源码

时间:2023-04-03 14:11:22 Node.js

介绍既然挖了坑,就要填了,在之前的Nuxt认证文章中,介绍了koa-session的使用方法,主要是配置以及如何更改内容在会话中。我们让我们回顾一下。这是配置文件app.use(session({key:"lxg",overwrite:true,//覆盖CookiehttpOnly:true,//允许通过JS改变renew:true,//session即将结束时继续expire订单,可以保持用户登录到商店:newMongooseStore({createIndexes:"appSessions",connection:mongoose,expires:86400,//默认一天name:"AppSession"})//传入一个外部session的存储,我这里使用mongodb},app)这是登录界面,使用koa-session修改sessionstaticasynclogin(ctx){let{passwd,email}=ctx.request.body;lethasuser=awaitUserModel.findOne({email:email,passwd:md(passwd)});if(!hasuser){returnctx.error({});}else{letuserid=hasuser._id;const{session}=ctx;session.userid=userid;returnctx.success({data:{userid:userid}});}}可以看出配置后修改session非常简单,当然光知道还不够为什么,我们也知道为什么了,进入源码一探究竟koa-session的工作流程。0.两种存储方式我们在源码中可以清楚的看到整个过程分为使用和不使用外部存储(store)。当没有设置store时,所有session数据都由用户编码浏览器存储在cookie中,设置store后,数据存储在服务器的外部存储中。cookie中只存储了一个唯一的用户标识(externalKey),而koa-session只需要持有这个key就可以到外部存储中查找其中的数据。与直接使用cookies来存储数据相比,使用store来存储数据有两个好处。数据的大小没有限制。使用cookie会对cookie的大小有严格的限制。数据再多一点就不合适了。数据更安全。使用cookies时,数据只是通过简单的编码保存在cookie中,很容易反编码出真正的数据,并且保存在用户本地,很容易被其他程序窃取。实际应用中推荐使用store。当然,数据很简单,不需要保密。也可以使用cookie。1、默认参数处理了解本节需要的一些稍微高深的JS知识。如果看不懂代码,可以先了解一下这些知识点。当然,你也应该了解一点koa相关的概念。语句源码知识点getter/setterES5Object.defineProperties/Object.hasOwnPropertyObject对象方法symbolES6打开node_modules中koa-session文件夹下的index.js文件,映入眼帘的是主进程函数,接收一个app(koa实例)和opt(配置文件)作为参数。第一个被调用的函数是this,传入的参数是opt。该函数的作用是将默认配置替换为用户设置的配置。2.创建会话对象。下一个就是它了。传入的参数是实例上下文和配置参数。该函数的作用是在当前上下文未设置会话的情况下创建一个新会话。当外界第一次调用此属性时,使用getter创建一个ContextSession对象。通过属性的引用关系可以知道,我们直接使用的ctx.session其实是一个ContextSession对象3.初始化外部存储的步骤只有在使用外部存储的情况下才有可能。如果使用外部存储,则会话存储在外部存储中。在数据库、缓存甚至文件中,cookies只负责存储一个唯一的用户标识,koa-session就是利用这个标识在外部存储中查找数据。如果不使用外部存储,所有会话数据只是简单地编码存储在cookie中,这限制了存储容量并且不安全。我们看代码:这个函数的第一行是创建一个名为sess的ContextSession对象。一般来说就是判断是否有externalKey,如果没有则新建一个。这个externalKey是保存在cookie中唯一标识用户的字符串。Koa-session通过这个字符串在外部存储中找到对应的用户会话数据。重点是这句话,用hashcode保存当前seeion,最后比较hash,如果session有变化,就保存,至此初始化完成,保存session的初始状态。4.初始化cookie在主流程中,我们还没有看到如何在不使用外部存储的情况下初始化session。实际上,这种情况下的初始化发生在业务逻辑中对session进行操作之后,例如:const{session}=ctx;session.userid=用户id;会触发ctx的session属性拦截器,ctx.session其实就是sess的get方法的返回值:最后在ContextSession对象的get方法中执行session初始化操作:可以看到没有外部存储执行这个.initFromCookie()initFromCookie(){debug('从cookie初始化');constctx=this.ctx;constopts=this.opts;constcookie=ctx.cookies.get(opts.key,opts);如果(!cookie){this.create();返回;}让json;调试('解析%s',cookie);尝试{json=opts.decode(cookie);}catch(err){//向后兼容性://如果解析失败则创建一个新会话。//当`string`不是base64编码时,newBuffer(string,'base64')似乎不会崩溃。//但`JSON.parse(string)`会崩溃。debug('解码%j错误:%s',cookie,err);if(!(errinstanceofSyntaxError)){//清除这个cookie以确保下一步请求不会再抛出ctx.cookies.set(opts.key,'',opts);//ctx.onerror将取消设置所有标头,并设置err中指定的标头err.headers={'set-cookie':ctx.response.get('set-cookie'),};抛出错误;}this.create();返回;}debug('解析%j',json);如果(!this.valid(json)){这个.create();返回;}//支持在会话中间件之前访问`ctx.session`this.create(json);this.prevHash=util.hash(this.session.toJSON());}主要逻辑只是如果没有发现存在的session,则新建一个Session对象并初始化。如果是第一次访问服务器,将isNew设置为true。4.保存修改完成我们的业务逻辑后,调用sess.commit函数保存:主要是根据hash值判断session是否有变化,如果有变化,调用this.sava保存,这才是真正的async保存函数保存(更改){constopts=this.opts;constkey=opts.key;constexternalKey=this.externalKey;让json=this.session.toJSON();//为检查设置过期时间letmaxAge=opts.maxAge?选择.maxAge:ONE_DAY;if(maxAge==='session'){//如果maxAge设置为'session',则不要在json中设置_expire//同时从选项中删除maxAgeopts.maxAge=undefined;json._session=true;}else{//设置过期检查json._expire=maxAge+Date.now();json._maxAge=maxAge;}//保存到外部存储if(externalKey){debug('save%jtoexternalkey%s',json,externalKey);if(typeofmaxAge==='number'){//确保存储在cookiemaxAge+=10000后过期;}awaitthis.store.set(externalKey,json,maxAge,{改变,rolling:opts.r滚,});如果(opts.externalKey){opts.externalKey.set(this.ctx,externalKey);}else{this.ctx.cookies.set(key,externalKey,opts);}返回;}//保存到cookiedebug('save%jtocookie',json);json=opts.encode(json);调试('保存%s',json);this.ctx.cookies.set(key,json,opts);可以看到session中保存了_expire和maxAge这两个与session老化相关的字段。_expire用于判断下次访问服务器时session是否过期,_maxAge用于保存过期时间。然后通过externalKey判断是否使用外部存储,进入不同的保存流程。综上所述,借用本文使用的流程图,很好的展示了整个逻辑过程。欢迎到我的博客