koajs / router

Router middleware for Koa. Maintained by @forwardemail and @ladjs.
MIT License
849 stars 174 forks source link

Support multiple hosts for routing without use of RegExp #164

Open EdanKriss opened 1 year ago

EdanKriss commented 1 year ago

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"]

Workaround 2.

Execute router middleware conditionally, only if domain matches manual check

domainLookup: Koa.Middleware = 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

- [x] I have searched through GitHub issues for similar issues.
- [x] I have completely read through the README and documentation.