前置
开始之前,先来看一个例子:
const Koa = require('koa');
const app = new Koa();
app.use(async (ctx,next)=>{
console.log('fn1');
await next();
console.log('end fn1');
})
app.use(async (ctx,next)=>{
console.log('fn2')
await next()
console.log('end fn2')
}
app.use(async (ctx,next)=>{
console.log('fn3')
}
app.listen(3000);
上面的代码打印的是什么内容?
中间件
上面的代码,体现了Koa源码中最重要的部分,即中间件,每次use也就推入一个中间件
打印的顺序也反映了Koa的中间件模型,即洋葱圈模型。
-
每个中间件执行过程中遇到next便执行下一个中间件,直到执行到最后一个中间件,然后往回执行剩余代码。
-
中间件代码(递归实现)
/* 文件名:compose.js */
function compose(middlewares) { // middlewares中间件列表
return function (ctx) { // ctx: request,response组合的context
// 定义一个派发器
function dispatch(i) {
// 获取当前中间件
const fn = middlewares[i]
try {
return Promise.resolve(
// 通过 i + 1 获取下一个中间件,传递给 next 参数
fn(ctx, function next(){ // next关键
return dispatch(i+1)
})
)
} catch (err) {
// fn不存在自动捕获错误
return Promise.reject(err)
}
}
// 开始派发第一个中间件
return dispatch(0)
}
}
module.exports = compose;
context, request, response
- 基于这些为原型定义真实的ctx,req,res
/* proto.js */
const Cookies = require('cookies');
const COOKIES = Symbol('context#cookies');
// 原型基础属性定义
const request = {
get header() {
return this.req.headers;
},
set header(val) {
this.req.headers = val;
},
get headers() {
return this.req.headers;
},
set headers(val) {
this.req.headers = val;
},
get url() {
return this.req.url;
},
set url(val) {
this.req.url = val;
}
/* 剩余其他内容 */
}
// 原型基础属性定义
const response = {
get header() {
const { res } = this;
return typeof res.getHeaders === 'function'
? res.getHeaders()
: res._headers || {}; // Node < 7.7
},
get headers() {
return this.header;
},
get status() {
return this.res.statusCode;
},
get header() {
const { res } = this;
return typeof res.getHeaders === 'function'
? res.getHeaders()
: res._headers || {}; // Node < 7.7
},
get headers() {
return this.header;
},
get status() {
return this.res.statusCode;
},
get body(){
return this._body
},
set body(val){
this._body = val
}
/* 剩余其他内容 */
}
const context = {
toJSON() {
return {
request: this.request.toJSON(),
response: this.response.toJSON(),
app: this.app.toJSON(),
originalUrl: this.originalUrl,
req: '<original node req>',
res: '<original node res>',
socket: '<original node socket>'
};
},
get cookies() {
if (!this[COOKIES]) {
this[COOKIES] = new Cookies(this.req, this.res, {
keys: this.app.keys,
secure: this.request.secure
});
}
return this[COOKIES];
},
set cookies(_cookies) {
this[COOKIES] = _cookies;
}
/* 剩余其他内容 */
};
module.exports = {
request,
response,
context
}
开始构建真正的Koa构建
const http = require('http');
const compose = require("./compose.js");
const {
request,
response,
context
} = require("./proto.js")
class Application {
constructor(){
this.context = Object.create(context);
this.request = Object.create(request);
this.response = Object.create(response);
this.middlewares = [];
}
use(fn){
this.middlewares.push(fn);
}
listen(...args) {
const server = http.createServer(this.callback());
return server.listen(...args);
}
/* createServer内容 */
callback() {
const fn = compose(this.middleware);
const handleRequest = (req, res) => {
const ctx = this.createContext(req, res);
return this.handleRequest(ctx, fn); // 就是fn(ctx) 执行中间件
};
return handleRequest;
}
handleRequest(ctx, fnMiddleware) {
/* 错误处理机制。。。 */
return fnMiddleware(ctx);
}
/* 创建真实的context */
createContext(req, res) {
const context = Object.create(this.context);
const request = context.request = Object.create(this.request);
const response = context.response = Object.create(this.response);
context.app = request.app = response.app = this;
context.req = request.req = response.req = req;
context.res = request.res = response.res = res;
request.ctx = response.ctx = context;
request.response = response;
response.request = request;
context.originalUrl = request.originalUrl = req.url;
context.state = {};
return context;
}
}
module.exports = Application