Description
I use Koa @koa/router to serve multiple routers from the same webserver.
Each router serves one or more domains, sometimes including specific subdomains.
My suggestion is to enhance the type signature of Router.RouterOptions["host"]
to support an array of strings, matched by equality to any member, in addition to the current single string equality and RegExp test.
I suppose the source code change would look like this: (line 771 in /lib/router.js
)
Router.prototype.matchHost = function (input) {
const { host } = this;
if (!host) {
return true;
}
if (!input) {
return false;
}
if (typeof host === 'string') {
return input === host;
}
if (typeof host === 'object' && host instanceof RegExp) {
return host.test(input);
}
+ if (Array.isArray(host)) {
+ return host.includes(input);
+ }
};
With the proposed change, the 'parent' koa app would only have the responsibility of providing an array of valid domains:
const router = new Router({
host: [
'some-domain.com',
'www.some-domain.com',
'some.other-domain.com',
],
});
I can currently accomplish similar results 2 separate ways, neither of which is as simple as I would like:
Workaround 1.
Build a dynamic RegExp from config at runtime, and assign to Router.RouterOptions["host"]
- requires escaping period characters from domain
- The ECMA proposal to ease this is still in infancy: https://github.com/tc39/proposal-regex-escaping
- cumbersome to handle subdomains without using wildcards or accidental performance pitfall
- pseudo example of my usage:
const domains = [
'some-domain.com',
'www.some-domain.com',
'some.other-domain.com',
];
const router = new Router({
host: new RegExp(`^(?:${domains.map(escapePeriods).join('|')})$`),
});
Workaround 2.
Execute router middleware conditionally, only if domain matches manual check
- Described generically here: https://github.com/koajs/examples/blob/master/conditional-middleware/app.js
- here is pseudo example of my usage:
constructor() {
this.#examplePortalRoutes = examplePortalRouter.routes();
this.#examplePortalAllowedMethods = examplePortalRouter.allowedMethods({ throw: true });
this.#app.use(this.#catchErrors);
this.#app.use(this.#domainLookup);
this.#app.use<DomainConfigState>(this.#appRouter);
}
#domainLookup: Koa.Middleware<SettableDomainConfigState> = async (ctx, next) => {
const domainConfig: DomainConfig | undefined = this.#domainConfigMap.get(ctx.request.hostname);
if (domainConfig) {
ctx.state.domainConfig = domainConfig;
await next();
} else {
throw { message: `Unrecognized domain: ${domain}`, status: 400 } satisfies Errors.InvalidRequest;
}
}
#appRouter: Koa.Middleware<DomainConfigState, Router.RouterParamContext> = async (ctx, next) => {
switch (ctx.state.domainConfig.domainApp) {
case 'example_portal':
await this.#examplePortalRoutes.call(this, ctx, async () => {
await this.#examplePortalAllowedMethods.call(this, ctx, next);
});
break;
default:
await next();
break;
}
}
Checklist
- I have searched through GitHub issues for similar issues.
- I have completely read through the README and documentation.