koa框架
現在很多專案都是基於koa框架實現的,主要是因為koa小巧輕便,採用外掛式擴充套件,可以根據需要的功能來選用不同的外掛,開發起來更加的方便快捷。所以瞭解koa的實現原理是十分、十分、十分有必要的。
koa系列文章
- koa框架會用也會寫—(koa的實現)
- koa框架會用也會寫—(koa-router)
- koa框架會用也會寫—(koa-view、koa-static)
- koa框架會用也會寫—(koa-bodyparser、koa-better-body)
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可以看看另一篇文章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的中介軟體: