前几天遇到一个技术问题,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时有效的路径。