路由配置中间件:koa-router
前言
koa-router官网
Koa
自身并没有提供关于对于不同的path、不同的method的处理中间件配置,假如我们在程序中采用一系列if判断,来识别不同的路径、不同的请求方式对应于不同的中间件函数的话,那么随着项目的迭代,产生的结果将会是毁灭性的!!
因此,需要 🈶 那么一个中间件,能够帮助我们管理好不同的路径、不同的请求方式下对应的中间件:koa-router
,通过koa-router
,可以满足这个情况的同时,还能够满足路由的叠加、嵌套,下面让我们来一一剖析一波!
如何使用
1 | const Koa = require('koa'); |
这里我们 🆕 了一个router
路由器对象,然后针对该路由器对象进行了一个配置(即路径/请求方式与中间件的映射关系,应该是将中间件fn进行存储),最后通过router.routes()
获取所有这个router
对象所配置的路由中间件!
koa-router的使用特性
👽 除了简单的请求方式与路径配置之后,koa-router
还拥有 👇 的一些特性,方便我们在日常的编码工作中更好的维护项目的路径与中间件关系(以下~
代表一个中间件):
- 允许通过restful的方式来配置路由(也就是可以使用
router.get
、router.post
、router.put
等); - 允许使用命名路由参数(这有点类似于vue中的路由参数机制):
router.get('/users/:id', ~)
1
2
3
4router.get('/:category/:title', (ctx, next) => {
console.log(ctx.params);
// => { category: 'programming', title: 'how-to-node' }
}); - 允许使用命名路由,也就是给某个路由命名:
router.get('user', '/users/:id', ~)
; - 允许配置host匹配,代表只与
request.host
匹配的中间件:new Router({ host: 'xxx.com' })
; - 允许配置405以及501响应;
- 允许配置一个路由对应多个中间件:
router.get('/users/:id/', ~, ~)
,实现某个特定路由拆分为不同的中间件来响应; - 允许配置嵌套路由(这也有点类似于vue中的路由嵌套):
1
2
3
4
5
6
7const forums = new Router();
const posts = new Router();
posts.get('/', (ctx, next) => {...});
posts.get('/:pid', (ctx, next) => {...});
forums.use('/forums/:fid/posts', posts.routes(), posts.allowedMethods());
// responds to "/forums/123/posts" and "/forums/123/posts/123"
app.use(forums.routes()); - 允许配置路由前缀:
new Router({prefix: '/user'})
,后续在该路由器下的配置,均匹配/user
前缀的路径,使得可以将业务拆分为不同的模块的路由中间件来处理;
koa-router的参数属性以及API
在使用
koa-router
的时候,有必要来了解一波关于new Router()
的相关参数以及对应的router
实例方法
参数属性:
属性 | 类型 | 默认值 | 描述 |
---|---|---|---|
methods | Array | ['HEAD','OPTIONS','GET','POST','PUT','PATCH','DELETE'] |
配置的可响应的请求方式,一般无需重新配置 |
exclusive | Boolean | false | 当有多个匹配项时,仅运行最后匹配的路由的控制器 |
host | String|Regexp | ‘’ | 当前路由器对象匹配的主机名 |
prefix | String | ‘’ | 前缀路由器路径 |
实例方法:
实例方法 | 描述 |
---|---|
restful系列() | 对应于不同的请求方式的api |
use() | 中间件按照.use() 定义的顺序运行,它们被顺序地调用,请求从第一个中间件开始,并沿着中间件堆栈“向下”工作。 |
prefix() | 设置当前路由器对象的前缀 |
routes()或者middleware() | 返回当前路由器对象的已配置的路由中间件 |
allowedMethods() | 返回单独的中间件,用于响应具有包含允许的方法的“Allow”头的“OPTIONS”请求,以及根据需要使用“405 Method Not Allowed”和“501 Not Implemented”进行响应。 |
all() | 通过all(也就是所有的methods)来注册路由 |
redirect() | 以30x状态来重定向路由 |
register() | 底层公共的创建并注册一个路由中间件 |
route() | 通过给定的name来寻找一个路由中间件 |
url() | 通过给定参数来返回对应的匹配路由路径 |
match() | 匹配给定的path 并返回相应的路由 |
matchHost() | 将给定的“input”与允许的主机匹配 |
param() | 运行命名路由参数的中间件,一般用于自动加载或验证 |
部分api使用分析
router.param()
方法是Koa Router中的一个方法,用于定义全局参数处理函数,通过使用router.param()
方法,可以在路由中定义一个特殊的中间件函数,用于处理指定名称的路由参数。该参数处理函数将在匹配到含有该参数的路由路径时被调用。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22/**
* @desc: 当路由路径中含有指定名称的参数时,Koa Router会自动调用参数处理中间件函数,并将参数值、上下文对象和下一个中间件函数作为参数传递给该函数
* @name: 要处理的路由参数的名称
* @middleware: 参数处理中间件函数。它接受三个参数,分别是参数值、上下文对象(ctx)和下一个中间件函数(next)
*/
router.param(name, middleware);
// 以下是对应的使用方式
// 定义参数处理中间件函数,针对id参数进行过滤处理
router.param('id', async (id, ctx, next) => {
// 处理参数值
console.log(`参数值:${id}`);
// 将参数值赋值给上下文对象
ctx.params.id = id;
// 调用下一个中间件函数
await next();
});
// 定义路由
router.get('/users/:id', async (ctx) => {
// 在路由处理函数中使用参数值
console.log(`用户ID:${ctx.params.id}`);
ctx.body = `用户ID:${ctx.params.id}`;
});通过这种方式,可以将某个
router
对象配置一系列对某些参数的全局控制,可精确到针对某个参数发生时所对应的值的特殊判断!router.use()
router.use()方法是Koa Router中的一个方法,用于在路由中注册全局中间件函数,通过使用router.use()
方法,可以将一个中间件函数应用到每个请求的路由上,这个中间件函数会在匹配到对应路由之前执行,可以用来实现一些通用的处理逻辑,如身份验证、错误处理等。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15/**
* middleware: 要注册的中间件函数,接受三个参数,分别是上下文对象(ctx)、下一个中间件函数(next)和当前层级的路径
*/
router.use(middleware);
// 定义全局中间件函数
const logger = async (ctx, next) => {
console.log('访问路径:', ctx.path);
await next();
}
// 注册全局中间件
router.use(logger);
// 定义路由
router.get('/users', async (ctx) => {
ctx.body = '获取用户列表';
});这里
logger()
函数是一个自定义的全局中间件函数,用于打印访问路径,通过调用router.use()方法,并将logger函数作为参数传递进去,可以将这个中间件应用到每一个请求的路由上,当有请求到达时,logger函数会被先执行,打印访问路径,然后再将请求传递给下一个中间件函数或路由处理函数,使用router.use()
方法可以实现在路由级别应用全局中间件,并且能够提供一种简便的方式来应用一些通用的处理逻辑。router.allowedMethods()
用于自动生成响应头中的Allow字段,指示服务器支持的请求方法,在HTTP协议中,服务器可以通过响应头中的Allow
字段告知客户端服务器所支持的请求方法1
app.use(router.allowedMethods());
👽 在Koa应用中使用.use()方法来注册router.allowedMethods()中间件,这样,当请求到达路由时,中间件会自动检查请求的方法,并根据路由配置生成Allow字段的值,然后将其添加到响应头中
源码分析
通过源码的阅读,我们发现,其中关键核心的方法是这个
register()
,作为koa-router
底层的注册方法,其中做了什么动作呢?使得koa-router
的实例能够管理这个路由与中间件的关系?
😕 我想应该先要有一个fn的路由数组对象,用来维护已配置的中间件吧
1 | function Router(opts = {}) { |
🌠 上面这里的stack
是用于存储Layer
的数组!
👽 由于这个中间件允许接收数组类型的路径,那么,应该有针对数组类型的中间进行批量自我注册的逻辑吧
1 | if (Array.isArray(path)) { |
😕 stack
中存储的Layer
对象是什么?
1 | const route = new Layer(path, methods, middleware, { |
🌠 初步看,像是针对对应的路径+请求方法+中间件+配置=Layer
的一个对象,也就是将相关的信息存储在Layer中,也就是说中间件的响应处理动作其实是Layer对象的响应处理,router
对象的注册动作其实就是创建一系列的Layer
对象存储下来(存储于Layer对象中的stack数组), 😕 这里存储下来了之后,有什么用处呢?我们将在后面的内容中具体剖析!
👽 现在来看app.use(router.routes())
这个方法具体做了什么动作,app.use()
是使用路由中间件函数,那么这个routes
方法应该是要返回一个(ctx, next) => {}
的函数!
1 | Router.prototype.routes = Route.prototype.middleware = function(){ |
👽 那么,当响应请求的时候(也就是执行这个dispatch方法的时候),应该是需要拿当前上下文来匹配之前存储的路由Layer
,根据匹配到的结果(应该是一个中间件数组),然后批量执行这个中间件数组(使用koa-compose
),执行包装好的一个中间件执行链条
1 | // 这里mostSpecificLayer是通过路径匹配到的最佳Layer集合 |
👽 当经过koa-router
中间件的处理之后,上下文ctx
对象被添加了 👇 几个属性:
属性 | 描述 |
---|---|
captures | 一个数组,其中存储了通配符参数的值,当请求的URL与Layer对象的路径匹配时,koa-router 会从URL中提取出对应的通配符参数值,然后将这些值存储在captures数组中,因此这个captures存储了参数值,且必须严格按照顺序来存储 |
params | 包含URL参数的对象。例如,如果路由定义为/users/:id,那么在路由处理函数中可以通过ctx.params.id 访问到对应的参数值 |
routerPath | 注册的中间件路径字符串 |
routerName | 匹配到的路由名称,一般是空的 |
学到什么
经过源码的学习,可以得出一些使用的技巧,方便自己在后续的学习与工作中运用:
- 可以借助
koa-compose
,来实现自定义中间件时,以一个数组的方式来组合自己的中间件,满足一个动作由两个自定义的中间件来完成; - 在自己自定义的中间件时,采用全局注册的方式,使得在存储原有中间件的同时(采用数组堆栈的方式来存储各个中间件),然后全局注册的时候,将全局动作插入到整个堆栈之前,实现全局注册的目;
- 在现有的中间件中,通过参数匹配的方式(或者是自定义属性),拦截部分中间件,做到高度自定义的目的!!
- 正常情况下router应该是作为末尾use的中间件了,但是,如果我们有响应数据记录日志的需求的话,就可以实现将这个请求与响应结合,然后在最后追加多一个自己的中间件,将响应的数据给维护到日志中,实现请求与响应数据的记录目的!!