koa-router란
우선 Koa는 미들웨어 관리를 위한 플랫폼이기 때문에, use
를 사용하여 실행할 미들웨어를 등록합니다. use
来执行。
无论是什么请求,都会将所有的中间件执行一遍(如果没有中途结束的话)
所以,这就会让开发者很困扰,如果我们要做路由该怎么写逻辑?
app.use(ctx => { switch (ctx.url) { case '/': case '/index': ctx.body = 'index' break case 'list': ctx.body = 'list' break default: ctx.body = 'not found' } })
诚然,这样是一个简单的方法,但是必然不适用于大型项目,数十个接口通过一个switch
来控制未免太繁琐了。
更何况请求可能只支持get
或者post
,以及这种方式并不能很好的支持URL中包含参数的请求/info/:uid
。
在express
中是不会有这样的问题的,自身已经提供了get
、post
等之类的与METHOD
同名的函数用来注册回调:
express
const express = require('express') const app = express() app.get('/', function (req, res) { res.send('hi there.') })
但是koa
做了很多的精简,将很多逻辑都拆分出来作为独立的中间件来存在。
所以导致很多express
项目迁移为koa
时,需要额外的安装一些中间件,koa-router
应该说是最常用的一个。
所以在koa
中则需要额外的安装koa-router
来实现类似的路由功能:
koa
const Koa = require('koa') const Router = require('koa-router') const app = new Koa() const router = new Router() router.get('/', async ctx => { ctx.body = 'hi there.' }) app.use(router.routes()) .use(router.allowedMethods())
看起来代码确实多了一些,毕竟将很多逻辑都从框架内部转移到了中间件中来处理。
也算是为了保持一个简练的koa框架所取舍的一些东西吧。
koa-router的逻辑确实要比koa的复杂一些,可以将koa想象为一个市场,而koa-router则是其中一个摊位
koa仅需要保证市场的稳定运行,而真正和顾客打交道的确是在里边摆摊的koa-router
koa-router
的结构并不是很复杂,也就分了两个文件:
. ├── layer.js └── router.ja
layer
主要是针对一些信息的封装,主要路基由router
提供:
File | Description |
---|---|
layer |
信息存储:路径、METHOD、路径对应的正则匹配、路径中的参数、路径对应的中间件 |
router |
主要逻辑:对外暴露注册路由的函数、提供处理路由的中间件,检查请求的URL并调用对应的layer中的路由处理 |
可以拿上边所抛出的基本例子来说明koa-router
是怎样的一个执行流程:
const router = new Router() // 实例化一个Router对象 // 注册一个路由的监听 router.get('/', async ctx => { ctx.body = 'hi there.' }) app .use(router.routes()) // 将该Router对象的中间件注册到Koa实例上,后续请求的主要处理逻辑 .use(router.allowedMethods()) // 添加针对OPTIONS的响应处理,以及一些METHOD不支持的处理
首先,在koa-router
实例化的时候,是可以传递一个配置项参数作为初始化的配置信息的。
然而这个配置项在readme
中只是简单的被描述为:
Param | Type | Description |
---|---|---|
[opts] |
Object |
|
[opts.prefix] |
String 요청이 무엇이든 모든 미들웨어는 한 번만 실행됩니다(중간 끝나지 않으면) | 그러면 라우팅을 하려면 로직을 어떻게 작성해야 할까요? const Router = require('koa-router') const router = new Router({ prefix: '/my/awesome/prefix' }) router.get('/index', ctx => { ctx.body = 'pong!' }) // curl /my/awesome/prefix/index => pong! | 이것은 간단한 방법이긴 하지만 확실히 대규모 프로젝트에는 적합하지 않습니다.
function Router(opts) { if (!(this instanceof Router)) { return new Router(opts) } this.opts = opts || {} this.methods = this.opts.methods || [ 'HEAD', 'OPTIONS', 'GET', 'PUT', 'PATCH', 'POST', 'DELETE' ] this.params = {} this.stack = [] }#🎜🎜#하지만
koa
는 많은 작업을 수행했습니다. 합리화하고 많은 로직을 분할하여 독립적인 미들웨어로 존재합니다. #🎜🎜# 따라서 많은 express
프로젝트가 koa
로 마이그레이션될 때 koa-router
를 추가로 설치해야 합니다. 가장 일반적으로 사용되는 것입니다. #🎜🎜#그래서 koa
에서 유사한 라우팅 기능을 구현하려면 추가 koa-router
를 설치해야 합니다: #🎜🎜#koa# 🎜 🎜#const Router = require('koa-router') const router = new Router({ sensitive: true }) router.get('/index', ctx => { ctx.body = 'pong!' }) // curl /index => pong! // curl /Index => 404#🎜🎜#정말 코드가 많은 것 같은데, 결국 처리를 위해 프레임워크에서 미들웨어로 로직이 많이 옮겨졌습니다. #🎜🎜# 간결한 Koa 프레임워크를 유지하기 위해 선택한 몇 가지 사항이라고 볼 수 있습니다. #🎜🎜#koa-router의 논리는 실제로 koa의 논리보다 더 복잡합니다. Koa를 하나의 시장으로 상상해 보세요. koa-router는 그 중 하나입니다. #🎜🎜# koa only 시장의 안정적인 운영을 보장하는 것이 필요하며 고객과의 실제 거래는 내부에 노점을 설치하는 koa-router입니다#🎜🎜#
koa-router
의 구조는 크게 복잡하지 않아 두 개의 파일로 나누어져 있습니다: #🎜🎜#const Router = require('koa-router') const router = new Router({ strict: true }) router.get('/index', ctx => { ctx.body = 'pong!' }) // curl /index => pong! // curl /Index => pong! // curl /index/ => 404#🎜🎜#
layer
는 주로 일부 정보를 캡슐화하는 데 사용됩니다. 기본 라우터는 다음을 제공합니다: #🎜🎜#파일 | 설명 | #🎜🎜#|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Param | 유형 | 설명 | #🎜🎜#|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
[선택] #🎜🎜# |
객체 #🎜🎜##🎜🎜##🎜🎜##🎜🎜# #🎜 🎜# |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
[opts.prefix] #🎜🎜# |
문자열 #🎜🎜 ## 🎜🎜#접두사 라우터 경로(경로 접두사)#🎜🎜##🎜🎜##🎜🎜##🎜🎜#告诉我们可以添加一个 const Router = require('koa-router') const router = new Router({ prefix: '/my/awesome/prefix' }) router.get('/index', ctx => { ctx.body = 'pong!' }) // curl /my/awesome/prefix/index => pong! P.S. 不过要记住,如果 实例化 function Router(opts) { if (!(this instanceof Router)) { return new Router(opts) } this.opts = opts || {} this.methods = this.opts.methods || [ 'HEAD', 'OPTIONS', 'GET', 'PUT', 'PATCH', 'POST', 'DELETE' ] this.params = {} this.stack = [] } 可见的只有一个
sensitive如果设置了 const Router = require('koa-router') const router = new Router({ sensitive: true }) router.get('/index', ctx => { ctx.body = 'pong!' }) // curl /index => pong! // curl /Index => 404 strict
const Router = require('koa-router') const router = new Router({ strict: true }) router.get('/index', ctx => { ctx.body = 'pong!' }) // curl /index => pong! // curl /Index => pong! // curl /index/ => 404 methods
const Router = require('koa-router') const router = new Router() router.all('/ping', ctx => { ctx.body = 'pong!' }) // curl -X GET /index => pong! // curl -X POST /index => pong! 这简直是太完美了,可以很轻松的实现我们的需求,但是如果再多实验一些其他的 > curl -X DELETE /index => pong! > curl -X PUT /index => pong! 这显然不是符合我们预期的结果,所以,在这种情况下,基于目前 const Koa = require('koa') const Router = require('router') const app = new Koa() // 修改处1 const methods = ['GET', 'POST'] const router = new Router({ methods }) // 修改处2 router.all('/', async (ctx, next) => { // 理想情况下,这些判断应该交由中间件来完成 if (!~methods.indexOf(ctx.method)) { return await next() } ctx.body = 'pong!' }) 这样的两处修改,就可以实现我们所期望的功能: > curl -X GET /index => pong! > curl -X POST /index => pong! > curl -X DELETE /index => Not Implemented > curl -X PUT /index => Not Implemented 我个人觉得这是 Router.prototype.allowedMethods = function (options) { options = options || {} let implemented = this.methods return function allowedMethods(ctx, next) { return next().then(function() { let allowed = {} // 如果进行了ctx.body赋值,必然不会执行后续的逻辑 // 所以就需要我们自己在中间件中进行判断 if (!ctx.status || ctx.status === 404) { if (!~implemented.indexOf(ctx.method)) { if (options.throw) { let notImplementedThrowable if (typeof options.notImplemented === 'function') { notImplementedThrowable = options.notImplemented() // set whatever the user returns from their function } else { notImplementedThrowable = new HttpError.NotImplemented() } throw notImplementedThrowable } else { ctx.status = 501 ctx.set('Allow', allowedArr.join(', ')) } } else if (allowedArr.length) { // ... } } }) } } 首先, 当然, routerPath这个参数的存在。。感觉会导致一些很诡异的情况。 Router.prototype.routes = Router.prototype.middleware = function () { let router = this let dispatch = function dispatch(ctx, next) { let path = router.opts.routerPath || ctx.routerPath || ctx.path let matched = router.match(path, ctx.method) // 如果匹配到则执行对应的中间件 // 执行后续操作 } return dispatch } 因为我们实际上向 const router = new Router({ routerPath: '/index' }) router.all('/index', async (ctx, next) => { ctx.body = 'pong!' }) app.use(router.routes()) app.listen(8888, _ => console.log('server run as http://127.0.0.1:8888')) 如果有这样的代码,无论请求什么URL,都会认为是 > curl http://127.0.0.1:8888 pong! > curl http://127.0.0.1:8888/index pong! > curl http://127.0.0.1:8888/whatever/path pong! 巧用routerPath实现转发功能同样的,这个短路运算符一共有三个表达式,第二个的 const router = new Router() router.all('/index', async (ctx, next) => { ctx.body = 'pong!' }) app.use((ctx, next) => { ctx.routerPath = '/index' // 手动改变routerPath next() }) app.use(router.routes()) app.listen(8888, _ => console.log('server run as http://127.0.0.1:8888')) 这样的代码也能够实现相同的效果。 // 老版本的登录逻辑处理 router.post('/login', ctx => { ctx.body = 'old login logic!' }) // 新版本的登录处理逻辑 router.post('/login-v2', ctx => { ctx.body = 'new login logic!' }) app.use((ctx, next) => { if (ctx.path === '/login') { // 匹配到旧版请求,转发到新版 ctx.routerPath = '/login-v2' // 手动改变routerPath } next() }) app.use(router.routes()) 这样就实现了一个简易的转发: > curl -X POST http://127.0.0.1:8888/login new login logic! 注册路由的监听上述全部是关于实例化 Router.prototype.register = function (path, methods, middleware, opts) { opts = opts || {} let router = this let stack = this.stack // support array of paths if (Array.isArray(path)) { path.forEach(function (p) { router.register.call(router, p, methods, middleware, opts) }) return this } // create route let route = new Layer(path, methods, middleware, { end: opts.end === false ? opts.end : true, name: opts.name, sensitive: opts.sensitive || this.opts.sensitive || false, strict: opts.strict || this.opts.strict || false, prefix: opts.prefix || this.opts.prefix || '', ignoreCaptures: opts.ignoreCaptures }) if (this.opts.prefix) { route.setPrefix(this.opts.prefix) } // add parameter middleware Object.keys(this.params).forEach(function (param) { route.param(param, this.params[param]) }, this) stack.push(route) return route } 该方法在注释中标为了 private 但是其中的一些参数在代码中各种地方都没有体现出来,鬼知道为什么会留着那些参数,但既然存在,就需要了解他是干什么的
可以看到,函数大致就是实现了这样的流程:
所以在介绍这几个参数之前,简单的描述一下 function Layer(path, methods, middleware, opts) { this.opts = opts || {} this.name = this.opts.name || null this.methods = [] this.paramNames = [] this.stack = Array.isArray(middleware) ? middleware : [middleware] methods.forEach(function(method) { var l = this.methods.push(method.toUpperCase()); if (this.methods[l-1] === 'GET') { this.methods.unshift('HEAD') } }, this) // ensure middleware is a function this.stack.forEach(function(fn) { var type = (typeof fn) if (type !== 'function') { throw new Error( methods.toString() + " `" + (this.opts.name || path) +"`: `middleware` " + "must be a function, not `" + type + "`" ) } }, this) this.path = path this.regexp = pathToRegExp(path, this.paramNames, this.opts) } layer是负责存储路由监听的信息的,每次注册路由时的URL,URL生成的正则表达式,该URL中存在的参数,以及路由对应的中间件。
path在函数头部的处理逻辑,主要是为了支持多路径的同时注册,如果发现第一个 router.register(['/', ['/path1', ['/path2', 'path3']]], ['GET'], ctx => { ctx.body = 'hi there.' }) 这样完全是一个有效的设置: > curl http://127.0.0.1:8888/ hi there. > curl http://127.0.0.1:8888/path1 hi there. > curl http://127.0.0.1:8888/path3 hi there. methods而关于 middleware
P.S. 在 opts
name首先是
router.register('/test1', ['GET'], _ => {}, { name: 'module' }) router.register('/test2', ['GET'], _ => {}, { name: 'module' }) console.log(router.url('module') === '/test1') // true try { router.register('/test2', ['GET'], null, { name: 'error-module' }) } catch (e) { console.error(e) // Error: GET `error-module`: `middleware` must be a function, not `object` } 如果多个 // route用来获取命名路由 Router.prototype.route = function (name) { var routes = this.stack for (var len = routes.length, i=0; i<len; i++) { if (routes[i].name && routes[i].name === name) { return routes[i] // 匹配到第一个就直接返回了 } } return false } // url获取该路由对应的URL,并使用传入的参数来生成真实的URL Router.prototype.url = function (name, params) { var route = this.route(name) if (route) { var args = Array.prototype.slice.call(arguments, 1) return route.url.apply(route, args) } return new Error('No route found for name: ' + name) } 跑题说下router.url的那些事儿如果在项目中,想要针对某些 router.register( '/list/:id', ['GET'], ctx => { ctx.body = `Hi ${ctx.params.id}, query: ${ctx.querystring}` }, { name: 'list' } ) router.register('/', ['GET'], ctx => { // /list/1?name=Niko ctx.redirect( router.url('list', { id: 1 }, { query: { name: 'Niko' } }) ) }) // curl -L http://127.0.0.1:8888 => Hi 1, query: name=Niko 可以看到, const Layer = require('koa-router/lib/layer') const layer = new Layer('/list/:id/info/:name', [], [_ => {}]) console.log(layer.url({ id: 123, name: 'Niko' })) console.log(layer.url([123, 'Niko'])) console.log(layer.url(123, 'Niko')) console.log( layer.url(123, 'Niko', { query: { arg1: 1, arg2: 2 } }) ) 上述的调用方式都是有效的,在源码中有对应的处理,首先是针对多参数的判断,如果
这个参数替换指的是,一个 // 可以简单的认为是这样的操作: let hash = { id: 123, name: 'Niko' } '/list/:id/:name'.replace(/(?:\/:)(\w+)/g, (_, $1) => `/${hash[$1]}`) 然后 prefix上边实例化 而且如果想要路由正确的应用 // Layer实例化的操作 function Layer(path, methods, middleware, opts) { // 省略不相干操作 this.path = path this.regexp = pathToRegExp(path, this.paramNames, this.opts) } // 只有调用setPrefix才会应用前缀 Layer.prototype.setPrefix = function (prefix) { if (this.path) { this.path = prefix + this.path this.paramNames = [] this.regexp = pathToRegExp(this.path, this.paramNames, this.opts) } return this } 这个在暴露给使用者的几个方法中都有体现,类似的 router.prefix('/things/:thing_id') 但是在翻看了 router.register('/index', ['GET'], ctx => { ctx.body = 'hi there.' }) router.prefix('/path1') router.prefix('/path2') // > curl http://127.0.0.1:8888/path2/path1/index // hi there. prefix方法会叠加前缀,而不是覆盖前缀 sensitive与strict这俩参数没啥好说的,就是会覆盖实例化 end
if (end) { if (!strict) route += '(?:' + delimiter + ')?' route += endsWith === '$' ? '$' : '(?=' + endsWith + ')' } else { if (!strict) route += '(?:' + delimiter + '(?=' + endsWith + '))?' if (!isEndDelimited) route += '(?=' + delimiter + '|' + endsWith + ')' } return new RegExp('^' + route, flags(options))
router.register( '/list', ['GET'], ctx => { ctx.body = 'hi there.' }, { end: false, strict: true } ) 也就是说上述代码最后生成的用于匹配路由的正则表达式大概是这样的: /^\/list(?=\/|$)/i // 可以通过下述代码获取到正则 require('path-to-regexp').tokensToRegExp('/list/', {end: false, strict: true}) 结尾的 ignoreCaptures
router.register('/list/:id', ['GET'], ctx => { console.log(ctx.captures, ctx.params) // ['1'], { id: '1' } }) // > curl /list/1 router.register('/list/:id', ['GET'], ctx => { console.log(ctx.captures, ctx.params) // [ ], { } }, { ignoreCaptures: true }) // > curl /list/1 这个是在中间件执行期间调用了来自 // 中间件的逻辑 ctx.captures = layer.captures(path, ctx.captures) ctx.params = layer.params(path, ctx.captures, ctx.params) ctx.routerName = layer.name return next() // 中间件的逻辑 end // layer提供的方法 Layer.prototype.captures = function (path) { if (this.opts.ignoreCaptures) return [] return path.match(this.regexp).slice(1) } Layer.prototype.params = function (path, captures, existingParams) { var params = existingParams || {} for (var len = captures.length, i=0; i<len; i++) { if (this.paramNames[i]) { var c = captures[i] params[this.paramNames[i].name] = c ? safeDecodeURIComponent(c) : c } } return params } // 所做的事情大致如下: // [18, 'Niko'] + ['age', 'name'] // => // { age: 18, name: 'Niko' } router.param的作用上述是关于注册路由时的一些参数描述,可以看到在 Object.keys(this.params).forEach(function (param) { route.param(param, this.params[param]) }, this) stack.push(route) // 装载 这里是用作添加针对某个 Router.prototype.param = function (param, middleware) { this.params[param] = middleware this.stack.forEach(function (route) { route.param(param, middleware) }) return this } 两者操作类似,前者用于对新增的路由监听添加所有的
router.register('/list/:id', ['GET'], (ctx, next) => { ctx.body = `hello: ${ctx.name}` }) router.param('id', (param, ctx, next) => { console.log(`got id: ${param}`) ctx.name = 'Niko' next() }) router.param('id', (param, ctx, next) => { console.log('param2') next() }) // > curl /list/1 // got id: 1 // param2 // hello: Niko 最常用的get/post之类的快捷方式以及说完了上边的基础方法 // get|put|post|patch|delete|del // 循环注册多个METHOD的快捷方式 methods.forEach(function (method) { Router.prototype[method] = function (name, path, middleware) { let middleware if (typeof path === 'string' || path instanceof RegExp) { middleware = Array.prototype.slice.call(arguments, 2) } else { middleware = Array.prototype.slice.call(arguments, 1) path = name name = null } this.register(path, [method], middleware, { name: name }) return this } }) Router.prototype.del = Router.prototype['delete'] // 以及最后的一个别名处理,因为del并不是有效的METHOD 令人失望的是, router.use-Router内部的中间件以及上文中也提到的
普通的use这里是 Router.prototype.use = function () { var router = this middleware.forEach(function (m) { if (m.router) { // 这里是通过`router.routes()`传递进来的 m.router.stack.forEach(function (nestedLayer) { if (path) nestedLayer.setPrefix(path) if (router.opts.prefix) nestedLayer.setPrefix(router.opts.prefix) // 调用`use`的Router实例的`prefix` router.stack.push(nestedLayer) }) if (router.params) { Object.keys(router.params).forEach(function (key) { m.router.param(key, router.params[key]) }) } } else { // 普通的中间件注册 router.register(path || '(.*)', [], m, { end: false, ignoreCaptures: !hasPath }) } }) } // 在routes方法有这样的一步操作 Router.prototype.routes = Router.prototype.middleware = function () { function dispatch() { // ... } dispatch.router = this // 将router实例赋值给了返回的函数 return dispatch } 第一种是比较常规的方式,传入一个函数,一个可选的 router.use('/list', ctx => { // 如果只有这么一个中间件,无论如何也不会执行的 }) // 必须要存在相同路径的`register`回调 router.get('/list', ctx => { }) app.use(router.routes()) 原因是这样的:
Router.prototype.match = function (path, method) { var layers = this.stack var layer var matched = { path: [], pathAndMethod: [], route: false } for (var len = layers.length, i = 0; i < len; i++) { layer = layers[i] if (layer.match(path)) { matched.path.push(layer) if (layer.methods.length === 0 || ~layer.methods.indexOf(method)) { matched.pathAndMethod.push(layer) // 只有在发现不为空的`methods`以后才会设置`flag` if (layer.methods.length) matched.route = true } } } return matched } // 以及在`routes`中有这样的操作 Router.prototype.routes = Router.prototype.middleware = function () { function dispatch(ctx, next) { // 如果没有`flag`,直接跳过 if (!matched.route) return next() } return dispatch } 将其他router实例传递进来可以看到,如果选择了 const middlewareRouter = new Router() const routerPage1 = new Router({ prefix: '/page1' }) const routerPage2 = new Router({ prefix: '/page2' }) middlewareRouter.get('/list/:id', async (ctx, next) => { console.log('trigger middleware') ctx.body = `hi there.` await next() }) routerPage1.use(middlewareRouter.routes()) routerPage2.use(middlewareRouter.routes()) app.use(middlewareRouter.routes()) app.use(routerPage1.routes()) app.use(routerPage2.routes()) 就像上述代码,实际上会有两个问题:
一定要小心使用,不要认为这样的方式可以用来实现路由的复用 请求的处理以及,终于来到了最后一步,当一个请求来了以后, app.use(router.routes()) app.use(router.allowedMethods())
Router.prototype.routes = Router.prototype.middleware = function () { var router = this var dispatch = function dispatch(ctx, next) { var path = router.opts.routerPath || ctx.routerPath || ctx.path var matched = router.match(path, ctx.method) var layerChain, layer, i if (ctx.matched) { ctx.matched.push.apply(ctx.matched, matched.path) } else { ctx.matched = matched.path } ctx.router = router if (!matched.route) return next() var matchedLayers = matched.pathAndMethod var mostSpecificLayer = matchedLayers[matchedLayers.length - 1] ctx._matchedRoute = mostSpecificLayer.path if (mostSpecificLayer.name) { ctx._matchedRouteName = mostSpecificLayer.name } layerChain = matchedLayers.reduce(function(memo, layer) { memo.push(function(ctx, next) { ctx.captures = layer.captures(path, ctx.captures) ctx.params = layer.params(path, ctx.captures, ctx.params) ctx.routerName = layer.name return next() }) return memo.concat(layer.stack) }, []) return compose(layerChain)(ctx, next) }; dispatch.router = this return dispatch } 首先可以看到, 进入中间件以后会进行URL的判断,就是我们上边提到的可以用来做 Router.prototype.match = function (path, method) { var layers = this.stack // 这个就是获取的Router实例中所有的中间件对应的layer对象 var layer var matched = { path: [], pathAndMethod: [], route: false } for (var len = layers.length, i = 0; i < len; i++) { layer = layers[i] if (layer.match(path)) { // 这里就是一个简单的正则匹配 matched.path.push(layer) if (layer.methods.length === 0 || ~layer.methods.indexOf(method)) { // 将有效的中间件推入 matched.pathAndMethod.push(layer) // 判断是否存在METHOD if (layer.methods.length) matched.route = true } } } return matched } // 一个简单的正则匹配 Layer.prototype.match = function (path) { return this.regexp.test(path) } 而之所以会存在说判断是否有
const router1 = new Router() const router2 = new Router() router1.post('/', _ => {}) router1.get('/', async (ctx, next) => { ctx.redirectBody = 'hi' console.log(`trigger router1, matched length: ${ctx.matched.length}`) await next() }) router2.get('/', async (ctx, next) => { ctx.redirectBody = 'hi' console.log(`trigger router2, matched length: ${ctx.matched.length}`) await next() }) app.use(router1.routes()) app.use(router2.routes()) // > curl http://127.0.0.1:8888/ // => trigger router1, matched length: 2 // => trigger router2, matched length: 3 关于中间件的执行,在 var matchedLayers = matched.pathAndMethod layerChain = matchedLayers.reduce(function(memo, layer) { memo.push(function(ctx, next) { ctx.captures = layer.captures(path, ctx.captures) ctx.params = layer.params(path, ctx.captures, ctx.params) ctx.routerName = layer.name return next() }) return memo.concat(layer.stack) }, []) return compose(layerChain)(ctx, next) 这坨代码会在所有匹配到的中间件之前添加一个 [ layer1[0], // 第一个register中对应的中间件1 layer1[1], // 第一个register中对应的中间件2 layer2[0] // 第二个register中对应的中间件1 ] // => [ (ctx, next) => { ctx.params = layer1.params // 第一个register对应信息的赋值 return next() }, layer1[0], // 第一个register中对应的中间件1 layer1[1], // 第一个register中对应的中间件2 (ctx, next) => { ctx.params = layer2.params // 第二个register对应信息的赋值 return next() }, layer2[0] // 第二个register中对应的中间件1 ] 在 Note이 시점에서
两个最简单的举证:
이러한 방식으로 사용할 수 있다고 문서에 명시되어 있지 않은 경우 코드에 해당 구현이 있어야 하는 이유는 무엇입니까?
|
위 내용은 코아라우터란 무엇인가요? Koa-Router의 일반적인 구조와 동작과정에 대한 자세한 설명의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!