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

夢想攻城獅發表於2018-09-26

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的實現

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和引入的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封裝

request上擴充套件url、path等屬性

//request.js

let 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;
複製程式碼

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
module.exports = proto;
複製程式碼

處理非同步和錯誤

上面的功能都是基於同步函式,但是在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還有一個重要的部分就是中介軟體,很多功能都是中介軟體實現的,後面一起學習kao的中介軟體:

相關文章