中间件使用
了解原理之前先来看一下Koa中间件怎么使用
import Koa from 'koa'
import koaBody from 'koa-body'
import checkToken from '@middleware/checkToken'
const app = new Koa()
app.use(koaBody({
multipart: true,
formidable: {
maxFileSize: 50 * 1024 * 1024
}
))
app.use(checkToken())
app.listen(port, () => {
console.log('server running')
})
通过Koa实例的use方法,把中间件函数的调用作为参数传递进去(调用的时候可以传递一些参数)。所以中间件函数的返回值是一个函数。Koa会根据use调用中间件的先后顺序执行中间件函数。接下来看一下中间件的实现。
中间件实现
export const checkToken = () => {
return async (ctx, next) => {
const { authorization } = ctx.request.headers // 在此中间件之前使用了koa-body中间件
try {
const data = await check(authorization)
ctx.userInfo = data.user_info
} catch (e) {
ctx.body = {
code: -1,
err: 'token校验失败'
}
return
}
await next()
}
}
中间件的返回函数可以接收两个参数,第一个参数是当前请求的上下文,第二个参数可以暂时理解为下一个中间件函数,自己去控制后面中间件的调用时机。可以在最后调用,也可以在调用之后再做其它逻辑处理。类似这样:
xxx // 此中间件逻辑
await next()
xxx // 此中间件逻辑
那么为什么中间函数的返回函数可以接收到这两个参数呢?接下来就需要了解Koa的中间件实现原理了。
中间件原理
先来看一下use方法大概是做了什么(与源码的变量名可能有差异,便于理解):
class Koa{
constructor (){
this.middlewares = []
}
use(middleware){
if (typeof middleware !== 'function') throw new TypeError('middleware must be a function!')
this.middlewares.push(middleware)
return this
}
}
校验代码就先忽略掉了,use主要是把中间件函数push到一个队列面,然后返回this是为了链式调用。
另外还需要了解一下Koa的执行流程,从listen开始:
const http = require('http')
listen(){
const server = http.createServer(this.callback())
return server.listen(...args)
}
这里使用了node的原生模块http
,把接收到的参数直接传递给原生模块的listen
方法。重点是传递给createServer
的this.callback
是什么。查看http
模块的api发现crerateServer接收的是一个requestListener
请求监听函数。接下来看下callback函数的实现。
callback(){
const fn = this.compose(this.middlewares) // 关键,中间的执行
// ...
const handleRequest = (req, res) => {
const ctx = this.createContext(req, res) // 通过createContext把原生的req和res生成ctx上下文对象
return this.handleRequest(ctx, fn) // 注意:这个handleRequest是类的方法,不是这个局部的方法
}
return handleRequest // 返回监听函数
}
上面出现了三个函数,compose、createContext、类上的。先来看下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!')
}
// 这个函数就是返回给handleRequest的主要中间件调用逻辑
return function (context, next) {
// ... 删掉一些边界判断,简化一下代码,更清晰
return dispatch(0)
function dispatch (i) {
let fn = middleware[i] // 从队列最开始取出一个中间件函数
try {
// 向中间件函数fn中传递两个参数,一个context,第二个是递归下一个中间件
// 也就是前面编写中间件时的两个参数,ctx和next
return Promise.resolve(fn(context, dispatch.bind(null, i + 1)))
} catch (err) {
return Promise.reject(err)
}
}
}
}
createContext
这里不关心,来看handleRequest
函数:
handleRequest(ctx, fnMiddleware){
// ...
const onerror = err => ctx.onerror(err)
const handleResponse = () => respond(ctx)
// ...
return fnMiddleware(ctx).then(handleResponse).catch(onerror) // 执行上面compose中返回的函数
}
相关的代码大致就这些,通过一个流程图来让整体调用流程更加一目了然吧。
![](https://img.haomeiwen.com/i3473978/17a2b1093d9b0455.png)
网友评论