Koa 的中间件就是函数,可以是 async 函数,或是普通函数,通过app.use来进行调用。
// async 函数
app.use(async (ctx, next) => {
const start = Date.now();
await next();
const ms = Date.now() - start;
console.log(`${ctx.method} ${ctx.url} - ${ms}ms`);
});
// 普通函数
app.use((ctx, next) => {
const start = Date.now();
return next().then(() => {
const ms = Date.now() - start;
console.log(`${ctx.method} ${ctx.url} - ${ms}ms`);
});
});
express 中间件的写法
app.use((req,res,next) => {
console.log('Time',Dtae.now());
next(); // 传递request对象给下一个中间件
})
egg 框架的中间件和 Koa 的中间件写法是一模一样的,所以任何 Koa 的中间件都可以直接被使用。
const isJSON = require('koa-is-json');
const zlib = require('zlib');
async function gzip(ctx, next) {
await next();
// 后续中间件执行完成后将响应体转换成 gzip
let body = ctx.body;
if (!body) return;
if (isJSON(body)) body = JSON.stringify(body);
// 设置 gzip body,修正响应头
const stream = zlib.createGzip();
stream.end(body);
ctx.body = stream;
ctx.set('Content-Encoding', 'gzip');
}
中间件的执行顺序
app.use(async (ctx, next) => {
console.log(1)
await next();
console.log(1)
});
app.use(async (ctx, next) => {
console.log(2)
next();
console.log(2)
})
app.use(async (ctx, next) => {
console.log(3)
})
中间件的执行顺序并不是从头到尾,而是类似于前端的事件流。事件流是先进行事件捕获,到达目标,然后进行事件冒泡。中间件的实现过程也是一样的,先从最外面的中间件开始执行,next()
后进入下一个中间件,一路执行到最里面的中间件,然后再从最里面的中间件开始往外执行。
Koa 中间件采用的是洋葱圈模型,每次执行下一个中间件传入两个参数 ctx 和 next,参数 ctx 是由 koa 传入的封装了 request 和 response 的变量,可以通过它访问 request 和 response,next 就是进入下一个要执行的中间件。
next的作用就像是一个占位符,或者说是执行器,遇到next就会去执行下一个函数。
比方说有三个函数,你希望执行完 console.log(1),然后执行fn2(),很简单嘛,你直接在 fn1() 函数里面调用 fn2() 不就解决了。
function fn1() {
console.log(1)
fn2();
}
function fn2() {
console.log(2)
fn3();
}
function fn3() {
console.log(3)
}
fn1();
你希望执行 fn3() 函数之后,可以返回,那简单,直接执行 return 嘛
function fn1() {
console.log(1)
fn2()
console.log(1)
}
function fn2() {
console.log(2)
fn3();
console.log(2)
}
function fn3() {
console.log(3)
return;
}
fn1();
但是呢,这样的代码耦合性太高,将来代码要修改,那工作量,等待你的只有加班了。
我们希望将每个函数里面的调用其他的代码抽取出来,在外部调用。将函数包裹起来,其实也就是将函数传给另一个函数。
async function fn1(next) {
console.log(1);
await next();
console.log(1);
}
async function fn2(next) {
console.log(2);
await next();
console.log(2);
}
async function fn3() {
console.log(3);
}
let next1 = async function () {
await fn2(next2);
}
let next2 = async function() {
await fn3();
}
fn1(next1);
之前其实我们一只都在干一件事,就是触发所有函数的执行。那怎么触发所有函数的执行?假设有一个函数数组,那么遍历这个数组,依次执行,不就达到了我们的想法。
第一步:收集函数
const middlewares = [fn1, fn2, fn3];
第二步:传参
function compose(middleware, next) {
return async function() {
await middleware(next);
}
}
第三步:循环传参
定义了一个next,来保存上一个已经接受next的函数。
let next;
for (let i = middlewares.length - 1; i >= 0; i--) {
next = compose(middlewares[i], next);
}
大概类似的效果
next = async function(){
await fn1(async function() {
await fn2(async function() {
await fn3(async function(){
return Promise.resolve();
});
});
});
};
完整代码:
const middlewares = [fn1, fn2, fn3];
function compose(middleware, next) {
return async function () {
await middleware(next);
}
}
let next;
for (let i = middlewares.length - 1; i >= 0; i--) {
next = compose(middlewares[i], next);
}
next();
koa 中使用 use 来收集 fn
constructor() {
this.middlewares = [];
}
use(fn) {
this.middlewares.push(fn)
}
编写属于自己的中间件
前后端分离开发,我们常采用 JWT 来进行身份验证,其中 token 一般放在 HTTP 请求中的 Header Authorization 字段中,每次请求后端都要进行校验,Koa 通过编写中间件来实现 token 验证。
const Router = require('koa-router')
// koa-jwt 就是已经封装好的中间件
const jwt = require('koa-jwt')
const auth = jwt({secret})
// 当请求 /center 路由时,需要经过 auth 的验证,验证通过之后,才可以进行下一步处理
router.post('/center', auth, create)
module.exports = router
token.js
// token 中间件
module.exports = (options) => async (ctx, next) {
// 获取 token
const token = ctx.header.authorization
if (verify(token)) {
// 进入下一个中间件
await next()
}
}
app.js
const Koa = require('koa')
const app = new Koa()
const token = require('./token')
app.use(async (ctx, next) => {
// ctx 是封装了 request 和 response 的上下文
ctx.body = 'hello koa'
await next()
// next 是下一个中间件
})
app.use(token())
app.listen(3000)
可以使用多个中间件
const Koa = require('koa')
const app = new Koa()
const token = require('./token')
const logger = require('./log')
const time = require('./time')
app.use(async (ctx, next) => {
// ctx 是封装了 request 和 response 的上下文
ctx.body = 'hello koa'
await next()
// next 是下一个中间件
})
app.use(token())
app.use(time())
app.use(logger())
app.listen(3000)