koa这里就不做介绍了,这里结合源码来一个小例子说说它的实现。koa源码结构通过npm安装koa(v2.2.0)后,代码在lib文件夹下,包括4个文件,application.js、context.js、request.js、response.js.application.js包含应用程序的结构和启动服务器的上下文对象context.js应用程序,传递给中间件的上下文对象request.js应用程序的请求对象包含一些与请求相关的属性response.js应用程序的响应对象包含response相关的一些属性本文主要讲的是application.js。我们先来看一个最简单的例子//app.jsconstKoa=require('koa')constapp=newKoa()app.use(ctx=>{ctx.body='helloworld'})app.listen(3000)然后通过nodeapp.js启动应用,最简单的koa服务器搭建完成,浏览器访问http://localhost:3000,服务器返回一个helloworld响应体。源码分析接下来通过源码看下服务器是如何启动的。constapp=newKoa(),显然Koa是一个构造函数。module.exports=classApplicationextendsEmitter{}Application类继承了nodejs的Events类,实现事件的监听和触发。看一下构造函数的实现。构造函数(){超级();this.proxy=false;这个.middleware=[];this.subdomainOffset=2;this.env=process.env.NODE_ENV||'发展';this.context=Object.create(context);this.request=Object.create(请求);this.response=Object.create(response);构造函数定义了一些app实例属性,包括proxy、middleware、subdomainOffset、env、context、request、response等。至此我们已经生成了一个koa的app实例。接下来,是时候使用app.use(middleware)来使用中间件了。use(fn){if(typeoffn!=='function')thrownewTypeError('中间件必须是一个函数!');if(isGeneratorFunction(fn)){deprecate('对生成器的支持将在v3中删除。'+'请参阅文档以获取有关如何转换旧中间件的示例'+'https://github.com/koajs/koa/blob/master/docs/migration.md');fn=转换(fn);}debug('use%s',fn._name||fn.name||'-');这个.middleware.push(fn);归还这个;首先,它会验证传入的参数是否是一个函数。如果不是函数,会报错。那么传入的函数如果是生成器函数,就会转为async函数。使用了koa-convert模块,这是一个非常重要的模块,可以将koa1时代的很多中间件转换成koa2时代可用的中间件。请注意,对生成器的支持将在v3中删除。koa3默认不支持generator函数作为中间件。然后将传入的中间件函数压入中间件数组,并将其返回以进行链式调用。app.use()只是定义了一些要使用的中间件,放到中间件数组中,那么如何使用这些中间件呢。看看app.listen方法。listen(){debug('listen');constserver=http.createServer(this.callback());返回server.listen.apply(服务器,参数);}app.listen是node的原生listen方法的语法糖。通过app.callback方法生成http.createServer方法需要的回调函数,然后调用原生http服务器的listen方法。其实也可以发现app的listen方法接收的参数和http服务器的listen方法是一样的。然后看app的回调方法,也是最重要的方法。callback(){constfn=compose(this.middleware);if(!this.listeners('error').length)this.on('error',this.onerror);consthandleRequest=(req,res)=>{res.statusCode=404;//默认为404constctx=this.createContext(req,res);//根据node.js原生req生成一个ctx对象,res对象constonerror=err=>ctx.onerror(err);//onerror回调函数consthandleResponse=()=>respond(ctx);//处理服务器响应onFinished(res,onerror);返回fn(ctx).then(handleResponse).catch(onerror);};返回句柄请求;可以看到回调方法返回的handleRequest函数就是http.createServer方法需要的回调函数。在回调函数中,首先通过koa-compose模块将所有的中间件组合成一个中间件函数,由app.use方法调用。然后监听一个error事件,onerror是默认的错误处理函数。onerror(err){assert(errinstanceofError,`非错误抛出:${err}`);如果(404==err.status||err.expose)返回;如果(this.silent)返回;constmsg=err.stack||err.toString();控制台.错误();console.error(msg.replace(/^/gm,''));控制台.错误();}onerror函数只输出错误。堆栈为错误消息。handleRequest函数完成请求的处理和响应结果的返回。首先app.createContext方法生成一个ctx供中间件函数fn调用。createContext(req,res){constcontext=Object.create(this.context);constrequest=context.request=Object.create(this.request);//请求属性constresponse=context.response=Object.create(this.response);//响应属性context.app=request.app=response.app=this;//获取请求和响应的应用属性,指向这个应用实例context.req=request.req=response.req=req;//req属性,req为本机节点的请求对象context.res=request.res=response.res=res;//res属性,res为本机节点的响应对象request.ctx=response.ctx=context;//request和response获取ctx属性,指向上下文对象request.response=response;response.request=请求;//请求和响应指向彼此context.originalUrl=request.originalUrl=req.url;//获取originalUrl属性,即原始req对象的url属性context.cookies=newCookies(req,res,{keys:this.keys,secure:request.secure});//cookie属性request.ip=request.ips[0]||请求.socket.remot电子地址||'';//ip属性context.accept=request.accept=accepts(req);//accept属性是判断Content-Type的方法context.state={};//context.state属性,用于保存请求返回上下文中需要的其他信息;所以createContext方法在context对象上挂载了一些常用的属性,比如resquest,response,nodenativereq,res然后看这句consthandleResponse=()=>respond(ctx).functionrespond(ctx){//允许绕过koaif(false===ctx.respond)return;constres=ctx.res;如果(!ctx.writable)返回;让body=ctx.body;//响应体constcode=ctx.status;//响应状态码//忽略bodyif(statuses.empty[code]){//这里特指三种204205304//stripheadersctx.body=null;返回res.end();}if('HEAD'==ctx.method){//如果是`HEAD`方法if(!res.headersSent&&isJSON(body)){ctx.length=Buffer.byteLength(JSON.stringify(body));}返回res.end();}//statusbodyif(null==body){//如果没有设置body,将ctx.message设置为body。当然默认是NotFound,因为默认状态是404body=ctx.message||字符串(代码);如果(!res.headersSent){ctx.type='text';ctx.length=Buffer.byteLength(body);}返回res.end(body);}//下面根据body类型判断响应结果//responsesif(Buffer.isBuffer(body))returnres.end(body);if('string'==typeofbody)returnres.end(body);if(bodyinstanceofStream)returnbody.pipe(res);//主体:jsonbody=JSON.stringify(body);如果(!res.headersSent){ctx.length=Buffer.byteLength(body);}res.end(body);}所以respond函数的作用就是根据传入的ctx对象的body和method属性来决定如何处理请求,如何响应。onFinished(res,onerror)先看onFinished(res,listener)函数的介绍附上一个监听器监听finish的响应。当响应完成时,侦听器将只被调用一次。如果响应完成错误,第一个参数将包含错误。如果响应已经完成,将调用侦听器。即当服务端响应时,执行监听回调函数。如果在响应过程中发生错误,error对象会作为监听回调函数的第一个参数,所以onFinished(res,onerror)表示koa服务器发送响应后,如果有错误,执行回调函数的错误。返回fn(ctx).then(handleResponse).catch(onerror)。我们来看看这句话。前面说过,fn是所有中间件功能的“集合”,这个中间件用来表示整个处理过程。同时fn也是一个async函数,执行结果返回一个promise对象,而handleResponse是它的resolved函数,onerror是rejected函数。综上所述,application.js描述了生成一个koa服务器(实例)的整个过程。newkoa()生成一个koa实例app.use(middleware)定义了这个app要使用的中间件app.listen方法,通过回调函数将合并后的中间件函数转换为httpserver.listen调用的回调,然后调用本机server.listen方法。文末
