Skip to content

Support multiple hosts for routing without use of RegExp #164

Open
@EdanKriss

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.

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions