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

【从零到一】Koa从理解到实现

时间:2023-04-03 20:31:02 Node.js

【点击查看文中相关源码】据官网介绍,Koa是一个全新的web框架,致力于成为更小、更强大的Web框架框架在Web应用程序和API开发领域更具表现力和健壮性的基石。有了异步函数,Koa不仅避免了回调地狱,还大大增强了错误处理能力。此外,一个关键的设计点是在其低级中间件层提供高级“语法糖”,其中包括内容协商、缓存清理、代理支持和重定向等常见任务的方法。Basic其实我们常见的一些web框架都是使用Http模块来创建一个服务,当请求来的时候,经过一系列的处理,将结果返回给前台。其实在Koa内部也是如此。通过查看源码不难发现,Koa主要分为四个部分:application、context、requestobject和responseobject。当我们引入Koa时,我们实际上得到了负责创建应用程序的类。让我们来看一个简单的HelloWorld应用程序:constKoa=require('koa')constapp=newKoa()app.use(asyncctx=>{ctx.body='HelloWorld'})app.listen(3000,()=>console.log('Theappisrunningonlocalhost:3000'))运行上面的代码,访问http://localhost:3000/,一个简单的应用程序就这样创建了。实现基于上面的用法,我们很容易想到如下实现:(req,res)=>{this.middleware(req,res)}returnhandleRequest}listen(...args){constserver=http.createServer(this.callback())returnserver.listen(...args)}}在上面的例子中,中间件获取的参数仍然是原来的请求和响应对象。根据Koa的实现,现在我们需要创建一个贯穿整个请求的上下文对象,包括原生的和封装好的请求响应对象。//request.jsmodule.exports={}//response.jsmodule.exports={}//context.jsmodule.exports={}//application.jsconsthttp=require('http')constrequest=require('./request')constresponse=require('./response')constcontext=require('./context')module.exports=classApplication{constructor(){//确保每个实例都有自己的请求响应上下文三objectsthis.request=Object.create(request)this.response=Object.create(response)this.context=Object.create(context)}createContext(){//...}callback(){consthandleRequest=(req,res)=>{constctx=this.createContext(req,res)this.middleware(ctx)}returnhandleRequest}}上面我们创建了三个对象放在应用实例上,最后我们将创建好的上下文对象传递给中间件。在上下文创建函数中首先要处理的是request和response等几个对象之间的关系:=(context.request=Object.create(this.request))常量响应=(context.response=Object.create(this.response))context.app=request.app=response.app=thiscontext.req=request.req=response.req=reqcontext.res=request.res=response.res=resrequest.ctx=response.ctx=contextrequest.response=responseresponse.request=requestreturncontext}}其中请求的上下文和response是我们后面进一步封装的request和response对象,而req和res是原始的request和response对象。Context如上所述,每次收到用户请求时都会创建一个Context对象。该对象封装了用户请求的信息,并提供了很多方便的方法来获取请求参数或设置响应信息。除了一些自己封装的属性和方法外,还有很多属性和方法是在请求和响应对象上通过代理获得的。constdelegate=require('delegates')constcontext=(module.exports={onerror(err){constmsg=err.stack||err.toString()console.error(msg)},})delegate(上下文,'response')//....access('body')delegate(context,'request').method('get')//....access('method')这里我们看到委托模块写了大名鼎鼎的TJ,采用委托模式,让暴露在外层的对象将请求委托给其他内部对象处理。Delegator接下来我们看一下delegates模块中的核心逻辑。functionDelegator(proto,target){if(!(thisinstanceofDelegator))returnnewDelegator(proto,target)this.proto=protothis.target=target}Delegator.prototype.method=function(name){constproto=this.protoconsttarget=this.target//调用的时候这里的this是context对象,target是request或者response//所以,会交给request对象或者response对象上的方法去处理proto[name]=function(){returnthis[target][name].apply(this[target],arguments)}returnthis}Delegator.prototype.access=function(name){返回this.getter(name).setter(name)}Delegator.prototype.getter=function(name){constproto=this.protoconsttarget=this.target//__defineGetter__方法可以设置(创建或修改)现有对象的访问器属性proto.__defineGetter__(name,function(){returnthis[target][name]})returnthis}Delegator.prototype.setter=function(name){constproto=this.protoconsttarget=this.target//__defineSetter__方法可以绑定一个函数到当前在对象的指定属性上,当该属性被赋值时,绑定的函数将被调用proto.__defineSetter__(name,function(val){return(this[target][name]=val)})returnthis}module.exports=Delegator通过method方法在context上创建一个指定的函数,调用时会对应调用onrequest对象或者response对象方法,而一些公共属性的读写直接通过__defineGetter__和__defineSetter__方法代理。RequestRequest是一个请求级对象,封装了Node.js原生的HTTPRequest对象,并提供了一系列辅助方法来获取HTTP请求公共参数。module.exports={getmethod(){//直接在nativerequest对象上获取对应的属性returnthis.req.method},setmethod(val){this.req.method=val},}类似requestcontext对象,除了会在request对象上封装一些常用的属性和方法外,还会直接读取并返回一些nativerequest对象上相应属性的值。ResponseResponse是一个请求级别的对象,它封装了Node.js原生的HTTPResponse对象,并提供了一系列辅助方法来设置HTTP响应。module.exports={getbody(){returnthis._body},setbody(val){//详细处理逻辑省略this._body=val},}处理方式与request对象类似。中间件不同于Express。Koa的中间件选择了洋葱圈模型。所有的请求在经过一个中间件的时候都会被执行两次,这样可以很方便的实现后处理逻辑。functioncompose(middlewares){returnfunction(ctx){constdispatch=(i=0)=>{constmiddleware=middlewares[i]if(i===middlewares.length){returnPromise.resolve()}returnPromise.resolve(middleware(ctx,()=>dispatch(i+1)))}returndispatch()}}module.exports=composeKoa的中间件处理单独放在koa-compose模块中,上面是插件处理的主要逻辑,核心思想是将调用下一个插件的函数通过回调传递给当前正在执行的中间件。一个问题是开发者可能会多次调用执行下一个中间件的函数(next),为此我们可以添加一个标志:functioncompose(middlewares){returnfunction(ctx){letindex=-1constdispatch=(i=0)=>{if(i<=index){返回承诺。reject(newError('next()调用了多次'))}index=iconstmiddlewares=middlewares[i]if(i===middlewares.length){returnPromise.resolve()}returnPromise.resolve(middleware(ctx,()=>dispatch(i+1)))}returndispatch()}}module.exports=compose由于in每个dispatch函数(即中间件中的next函数)中i的值是固定的,调用一次后其值等于index的值,再次调用会报错。ApplicationApplication是一个全局应用程序对象。在一个应用程序中,只会实例化一个。在上面,我们建立了几个对象之间的关系,同时也会负责整理上述的插件。另外,我们的use方法直接将指定的插件赋值给了中间件,但是这种方式只能有一个插件,所以需要改一下,维护一个数组。constcompose=require('../koa-compose')module.exports=classApplication{constructor(){//...this.middleware=[]}use(fn){this.middleware.push(fn)}callback(){constfn=compose(this.middleware)consthandleRequest=(req,res)=>{constctx=this.createContext(req,res)fn(ctx)}returnhandleRequest}}到目前为止我们基本上这个请求的处理已经完成,但是响应还没有完成,最后我们需要返回ctx.body上的数据。module.exports=classApplication{callback(){constfn=compose(this.middleware)consthandleRequest=(req,res)=>{constctx=this.createContext(req,res)this.handleRequest(ctx,fn)}returnhandleRequest}handleRequest(ctx,fnMiddleware){constonerror=err=>ctx.onerror(err)consthandleResponse=()=>respond(ctx)returnfnMiddleware(ctx).then(handleResponse).catch(onerror)}}functionrespond(ctx){ctx.res.end(ctx.body)}现在实现了一个基本的Koa。这里写的其他实现只是提供一个思路,欢迎大家一起交流学习。帕特[滑稽]。..