Skip to content

Instantly share code, notes, and snippets.

@banyudu
Created March 10, 2020 11:03
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save banyudu/2eeaa1ddd3293ddeba20741af51e49e4 to your computer and use it in GitHub Desktop.
Save banyudu/2eeaa1ddd3293ddeba20741af51e49e4 to your computer and use it in GitHub Desktop.
Koa中动态修改路由

背景

前几天遇到一个技术问题,Koa-router如何在一定条件之下,动态地修改路由?

具体来说,是使 foo.example.com 和 example.com/foo 都能访问到相同的路由,即子域名和子路由具有相同的效果。

问题

我们可以很容易地创建一个middleware,通过ctx.request.hostname获取到是否包含子域名 subdomain。

const subDomainMiddleware = async (ctx, next) => {
  const hostname = ctx.request.hostname
  const domainNames = hostname.split('.')
  if (domainNames.length > 2) {
    // 有subdomain
    ctx.appName = domainNames[0]
  } else {
    // 无subdomain,取路径的第一段当subdomain
    ctx.appName = ctx.path.split('/')[0]
  }
	// 做一些其它与subDomain关联的配置
  return next();
}

上述的代码,通过子域名或第一级路径的方式获取到了子应用的名称ctx.appName

但是当我们想要让二者共享同一套router的时候,就遇到了问题。

先考虑简单的情况,假设有如下的 router 定义:

import * as Router from 'koa-router';
const router = new Router();

router.get('/', async (ctx) => { ctx.body = 'Hello World!'; });
router.get('/test', async (ctx) => { ctx.body = 'test';});

export const routes = router.routes();

为了同时支持有子域名的情况(不用忽略一级路径)和没有子域名的情况(需要忽略掉第一级路径),最直观的想法是加入如下的改动

import * as Router from 'koa-router';
const router = new Router();

router.get('/', async (ctx) => { ctx.body = 'Hello World!'; });
router.get('/test', async (ctx) => { ctx.body = 'test';});

// 复制一份带通配符的
router.get('/:appName/', async (ctx) => { ctx.body = 'Hello World!'; });
router.get('/:appName/test', async (ctx) => { ctx.body = 'test';});

export const routes = router.routes();

但是如上的代码是有bug的,对于 foo.example.com的访问方式来说,本来应该只有 //test两个路由,但是现在还会有 /bar/test之类的路由,产生了多余的路由。

另外还有一个问题,就是这两份router是有可能产生冲突的。如 appName恰好是test,则 /test这个路由会出现两次,有冲突。

解决方案

搜索了好久,没发现有人提过类似的问题,就翻了下源码。

刚好看到了如下的一段代码:

var dispatch = function dispatch(ctx, next) {
    debug('%s %s', ctx.method, ctx.path);

    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 path = router.opts.routerPath || ctx.routerPath || ctx.path;

从这行代码中可以看出,router中在使用ctx.path(实际的路径)之前,会先判断ctx.routerPath是否有值,并优先使用它。

所以我们可以通过修改ctx.routerPath,达到在指定条件下忽略url中第一层路径的需求。

具体代码如下:

const subDomainMiddleware = async (ctx, next) => {
  const hostname = ctx.request.hostname
  const domainNames = hostname.split('.')
  if (domainNames.length > 2) {
    // 有subdomain
    ctx.appName = domainNames[0]
  } else {
    // 无subdomain,取路径的第一段当subdomain
    ctx.appName = ctx.path.split('/')[0]
    
    // 同时修改ctx.routerPath
    ctx.routerPath = ctx.path.substr(ctx.appName.length + 1) || '/'
  }
	// 做一些其它与subDomain关联的配置
  return next();
}

关键代码是ctx.routerPath = ctx.path.substr(ctx.appName.length + 1) || '/'这一行。

结论

通过修改ctx.routerPath值,可以动态地调整koa中请求传递给koa-router时有效的路径。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment