koa2是继express之后,node的又一个主流的web框架,相比于express,koa只保留了核心的中间件处理逻辑,去掉了路由、模版以及一些其他的功能,是一个基于node实现的web框架,特点是优雅、简介、健壮、体积小、表现力去强,它所有的功能都是通过插件的形式来实现的。
koa的原理:
koa是一个基于node实现的web框架,koa通过封装原声的node http模块,koa的context把node的request和response对象封装到单个对象中,并暴露给中间件等调用函数。其最主要的核心是中间件机制洋葱模型。
通过use()注册多个中间件放入到数组中,然后从外层开始往内层执行,遇到next()后进入到下一个中间件,当所有中间件执行完成后,开始返回,一次执行中间件中没有执行的部分,整体流程就是递归处理。
function compose(middleware) {
return () => {
return dispatch(0)
function dispatch(i) {
let fn = middleware[i]
if (!fn) {
return Promise.resolve()
}
return Promise.resolve(function next() {
return dispatch(i + 1)
})
}
}
}
核心代码就是return Promise.resolve(fn(context, dispatch.bind(null, i+1)));递归遍历,直到所有的中间件next,生成一个多层嵌套的promise函数。
koa中间件处理可以当作是洋葱模型。中间件数组中中间件的执行是通过递归的方式来执行的,调用dispatch函数,从第一个开始执行,当有next方法时,创建promise,等到下一个中间件执行结果再执行next后端的代码,。当第二个中间件也执行next方法的时候,依然会创建promise,等待下一个中间件执行结果这也就是中间件的next的执行原理,app.use()将中间件push到中间件数组中,然后在listen方法中通过调用compose方法进行集中处理。
koa的基本组成:
application.js: application负责管理中间件的处理,以及请求的处理
context.js:context维护了一个请求的上下文
request.js:requset对req做了抽象和封装
response.js: response对res做了抽象封装
Application:主要是用于维护中间件以及一些其他的环境
module.export = class Application extends Emitter {
constructor () {
super()
this.proxy = false
this.middleware = []
this.subdomainOffect = 2
this.env = process.env.NODE_ENV || 'development'
this.context = Object.create(context)
this.request = Object.create(request)
this.response = Object.create(response)
}
}
通过app.use(fn)可以将fn添加到中间件列表this.middleware中.
app.listen的方法源码:
listen () {
const server = http.createServer(this.callback)
return server.listen.apply(server, arguments)
}
首先通过this.callback方法返回一个函数作为http.createServer的回掉函数,然后进行监听,我们已经知道,http.createServer的回掉接受俩个参数,req和res,下面就是this.callback的实现:
callback () {
const fn = compose(this.middleware)
if (!this.listeners('error').length) this.on('error', this.onerror)
return (req, res) => {
res.statusCode = 404
const ctx = this.createContext(req, res)
onFinshed(res, ctx.onerror)
fn(ctx).then(() => respond(ctx)).catch(ctx.onerror)
}
}
首先将所有的中间件通过compose组合成一个函数,然后返回http.createServer所需要的回掉函数,于是我们可以看到,当服务器收到一个请求的时候会使用req和res通过this.createContext方法来创建上下文环境ctx,然后使用fn来进行中间件的逻辑处理。
context:
当请求到来的时候,会通过req和res来创建一个context(ctx),然后执行中间件。实际上,在创建context的时候,还会创建request和response。
最左边表示每个文件到处的对象
中间一列表示每个koa应用以及维护的属性
右边的俩列表示对应的每个请求维护的对象
黑色的线表示实例化
红色的线表示原型链
蓝色的线表示属性
实际上,ctx主要的功能是代理处理requset和response的功能,提供了对request和response对象的边界访问能力,源码:
delegate(proto, 'response')
.method('attchment')
.access('status')
.getter('writable')
delegate(proto, 'request')
.method('acceptsLanguages')
.access('quserystring')
.getter('ip')
这里通过delegates模块来实现属性的访问代理,简单来说,通过delegate(proto, 'response')当访问proto的代理属性的时候,实际访问的是proto.response的对象属性。
中间件的执行
我们知道所有的中间件都会经过compose的处理,返回一个新的函数,源码:
function componse(middleware) {
if (!Array.isArray(middleware)) throw new Error('middleware stack must be an array')
for(const fn of middleware) {
if (typeof fn !== 'function') throw new Error('middleware must be componsed of functions')
}
return function (context, next) {
let index = -1
return dispatch(0)
function dispatch(i) {
if (i <= index) return Promise.reject(new Error('next() called multip times'))
index = i
let fn = middleware[i]
if (i === middleware.length) fn = next
if (!fn) retrun Promise.resolve()
try {
return Promise.resolve(fn(context, function next () {
return dispatch(i + 1)
}))
}
catch(err) {
return Promise.reject(err)
}
}
}
}
koa的中间件支持普通函数,返回一个Promise函数,以及async/await函数,由于generator函数中间件在新版本中不在支持,因此不建议使用。