koa框架会用也会写—(koa的实现)

4,128 阅读5分钟

koa框架

现在很多项目都是基于koa框架实现的,主要是因为koa小巧轻便,采用插件式扩展,可以根据需要的功能来选用不同的插件,开发起来更加的方便快捷。所以了解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

洋葱模型和中间件组合

洋葱模型

洋葱模型1 洋葱模型2

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派发中间件。

  • 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);
    })
}
  • Array.prototype.reduceRight组合中间件:
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(()=>{}))
  • Array.prototype.reduce组合中间件:
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的组成部分

koa的组成部分 koa主要是由四部分组成:

  • application:koa的主要逻辑,包含了中间件处理过程
  • context:koa关于ctx的封装
  • request:koa请求对象的封装
  • response:koa响应对象封装

koa的实现

原型继承

let proto = {
	//类似Object.defineProperty(request,'url'){get(){...}}
	get height(){
    	// this => example
    	return this.options.height
    },
	name: 'proto'
}
let example = Object.create(proto)
console.log(proto)		//{name:'proto'}	
console.log(example)		//{}
example.name = 'example'
example.options = {height:170,weight:50}
console.log(proto)      	//{name:'proto'}    
console.log(example)            //{name:'example',options:{height:170,weight:50}}
console.log(example.height) //170

/**************************************************************/

let proto = {
	name: 'proto'
}
let sub = proto
console.log(proto)		//{name:'proto'}	
console.log(sub)		//{name:'proto'}
sub.name = 'sub'
console.log(proto)      	//{name:'sub'}    
console.log(sub)           	//{name:'sub'}

request封装

request上扩展url、path等属性

//request.js

let request = {
    get url(){
        // this => ctx.request
        // ctx.request.req.url
        this.req.url;   
    }
    get path(){
        let url = require('url');
        return url.parse(this.req.url).pathname;
    }
}
module.exports = request;

response封装

request上扩展body等属性

//response.js

let response = {
    get body(){
        return this._body;
    }
    set body(val){  //设置内置的_body来存储
        this._body = val
    }
}
module.exports = response;

ctx封装

ctx属性代理了一些ctx.request、ctx.response上的属性,使得ctx.xx能够访问ctx.request.xx或ctx.response.xx

//context.js

let 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的get
defineGetter('request','path'); //ctx代理了ctx.request.path的get
defineGetter('response','body'); //ctx代理了ctx.response.body的get
defineSetter('response','body'); //ctx代理了ctx.response.body的set

//返回一个对象作为ctx的原型__proto__
module.exports = proto;

Koa类初始化

  • Koa是一个类,拥有middleware、ctx、request、response
  • Koa.prototype拥有use注册中间件
  • Koa.prototype拥有listen监听网络请求,其内部是对http模块的封装
  • Koa中handleRquest处理上下文ctx和中间件middleware
//application.js

const http = require('http');
let context = require('./context');
let request = require('./request');
let response = require('./response');
class Koa {
    constructor(){
        this.middlewares = [];
        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){
    
        // ctx.request、ctx.response属于koa的对象
        // koa插件会在ctx.request上扩展其他属性
        let ctx = Object.create(this.context);
        ctx.request = Object.create(this.request);
        ctx.response = Object.create(this.response);
        
        // http模块原生属性req
        ctx.request.req = ctx.req = req; 
        ctx.response.res = ctx.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

处理异步和错误

上面的功能都是基于同步函数,但是在node中大多数都是异步函数,所以这里面中间件的处理函数需要兼容异步函数。因为async+awit等于generator+co(koa1.0),而co中实现generator自动化是基于Promise实现的,所以这里必须函数promise化。如果不了解Promise、generator、async可以看看另一篇文章promise原理就是这么简单

//application.js

const 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还有两个重要的组成路由和中间件,下篇文章一起撸一下koa的路由!