brunoyang/blog

koa-router

brunoyang opened this issue · 0 comments

koa-router(以下简称KRouter)是使用很广泛的koa路由中间件,它的路由风格与express一致,使用方便。现7.0版本以上已支持koa2.0,本文基于koa-router@7.0

之所以KRouter的路由风格与express一致,是因为都使用到了path-to-regexp这个模块,该模块可以很方便地将路由转换成正则,方便路由匹配。

Route的代码十分清晰,分为 methodsuseprefixroutesallowedMethodsallredirectregisterrouteurlmatchparam,现在按顺序分析。

Router

this.optsregister方法中的配置项,传给path-to-regexp,可以接收5个参数:

{
  end: 不再往下匹配路由,默认为true,
  name: KRouter实例名,
  sensitive: 大小写敏感,默认为false,
  strict: 请求的路径末尾是否必须得带上`/`,默认为false,
  prefix: 全局前缀
}

this.params中放的是路由参数中需要提前处理的参数,通过param方法置入。

this.stack中放的是Layer实例,Layer实例是通过register方法产生,具体

Router.prototype[method]

将http协议中的各个方法绑定到原型上,并对传入的参数做了下处理,Router的各http方法支持传入两或三个参数,第一个参数可以是命名路由(named route),或不传该参数。随后调用了register注册该中间件。

Router.prototype.use

KRouter也有一个use方法,用来单独对某些路由做前置操作,如可以对所有的路由都进行session校验,也可以对某一条ajax路由做cors,这都是可以的。此外,也可以用来做内嵌路由。啥是内嵌路由呢,举个🌰:

var forums = new Router();
var posts = new Router();

posts.get('/', function (ctx, next) {...});
posts.get('/:pid', function (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());

这是readme里面的一段,仔细看一下,所谓的内嵌路由就是把前面的路由当做后面路由的前缀,这样再来看代码就清楚了。

若第一个参数是路由数组的形式,将其抓换为单路由形式;若为单路由形式,就用该路由覆盖默认的(.*),表示这个中间件是针对某个路由的,否则就是对所有路由都生效。

m.router中的router是在Router.prototype.routes中被挂上的,指向KRouter,单纯的中间件是没有这个属性的。随后,用父路由和全局前缀为该路由增加前缀,并给子路由的param存入父路由的param。这样,就得到了一个『内嵌路由』。

Router.prototype.prefix

KRouter可以使用setPrefix方法为每个已存在的路由设置默认前缀,这个setPrefix方法是Layer实例的方法。Layer是什么具体在register方法里讲。

Router.prototype.routes

该方法提供给koa使用,相当于入口程序。首先,往this.match方法传入路径和方法,看是否匹配。若匹配,则把匹配的Layer实例往ctx上挂,便于下次相同路由使用;否则,return next()。一个路径可能会匹配上多个路由,如/student/11可以与/:type/:id/student/:id两条路由都匹配上,这时就会先处理/:type/:id下的中间件,然后处理/student/:id下的。layerChain就是一个按顺序存放着中间件的数组。数组长度是路由长度的两倍,第N个是对路由中参数的提取包装,第N+1个是该路由对应的中间件(N从0开始)。随后使用compose执行layerChain中的中间件。

|--------------------------------koa 中间件------------------------------------|
|---[前置中间件]---|
                 |[KRouter]=>[获取匹配到的路由参数]=>[对应的中间件]|
                                                             |---[后置中间件]---|

再重新理一下,当一个请求到达KRouter时,先分析有没有匹配的路由,在匹配到的情况下,return出一个中间件数组,这个数组是进过compose包装的,而koa本身就是使用compose来处理中间件的,也就是说,KRouter是通过这个手段,在koa的中间件数组中上动态地插入中间件。

Router.prototype.allowedMethods

当响应为404或没有响应码时,我们可以再细分一点,给客户端返回不同的状态码。当请求的方法是我们没有实现的,返回个501;若这个http方法实现了,且是个options方法,就返回支持的http方法;若这个http方法实现了,但并没有路由使用了这个方法,返回个405

Router.prototype.all

为所有方法都注册该条路由,是Router.prototype[method]的加强版。

Router.prototype.redirect

该方法接受三个参数,从哪儿来,到哪儿去,以及状态码。只不过是调用了url、all方法和koa的redirect,很容易理解。

Router.prototype.register

register应该是最重要的一个方法,该方法4个方法,路径、该路径的http方法、改路径所对应的中间件、配置项。这4个参数在经过一些处理后,传入Layer中。至于Layer怎么翻译我也母鸡啊。存贮在stack中的元素全是Layer的实例,所以所以我们再来看Layer.js。

Layer

首先检查传入的方法,有get方法的话把head方法放第一个(不知道为什么),接着检查stack(其实就是中间件列表)中的元素是否为函数,然后用path-to-regexp模块得到匹配该条路径的正则和参数map(paramNames)。

Layer.prototype.match

检测该条路径与传入的路径是否匹配。

Layer.prototype.params

对从请求路径中得到的参数名和值做处理,返回值是一个键值对,形如{type: stu, id: 1}

Layer.prototype.captures

返回捕获到的参数数组。一个🌰:

const regexp = /^\/([^\/]+?)\/([^\/]+?)(?:\/(?=$))?$/i; // 通过reg-to-regexp解析/:type/:id得到的正则
const path = '/student/1'
path.match(regexp).slice(1); // ['stu', 1]

Layer.prototype.url

可以将字符串,数字,对象转换后传入pathToRegExp.parse后得到解析后的路径,相当于是pathToRegExp.parse的加强版。

Layer.prototype.param

该方法被 Router.prototype.param暴露出来,作用是对路由中的参数做前置操作。两个参数分别是路由的『小名』和对应的处理函数。

Layer.prototype.setPrefix

用传入的字符串为该条路由增加个前缀。


每条路由对应一个Layer实例,这些Layer实例按顺序存入stack中。

Router.prototype.route

每条路由都有一个配置项name,用来给这条路由起个『小名』,可以通过这个小名找到对应的Layer实例。

Router.prototype.url

该方法用来得到解析后的url,用Router.prototype.route得到对应的Layer实例后,调用实例上的url方法得到解析后的url。

Router.prototype.match

将请求路径与stack中的路由作对比,若有匹配上的请求就放入matched.pathAndMethod

Router.prototype.param

该方法的具体逻辑在Layer.prototype.param里。