上一篇讲了 Koa 路由、GET 传值和动态路由。这一篇看 中间件,以及 Koa 特有的 洋葱模型:请求一层层走进去,响应再一层层走出来。
中间件就是匹配路由之前或匹配完成之后要做的那串操作。Express 里中间件能拿到 req、res 和 next;Koa 类似,只是上下文收进 ctx,向下走要 await next()。不调用 next,就不会继续往下匹配。
一、中间件能做什么
Koa 应用里常见四类:应用级、路由级、错误处理、第三方。第三方中间件后面用到具体模块再展开。
| 类型 |
挂载方式 |
典型用途 |
| 应用级 |
app.use(async (ctx, next) => {}) |
匹配任意路由,日志、鉴权、公共数据 |
| 路由级 |
router.get(‘/’, async (ctx, next) => {}) |
同一路径上先处理再 next 到下一个处理器 |
| 错误处理 |
await next() 之后看 ctx.status |
统一 404 / 异常页 |
| 第三方 |
app.use(某模块()) |
bodyparser、static、session 等 |
二、应用级中间件
不写路径的 app.use 会匹配任意路由,而且最先执行。如果不写 await next(),匹配到这里就停,不会再往下走路由。原文把这句话写反了:写了 next 才会继续,不写才会终止。
//引入 koa模块
var Koa = require('koa');
var Router = require('koa-router');
//实例化
var app = new Koa();
var router = new Router();
//匹配任何路由,如果不写next,这个路由被匹配到了就不会继续向下匹配
app.use(async (ctx, next) => {
console.log("我是一个中间件");
// 当前路由匹配完成以后继续向下匹配
await next();
});
//配置路由
router.get('/', async (ctx) => {
ctx.body = "首页";
});
router.get('/news', async (ctx) => {
ctx.body = "新闻列页面";
});
//启动路由
app.use(router.routes());
app.use(router.allowedMethods());
app.listen(3000);
访问任意已配置路由时,控制台会先打出「我是一个中间件」,再进入对应的 router.get。
三、路由级中间件
同一个路径可以挂两条 router.get('/'):第一条打日志并 await next(),第二条才给页面写 body。第一条不写 next,就不会进入第二条。
//引入 koa模块
var Koa = require('koa');
var Router = require('koa-router');
//实例化
var app = new Koa();
var router = new Router();
//配置路由
router.get('/', async (ctx, next) => {
console.log("控制台打印");
// 当前路由匹配完成以后继续向下匹配
await next();
});
router.get('/', async (ctx) => {
ctx.body = "首页";
});
router.get('/news', async (ctx) => {
ctx.body = "新闻列页面";
});
//启动路由
app.use(router.routes());
app.use(router.allowedMethods());
app.listen(3000);
四、错误处理中间件
页面响应会带 status。Koa 默认没匹配到路由时 status 仍是 404。把判断写在 await next() 之后,等下游路由跑完再看结果:匹配到 /news 就正常显示;少写一个 s 变成 /new,就可以给前端返回「404 页面」。
//引入 koa模块
var Koa = require('koa');
var Router = require('koa-router');
//实例化
var app = new Koa();
var router = new Router();
//匹配任何路由,如果不写next,这个路由被匹配到了就不会继续向下匹配
app.use(async (ctx,next)=>{
await next();
//如果页面找不到
if(ctx.status==404){
ctx.status = 404;
ctx.body="404 页面"
}
});
//配置路由
router.get('/', async (ctx) => {
ctx.body = "首页";
});
router.get('/news', async (ctx) => {
ctx.body = "新闻列页面";
});
//启动路由
app.use(router.routes());
app.use(router.allowedMethods());
app.listen(3000);
if 能不能放到 next 之前?放到前面时,下游还没跑,status 往往仍是默认 404,会直接拦住,匹配不到后面的路由。这正是下一节洋葱模型要解释的。
五、洋葱模型:先进后出
Koa 常被称为第二代 Node Web 框架,最大特点就是中间件流程控制:先逐级处理 request,匹配完路由后再逐级处理 response。登录系统也是这个思路——先把账号密码送进后台,数据库匹配完再处理,处理完再返回前端。
//引入 koa模块
var Koa = require('koa');
var Router = require('koa-router');
//实例化
var app = new Koa();
var router = new Router();
app.use(async (ctx, next) => {
console.log('1、这是第一个中间件01');
await next();
console.log('5、匹配路由完成以后又会返回来执行中间件');
});
app.use(async (ctx, next) => {
console.log('2、这是第二个中间件02');
await next();
console.log('4、匹配路由完成以后又会返回来执行中间件');
});
router.get('/', async (ctx) => {
console.log('3、匹配到了news这个路由');
ctx.body = "首页";
});
//启动路由
app.use(router.routes());
app.use(router.allowedMethods());
app.listen(3000);
访问 localhost:3000/ 时,控制台顺序是 1 → 2 → 3 → 4 → 5。所以错误处理要把 status 判断放在 next 之后:等路由跑完(或没匹配上)再决定要不要输出 404。
一句话总结:Koa 中间件靠 await next() 往下传;不写 next 就停。洋葱模型先进后出,404 判断必须放在 next 之后。