看了koa的源碼,才發現它竟如此簡潔,真正作到了小而美的框架。一個字,amazing!javascript
koa的小,在於直接使用nodejs原生的http模塊來提供web服務,很簡單;html
koa的美,在於經過中間件來實現複雜的功能(中間件之間的銜接是經過上下文變量ctx);java
原生http服務node
let http = require('http')
let server = http.createServer((req, res)=>{
res.end('hello world.')
})
server.listen(3000)
複製代碼
koagit
const Koa = require('koa')
const app = new Koa()
app.use((ctx, next)=>{
ctx.body = 'hello world.'
})
app.listen(3000)
複製代碼
在use函數中,koa收集中間件;github
在listen函數中, koa調用http.createServer來建立web server服務(在其回調中處理上下文變量ctx、處理中間件的調用);web
myKoa
├── app.js //調用lib中的application.js獲得koa服務
├── lib
│ ├── application.js //定義Application類,建立web服務,處理中間件
│ ├── context.js //上下文對象,代理一些屬性,好比ctx.body就是代理的ctx.response.body
│ ├── request.js //請求對象
│ └── response.js //響應對象
└── package.json複製代碼
代碼鏈接:mykoa在github上的連接json
app.js
數組
let Koa = require('./lib/application')
let app = new Koa()
app.use(async (ctx, next) => {
console.log(1);
await next();
console.log(6);
});
app.use(async (ctx, next) => {
console.log(2);
await next();
console.log(5);
});
app.use(async (ctx, next) => {
console.log(3);
ctx.body = "hello world";
console.log(4);
});
app.listen(3000, ()=>{
console.log('listening on 3000');
})
複製代碼
當在瀏覽器上訪問localhost:3000,在server中打印輸出是:1 2 3 4 5 6瀏覽器
let http = require('http')
let EventEmitter = require('events')
let context = require('./context')
let request = require('./request')
let response = require('./response')
let Stream = require('stream')
class Application extends EventEmitter{
constructor(){
super()
this.middlewares = []
this.context = context
this.request = request
this.response = response
}ge
use(fn){//收集中間件
this.middlewares.push(fn)
}
compose(){//合成中間件,準確的說是串聯中間件
return async ctx =>{
function createNext(middleware, oldNext){
return async ()=>{
await middleware(ctx, oldNext)
}
}
let len = this.middlewares.length
let next = async ()=>{
return Promise.resolve()
}
for (let i=len-1; i>=0; i--) {
let currentMiddleware = this.middlewares[i]
next = createNext(currentMiddleware, next)
}
await next()
}
}
createContext(req, res){
const ctx = Object.create(this.context)
const request = ctx.request = Object.create(this.request)
const response = ctx.response = Object.create(this.response)
ctx.req = request.req = response.req = req
ctx.res = request.res = response.res = res
request.ctx = response.ctx = ctx
request.response = response
response.request = request
return ctx
}
handleRequest(ctx, fn){
let res = ctx.res
fn(ctx)//調用中間件
.then(()=>{
let bodyType = typeof ctx.body
if (bodyType == 'object') {
res.setHeader('Content-type', 'application/json;charset=utf8')
res.end(JSON.stringify(ctx.body))
} else if (ctx.body instanceof Stream) {
ctx.body.pipe(res)
} else if (bodyType == 'string' || Buffer.isBuffer(ctx.body)) {
res.setHeader('Content-type', 'text/html;charset=utf8')
res.end(ctx.body)
} else {
res.end('not found')
}
})
.catch(err=>{
this.emit('error', err)
res.statusCode = 500
res.end('server error')
})
}
callback(){
const fn = this.compose()//將中間件合成
return (req,res)=>{//返回web服務的回調
console.log('callback....')
const ctx = this.createContext(req, res)//獲得上下文變量ctx
return this.handleRequest(ctx, fn)//處理請求
}
}
listen(...args){
let server = http.createServer(this.callback())//建立web服務
return server.listen(...args)
}
}
module.exports = Application
複製代碼
listen和use函數沒啥好說的,關鍵仍是compse函數的理解,這纔是koa的核心
在koa源碼中使用的是koa-compose中間件來實現的,這裏參考的是別人手寫的一個compose函數,很精妙。
compose(){
return async ctx =>{
function createNext(middleware, oldNext){
return async ()=>{
await middleware(ctx, oldNext)
}
}
let len = this.middlewares.length
let next = async ()=>{
return Promise.resolve()
}
for (let i=len-1; i>=0; i--) {
let currentMiddleware = this.middlewares[i]
next = createNext(currentMiddleware, next)
}
await next()
}
}
複製代碼
compose函數的關鍵是怎麼依次調用中間件,不對,準確的說法應該是串聯中間件;
串聯好中間件後,只要在handleRequest中調用第一個中間件就能夠了。
上述代碼的關鍵是createNext函數,將下一個應該被調用的middleware包裹在async函數中;
倒着遍歷中間件數組,就能夠把下一個應該被調用的middleware放在上個調用的next中。
說的可能有點暈,我把代碼轉化爲一個調用圖,就明白了,以app.js中的中間件調用爲例:
發現async/await功能真的是超級好用啊。
koa的源碼真的很短,也有不少很好的解析,本身看一遍,寫一遍,仍是超有收穫的。