koa框架會用也會寫—(koa的實現)

koa框架

如今不少項目都是基於koa框架實現的,主要是由於koa小巧輕便,採用插件式擴展,能夠根據須要的功能來選用不一樣的插件,開發起來更加的方便快捷。因此瞭解koa的實現原理是十分、十分、十分有必要的。node

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派發中間件。json

  • 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主要是由四部分組成:promise

  • 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等屬性markdown

//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的路由!app

相關文章
相關標籤/搜索