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

高质量-Koa源码分析

时间:2023-04-04 00:31:30 Node.js

Koa源码分析一个简单的koa程序constKoa=require('koa');constapp=newKoa();//responseapp.use(ctx=>{ctx.body='HelloKoa';});app.listen(3000);源码分析-koa源码目录application.js:koa的export文件夹,也就是定义koa的地方context.js:context,也就是普通的ctx.通过继承context.js中定义的上下文,每个应用程序都会有一个上下文。同时,通过继承app中定义的context,在每个请求中都会有一个单独的ctx。说白了,就是复用。request.js:同前面context.js的描述。response.js:同前面context.js的描述。注:在koa中,nodejs原生对应req和res源码解析——application.js包的功能可以参考截图给出的注解。Application是koa定义的类。第一步——newkoa()New会执行构造函数。所以在实例化的时候,可以传入这些选项。第二步——app.use会检查这里的中间件类型,如果是旧的中间件就会进行转换,最后直接放到中间件数组中。数组中的中间件会在每个请求中被一个一个执行。第三步——当app.listenlisten时,服务器会被创建。对于每一个请求,它都会去回调,所以回调是用来处理实际请求的。一般不要改写这个回调。接下来我们看看回调是干什么的:这里主要涉及到几个点:createContext是干什么的,Compose是如何实现洋葱模型的。this.handleRequest(ctx,fn)是做什么的?这些点分为两大块,2和3放在一起。createContext有什么作用?这是三件重要的事情。每个应用程序都有其相应的上下文、请求和响应实例。每个请求都会根据这些实例创建自己的实例。这里创建了context、request、response,将node原生的res、req、this挂载到context、request、response中。还有一些其他安装座可以轻松访问,但需要前三个安装座。返回创建的上下文,作为本次请求的上下文传递给所有中间件的第一个ctx参数。我来解释一下为什么要在第二点挂载这些属性。因为所有的访问都是代理,最终访问的是req和res上的东西,context访问request和response上的东西,但是它们上面的东西也访问req和res上的东西。比如访问ctx.method时,context会去request,然后request会返回req.method。这个代理结构在后面分析其他文件的时候会提到。compose是如何实现洋葱模型的在第三步最后提到的回调中,所有的中间件都被koa-compose包封装起来,返回一个可执行的方法,在请求阶段会执行这个方法来执行各个中间件。先自己编一个吧。functioncompose(middleware){returnfunction(ctx,next){functiondispatch(i){if(i>=middleware.length){returnPromise.resolve()}让curMiddleware=middleware[i]returncurMiddleware(ctx,dispatch.bind(null,i+1))}returndispatch(0)}}functionmid1(ctx,next){console.log('mid1before')next()console.log('mid1after')}functionmid2(ctx,next){console.log('mid2before')next()console.log('mid2after')}functionmid3(ctx,next){console.log('mid3before')next()控制台。log('mid3after')}constfn=compose([mid1,mid2,mid3])fn({})------------------------------------------------------------------打印结果mid1beforemid2beforemid3beforemid3aftermid2aftermid1after在compose中,中间件会根据i一个一个执行,有一个回溯过程。官方代码如下。functioncompose(middleware){if(!Array.isArray(middleware))thrownewTypeError('Middlewarestackmustbeanarray!')for(constfnofmiddleware){if(typeoffn!=='function')thrownewTypeError('中间件必须由函数组成!')}returnfunction(context,next){//最后调用的中间件#letindex=-1returndispatch(0)functiondispatch(i){if(i<=index)returnPromise.reject(newError('next()calledmultipletimes'))index=iletfn=middleware[i]if(i===middleware.length)fn=nextif(!fn)返回Promise。resolve()try{returnPromise.resolve(fn(context,dispatch.bind(null,i+1)));}}catch(err){returnPromise.reject(err)}}}}所以总结一下,洋葱模型的本质是通过递归实现的。说完compose的原理,回到第三步最后的this.handleRequest(ctx,fn);fn是compose返回的函数,它包装了中间件。下面输入handleRequest,可以看到当有request来的时候,最后会执行包裹的中间件函数,也就是这里的最后一行,中间件执行完之后,会去handleResponse处理response。在handleResponse中最后执行的是respondrespondfunctionrespond(ctx){//允许绕过koaif(false===ctx.respond)return;如果(!ctx.writable)返回;constres=ctx.res;让body=ctx.body;constcode=ctx.status;//忽略正文if(statuses.empty[code]){//剥离标题ctx.body=null;返回res.end();}if('HEAD'===ctx.method){if(!res.headersSent&&!ctx.response.has('Content-Length')){const{length}=ctx.response;if(Number.isInteger(length))ctx.length=length;}返回res.end();}//状态主体if(null==body){if(ctx.req.httpVersionMajor>=2){body=String(code);}else{body=ctx.message||字符串(代码);}if(!res.headersSent){ctx.type='text';ctx.length=Buffer.byteLength(body);}返回res.end(body);}//响应if(Buffer.isBuffer(body))returnres.end(body);if('string'==typeofbody)返回res.end(bod是);if(bodyinstanceofStream)returnbody.pipe(res);//主体:jsonbody=JSON.stringify(body);如果(!res.headersSent){ctx.length=Buffer.byteLength(body);}res.end(body);}主要是通过res.end解析挂载在ctx上的body,返回响应源码-request.jsmodule.exports={/***返回请求头。**@return{Object}*@apipublic*/getheader(){returnthis.req.headers;},/***设置请求头。**@apipublic*/setheader(val){this.req.headers=val;},..........}可见creatContext时在request上挂载req和res的原因就在这里。源代码解析-response.jsmodule.exports={/***返回请求套接字。**@return{Connection}*@apipublic*/getsocket(){returnthis.res.socket;},/***获取响应状态码。**@return{Number}*@apipublic*/getstatus(){returnthis.res.statusCode;},................................}源代码解析-context.jsconstproto=module.exports={..................getcookies(){if(!this[COOKIES]){this[COOKIES]=newCookies(this.req,this.res,{keys:this.app.keys,secure:this.request.secure});}返回这个[COOKIES];},设置cookies(_cookies){this[COOKIES]=_cookies;}......................}/***响应委托。*/delegate(proto,'response').method('attachment').method('redirect').method('remove').method('vary').method('has').method('set').method('append').method('flushHeaders').access('status').access('message').access('body').access('length').access('type').access('lastModified').access('etag').getter('headerSent').getter('writable');/***请求委托。*/delegate(proto,'request').method('acceptsLanguages').method('acceptsEncodings').method('acceptsCharsets').method('accepts').method('get').method('is').access('querystring').access('idempotent').access('socket').access('search').access('method').access('query').access('path').access('url')。access('accept').getter('origin').getter('href').getter('subdomains').getter('protocol').getter('host').getter('hostname').getter('URL').getter('header').getter('headers').getter('secure').getter('stale').getter('fresh').getter('ips').getter('IP');这里的proto是context,它自己定义了一个commonmethod,可以通过ctx.method访问,后面会用到delegate。这个函数会定义request和response的一些属性,从context转到proto,也就是context,但是在使用ctx.xxx访问的时候,实际上访问的是request和response上的属性,这也是为什么将请求和响应挂载到上下文所必需的

猜你喜欢