Koa-router 優先級問題

問題描述

在使用Koa-router做爲路由遇到了一個優先級問題.以下代碼正則表達式

// routerPage.js file
const router = require("koa-router")
router.get("/test", ctx => { ctx.body = "test" })
router.get("/router/test", ctx => { ctx.body = "router test" })
module.exports = router

// routerIndex.js file
const router = require("koa-router")
const routerPage = require("./routerPage")
router.use(routerPage.routes(), routerPage.allowedMethods())
module.exports = router

在訪問"/router/test"時路由會優先匹配到"/test"路由,返回ctx.body = "test",這個問題就很尷尬了,項目空閒下來去翻看源碼終於找到了緣由segmentfault

問題緣由

Koa-router的源碼並不長,layer.js和router.js兩個文件加起來共一千多行代碼.建議能夠結合這篇文章閱讀.
其中形成這個問題的緣由就是router.js中router.use這個方法,方法源碼以下數組

// 主要做用: 給path添加中間件
Router.prototype.use = function () {
  var router = this;
  var middleware = Array.prototype.slice.call(arguments);
  var path = '(.*)';

  // 若是path爲array則遞歸調用use方法
  if (Array.isArray(middleware[0]) && typeof middleware[0][0] === 'string') {
    middleware[0].forEach(function (p) {
      router.use.apply(router, [p].concat(middleware.slice(1)));
    });

    return this;
  }
  //若是傳入了path,則只對此path操做
  var hasPath = typeof middleware[0] === 'string';
  if (hasPath) {
    path = middleware.shift();
  }
  // 若是傳入參數爲一個路由數組,則遍歷爲每一個路由添加前綴,中間件,並將此路由放入全局的路由數組
  middleware.forEach(function (m) {
    if (m.router) {
      m.router.stack.forEach(function (nestedLayer) {
        if (path) nestedLayer.setPrefix(path);
        if (router.opts.prefix) nestedLayer.setPrefix(router.opts.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 });
    }
  });

  return this;
};

問題就出在router.use(routerPage.routes(), routerPage.allowedMethods())時沒有設置前綴,
路由就自動添加了默認的前綴"(.*)",這裏的path發生了改變,在路由後續的操做中,將path使用pathToRegExp轉換成正則表達式時"/test"這個path本應該是/^\/test...../就會變成/(.*)/\/test...(大概是這個意思)
那麼本來以/test開頭的路由就會匹配包含/test的路由
因此request path 爲/router/test時會被/test路由先匹配中,路由也就不會往下匹配app

解決方式

  1. 將條件更精確的路由放到前面
  2. /test那個路由中加一箇中間件,當匹配到/router/testawait next()繼續向下執行
  3. 更改源碼Router.propertype.usepath = "(.*)"path = false
  4. 在使用router.use時代碼作必定更改,代碼以下
// routerPage.js file
const router = require("koa-router")
router.get("test", ctx => { ctx.body = "test" })
router.get("router/test", ctx => { ctx.body = "router test" })
module.exports = router

// routerIndex.js file
const router = require("koa-router")
const routerPage = require("./routerPage")
router.use("/", routerPage.routes(), routerPage.allowedMethods())
module.exports = router
相關文章
相關標籤/搜索