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 複製程式碼
洋蔥模型和中介軟體組合
洋蔥模型


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主要是由四部分組成:
- 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可以看看另一篇文章 ofollow,noindex">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 複製程式碼