Koa2是基于node实现的web框架,特点是优雅,简洁,表达力强,自由度高。相比express相比更轻量。
koa1.0使用的是generator+co.js的执行方式,而2.0采用的是async/await,因此使用2.0的话需要运行在node 8版本及以上,如果当前的版本太低的话,可以进行版本的升级或者安装babel-cli,用其中的babel-node来运行。
四大模块分别是:
1.封装node http server,创建Koa类构造函数
2.构造request,reponse,context对象
3.中间件机制和剥洋葱模型的实现
4.错误机制和错误处理
通过koa2源码可以知道,实现koa的服务器应用和端口监听是基于node代码进行了封装。
let http = require('http'); let server = http.createServer((req, res) => { res.writeHead(200); res.end('hello world');}); server.listen(3000, () =>{ console.log('listenning on 3000'); });
将上面的node原生代码封装成koa模式:
const http=require('http'); const Koa=require('koa'); const app=new Koa(); app.listen(3000);
通过阅读源码可以知道,lib文件夹下放着四个koa2的核心文件中的context.js,request.js,reponse.js分别对应request,reponse,context三个模块的代码文件。context是ctx,相当于一个全局koa实例上下文this,连接request,reponse两个功能模块,并且暴露给koa的实例和中间件等回调函数的参数中。
request和reponse两个模块分别对node的原生request和reponse进行了封装,例如
request.js模块:
let url = require('url'); module.exports = {get query() { return url.parse(this.req.url, true).query; }};
当在koa实例中使用ctx.query时,相当于返回url.parse(this.req,true).query的值。此外,还封装了header,url,origin,path等方法,都是对原生request上用getter和setter进行了进一步的封装。
response.js模块:
module.exports = {get body() { return this._body; }, set body(data) { this._body = data; }, get status() { return this.res.statusCode; }, set status(statusCode) { if (typeof statusCode !== 'number') { throw new Error('something wrong!'); } this.res.statusCode = statusCode; }};
上面是对koa的status的读取和设置,读取的时候是基于原生的statusCode属性。
context.js模块:
上面已经大概介绍了一下context的作用,是连接request,reponse两个功能模块,及将request,response对象挂载到ctx的上面,从而可以让koa的实例使用request,response对象中的方法。
let proto = {}; function delegateSet(property, name) { proto.__defineSetter__(name, function (val) { this[property][name] = val; }); } function delegateGet(property, name) { proto.__defineGetter__(name, function () { return this[property][name]; }); } let requestSet = []; let requestGet = ['query']; let responseSet = ['body', 'status']; let responseGet = responseSet;requestSet.forEach(ele =>; { delegateSet('request', ele); }); requestGet.forEach(ele =>; { delegateGet('request', ele); }); responseSet.forEach(ele =>; { delegateSet('response', ele); }); responseGet.forEach(ele =>; { delegateGet('response', ele); }); module.exports = proto;
下面将req,res所有方法挂在到context下,修改application.js文件
let http = require('http'); let context = require('./context'); let request = require('./request'); let response = require('./response'); createContext(req, res) { let ctx = Object.create(this.context); ctx.request = Object.create(this.request); ctx.response = Object.create(this.response); ctx.req = ctx.request.req = req; ctx.res = ctx.response.res = res; return ctx; }
我们生成了createContext这个方法,首先,它通过Object.create创建ctx,并将req和res挂在到新创建的ctx上。
koa的中间件模型是一个剥洋葱模型,多个中间件通过use放在一个数组队列然后从外层开始执行,遇到next后进入队列中的下一个中间件,所有的中间件开始回帧,执行队列中之前中间件中未执行的代码部分,这就是剥洋葱模型。
例子:
let Koa = require('../src/application'); let app = new Koa(); app.use(async (ctx, next) => { console.log(1); await next(); console.log(6); }); app.use(async (ctx, next) => { console.log(2); await next();console.log(5); }); app.use(async (ctx, next) =>; { console.log(3); ctx.body = "hello world";console.log(4); }); app.listen(3000, () =>; { console.log('listenning on 3000'); });
根据上面的剥洋葱模型,以上代码的执行顺序应该是123456。 中间件模块的实现代码: compose() { return async ctx => { function createNext(middleware, oldNext) { return async () => { await middleware(ctx, oldNext); }} let len = this.middlewares.length; let next = async () => { return Promise.resolve(); }; for (let i = len - 1; i >= 0; i--) { let currentMiddleware = this.middlewares[i]; //将上一个中间件的next当做参数传给下一个中间件 next = createNext(currentMiddleware, next); } await next();};}callback() { return (req, res) => { let ctx = this.createContext(req, res); let respond = () => this.responseBody(ctx); let onerror = (err) =>; this.onerror(err, ctx); let fn = this.compose();return fn(ctx); };}
主要流程是通过use传进来的中间件是一个回调函数回调函数的参数是ctx上下文和next,next的作用是停止当前中间件,执行下一个中间件next()之前的代码,在下一个中间件运行的代码遇到next(),又会将执行权交给下下中间件。
该模块主要是在运行过程中发生错误的时候可以捕获错误和抛出异常,并将该错误信息进行反馈。
中间件错误捕获: return fn(ctx).then(respond).catch(onerror); 框架层面异常捕获: let EventEmitter = require('events'); //koa构造函数继承events模块 class Application extends EventEmitter {} let app = new Koa(); //on监听函数 app.on('error', err => { console.log('error happends: ', err.stack); });