本文共 7998 字,大约阅读时间需要 26 分钟。
现在很多项目都是基于koa框架实现的,主要是因为koa小巧轻便,采用插件式扩展,可以根据需要的功能来选用不同的插件,开发起来更加的方便快捷。所以了解koa的实现原理是十分、十分、十分有必要的。
const Koa = require('koa');let app = new Koa();//Koa是一个类,通过new生成一个实例//koa的原型上有use方法,来注册中间件app.use((ctx,next)=>{ //koa拥有ctx属性,上面挂载了很多属性 console.log(ctx.req.path); console.log(ctx.request.req.path); console.log(ctx.request.path); console.log(ctx.path); next();//洋葱模型,中间件组合})app.listen(3000);//Koa的原型上拥有监听listen复制代码
const Koa = require('koa');const app = new Koa();app.use(async (ctx, next)=>{ console.log(1) await next(); console.log(2)});app.use(async (ctx, next) => { console.log(3) await next(); console.log(4)})app.use(async (ctx, next) => { console.log(5) awit next(); console.log(6)})//打印结果:1 3 5 6 4 2 复制代码
中间件组合
koa洋葱模型的实现,其实就是通过use将函数存放在一个middlewares队列中,然后通过函数dispatch派发中间件。
let app = { middlewares:[]; //缓存队列 use(fn){ //注册中间件 this.middlewares.push(fn); }} app.use(next=>{ console.log(1) next(); console.log(2)});app.use(next => { console.log(3) next(); console.log(4)})app.use(next => { console.log(5) next(); console.log(6)})dispatch(0)function dispatch(index){ //派发执行中间件 if(index===app.middlewares.length) retrun ; let middleware = app.middlewares[index]; middleware(()=>{ dispatch(index+1); })}复制代码
let app = { middlewares:[];//缓存队列 use(fn){//注册中间件 this.middlewares.push(fn); }} app.use(next=>{ //fn1(next) next => fn2 console.log(1) next(); console.log(2)});app.use(next => { //fn2(next) next => fn3 console.log(3) next(); console.log(4)})app.use(next => { //fn3(next) next => null; console.log(5) next(); console.log(6)})let fn= compose(app.middlewares)function conpose(middle){ return middles.reduceRight((a,b)=>{ //收敛成一个函数 return function(){ b(a); } },()=>{});}fn();//fn3(next) next:() => {};//fn2(next) next:() => fn3(()=>{})//fn1(next) next:() => fn2(()=>fn3(()=>{}))复制代码
let app = { middlewares:[];//缓存队列 use(fn){//注册中间件 this.middlewares.push(fn); }} app.use(next=>{ //fn1(next) next => fn2 console.log(1) next(); console.log(2)});app.use(next => { //fn2(next) next => fn3 console.log(3) next(); console.log(4)})app.use(next => { //fn3(next) next => null; console.log(5) next(); console.log(6)})let fn= compose(app.middlewares)function conpose(middle){ return middles.reduce((a,b)=>{ //收敛成一个函数 return (arg)=>{ a(()=>{b(arg)}) } });}fn(()=>{});复制代码
koa主要是由四部分组成:
//application.jsconst http = require('http');let context = require('./context');let request = require('./request');let response = require('./response');class Koa { constructor(){ this.middlewares = []; // 原型继承,防止引用空间的问题使后加的属性也会加在这个对象上 //this.context和引入的context不是同一个对象 this.context = Object.create(context); this.request = Object.create(request); this.response = Object.create(response); } use(fn){ this.middlewares.push(fn) ; } //挂载封装处理ctx createContext(req,res){ let ctx = this.context; ctx.request = this.request; ctx.response = this.response; ctx.req=ctx.request.req =req; ctx.res=ctx.response.res=res; return ctx; } //组合中间件 compose(ctx,middles){ function dispatch(index){ if(index === middle.length) return; let middle = middles[index]; middle(ctx,()=>dispatch(index+1)); } dispatch(0); } //网络请求监听回调 handleRequest(req,res){ let ctx = createContext(req,res); this.compose(ctx,this.middlewares); } listen(...args){ let server = http.createServer(this.handleRquest); server.listen(...args) } }module.exports = Koa复制代码
request上扩展url、path等属性
//request.jslet request = { //类似Object.defineProperty(request,'url'){get(){}} get url(){ //this.req => ctx.request.req = req,调用时ctx.request.url this.req.url; } get path(){ let url = require('url'); return url.parse(this.req.url).pathname; }}module.exports = request;复制代码
request上扩展body等属性
//response.jslet response = { get body(){ return this._body; } set body(val){ //设置内置的_body来存储 this._body=val }}module.exports = response;复制代码
ctx属性代理了一些ctx.request、ctx.response上的属性,使得ctx.xx能够访问ctx.request.xx或ctx.response.xx
//context.jslet proto = {};function defineGetter(property,key){ proto.__defineGetter(key,function(){ return this[property][key]; })}function defineSetter(property,key){ proto.__defineSetter(key,function(val){ this[property][key] = val; })}defineGetter('request','url'); //ctx代理了ctx.request.url的getdefineGetter('request','path'); //ctx代理了ctx.request.path的getdefineGetter('response','body'); //ctx代理了ctx.response.body的getdefineSetter('response','body'); //ctx代理了ctx.response.body的setmodule.exports = proto;复制代码
上面的功能都是基于同步函数,但是在node中大多数都是异步函数,所以这里面中间件的处理函数需要兼容异步函数。因为async+awit等于generator+co(koa1.0),而co中实现generator自动化是基于Promise实现的,所以这里必须函数promise化。如果不了解Promise、generator、async可以看看另一篇文章
//application.jsconst http = require('http');let context = require('./context');let request = require('./request');let response = require('./response');let Stream = require('stream');let EventEmitter = require('events');class Koa extends EventEmitter { //继承EE,处理错误 constructor(){ this.middlewares = []; this.context = Object.create(context); this.request = Object.create(request); this.response = Object.create(response); } use(fn){ this.middlewares.push(fn) ; } createContext(req,res){ let ctx = this.context; ctx.request = this.request; ctx.response = this.response; ctx.req=ctx.request.req =req; ctx.res=ctx.response.res=res; return ctx; } compose(ctx,middles){ function dispatch(index){ //没有注册中间件,返回一个promise if(index === middle.length) return Promise.resolve(); let middle = middles[index]; // Promise化,next一定为promise return Promise.resolve(middle(ctx,()=>dispatch(index+1))); } return dispatch(0); } handleRequest(req,res){ res.statusCode = 404; let ctx = createContext(req,res); //所有的中间件执行时候,可以执行内置逻辑,处理错误等 let p = this.compose(ctx,this.middlewares); p.then(()=>{ //统一处理res.body的不同情况 let body = ctx.body; if (Buffer.isBuffer(body) || typeof body === 'string'){ res.setHeader('Content-Type','text/plain;charset=utf8') res.end(body); } else if (body instanceof Stream){ body.pipe(res); }else if(typeof body == 'object'){ res.setHeader('Content-Type','application/json;charset=utf8') res.end(JSON.stringify(body)); }else{ res.end('Not Found'); } }).catch(e=>{ //处理错误 this.emit('error',e); res.statusCode = 500; //_http_server可以根据状态码找到对应的类型字段 res.end(require('_http_server').STATUS_CODES[res.statusCode]); }) } listen(...args){ let server = http.createServer(this.handleRquest); server.listen(...args) } }module.exports = Koa复制代码
koa的原理基本就介绍完了,koa还有一个重要的部分就是中间件,很多功能都是中间件实现的,后面一起学习kao的中间件: