Middleware中间件
中间件是Koa一个非常重要的一个概念,利用中间件,可以很方便的处理用户的请求。
我们先看下这个洋葱模型图
看官网例子的执行结果
const Koa = require('koa')
const app = new Koa()
// x-response-time
app.use(async (ctx, next) => {
const start = Date.now()
console.log(1)
await next()
console.log(2)
const ms = Date.now() - start
ctx.set('X-Response-Time', `${ms}ms`)
})
// logger
app.use(async (ctx, next) => {
const start = Date.now()
console.log(3)
await next()
console.log(4)
const ms = Date.now() - start
console.log(`${ctx.method} ${ctx.url} - ${ms}`)
})
// response
app.use(async ctx => {
ctx.body = 'Hello World'
console.log(5)
})
app.listen(3000)
一个请求过来,这个例子的执行结果为
1
3
5
4
2
看例子和洋葱模型图还是很难理解,我们最好可以去看看源码,分析中间件执行的过程
首先打开源码,我们首先看到package.json文件的main指向lib/application.js。我们可以去看下application这个类都有哪些方法
application.js
我们先把方法收起来,看下都有哪些方法
其中我们的例子当中使用了application实例中的use方法和listen方法
use
我们把use方法展开
use (fn) {
if (typeof fn !== 'function') throw new TypeError('middleware must be a function!')
debug('use %s', fn._name || fn.name || '-')
this.middleware.push(fn)
return this
}
可以看到use方法很简单,仅仅是将中间件的函数push到middleware数组中
listen
我们再看下listen方法
listen (...args) {
debug('listen')
const server = http.createServer(this.callback())
return server.listen(...args)
}
http是node的内置模块,我们知道用法为
const server = http.createServer((req, res) => {
});
所以this.callback()一定是返回一个有req和res两个参数的回调函数
我们再去看下callback方法和handleRequest方法
callback () {
const fn = compose(this.middleware)
if (!this.listenerCount('error')) this.on('error', this.onerror)
const handleRequest = (req, res) => {
const ctx = this.createContext(req, res)
return this.handleRequest(ctx, fn)
}
return handleRequest
}
handleRequest (ctx, fnMiddleware) {
const res = ctx.res
res.statusCode = 404
const onerror = err => ctx.onerror(err)
const handleResponse = () => respond(ctx)
onFinished(res, onerror)
return fnMiddleware(ctx).then(handleResponse).catch(onerror)
}
首先我们看到一个compose这个方法,这个是使用了一个koa-compose类库。
我们提前说下compose的作用: compose是将多个函数合并成一个函数(eg: a() + b() +c()=> a(b(c())))
koa-compose
我们分析下koa-compose源码
function compose (middleware) {
if (!Array.isArray(middleware)) throw new TypeError('Middleware stack must be an array!')
for (const fn of middleware) {
if (typeof fn !== 'function') throw new TypeError('Middleware must be composed of functions!')
}
return function (context, next) {
// last called middleware #
let index = -1
return dispatch(0)
function dispatch (i) {
if (i <= index) return Promise.reject(new Error('next() called multiple times'))
index = i
let fn = middleware[i]
if (i === middleware.length) fn = next
if (!fn) return Promise.resolve()
try {
return Promise.resolve(fn(context, dispatch.bind(null, i + 1)));
} catch (err) {
return Promise.reject(err)
}
}
}
}
在handleRequest方法我们调用了fnMiddleware方法,即compose返回的函数 自动调用dispatch(0) 我们重点分析这行代码
return Promise.resolve(fn(context, dispatch.bind(null, i + 1)));
在第n个中间件函数里面调用next()函数时, next会指向dispatch(n + 1),, 执行dispatch(n + 1)函数会去执行n + 1个中间件函数
分析接口请求
其实next即是执行下一个中间件函数,所以我们可以去掉next函数,直接写也可以实现该功能。
const http = require('http')
const xResponseTime = async (ctx) => {
console.log(1)
await logger(ctx, response)
console.log(2)
}
const logger = async (ctx) => {
console.log(3)
await response(ctx, undefined)
console.log(4)
}
const response = async (ctx) => {
console.log(5)
}
http.createServer(async (req, res) => {
const ctx = {
req: req,
res: res,
app: {}
}
await xResponseTime(ctx, logger)
}).listen(3000)