From d2ad8491fd8484aab465d30d1f9a18a49ecf4fe1 Mon Sep 17 00:00:00 2001 From: yeliex Date: Mon, 4 Jul 2022 16:49:57 +0800 Subject: [PATCH] feat: allow set router host match (#156) --- API.md | 34 ++++++++++++++++--- README.md | 1 + lib/router.js | 52 +++++++++++++++++++++++++++++ test/lib/router.js | 81 ++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 163 insertions(+), 5 deletions(-) diff --git a/API.md b/API.md index 6f48c24..8ca55fd 100644 --- a/API.md +++ b/API.md @@ -7,6 +7,7 @@ * [new Router(\[opts\])](#new-routeropts) * [router.get|put|post|patch|delete|del ⇒ Router](#routergetputpostpatchdeletedel--coderoutercode) * [Named routes](#named-routes) + * [Match host](#match-host) * [Multiple middleware](#multiple-middleware) * [Nested routers](#nested-routers) * [Router prefixes](#router-prefixes) @@ -30,11 +31,12 @@ Create a new router. -| Param | Type | Description | -| ---------------- | -------------------- | ------------------------------------------------------------------------ | -| [opts] | Object | | -| [opts.prefix] | String | prefix router paths | -| [opts.exclusive] | Boolean | only run last matched route's controller when there are multiple matches | +| Param | Type | Description | +| ---------------- | -------------------------- | ------------------------------------------------------------------------ | +| [opts] | Object | | +| [opts.prefix] | String | prefix router paths | +| [opts.exclusive] | Boolean | only run last matched route's controller when there are multiple matches | +| [opts.host] | String/Regexp | hostname to match for all routes | **Example** Basic usage: @@ -106,6 +108,28 @@ router.url('user', 3); // => "/users/3" ``` +### Match host + +Routers can match against a specific host by using the `host` property. + +```javascript +const routerA = new Router({ + host: 'hosta.com' // only match if request host exactly equal `hosta.com` +}); + +router.get('/', (ctx, next) => { + // Response for hosta.com +}); + +const routerB = new Router({ + host: /^(.*\.)?hostb\.com$/ // match all subdomains of hostb.com, including hostb.com, www.hostb.com, etc. +}); + +router.get('/', (ctx, next) => { + // Response index for matched hosts +}); +``` + ### Multiple middleware Multiple middleware may be given: diff --git a/README.md b/README.md index 6656824..0d11075 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,7 @@ * Express-style routing (`app.get`, `app.put`, `app.post`, etc.) * Named URL parameters * Named routes with URL generation +* Match routes with specific host * Responds to `OPTIONS` requests with allowed methods * Support for `405 Method Not Allowed` and `501 Not Implemented` * Multiple route middleware diff --git a/lib/router.js b/lib/router.js index d682c63..2c0ad31 100644 --- a/lib/router.js +++ b/lib/router.js @@ -48,6 +48,7 @@ module.exports = Router; * @param {Object=} opts * @param {Boolean=false} opts.exclusive only run last matched route's controller when there are multiple matches * @param {String=} opts.prefix prefix router paths + * @param {String|RegExp=} opts.host host for router match * @constructor */ @@ -68,6 +69,7 @@ function Router(opts = {}) { this.params = {}; this.stack = []; + this.host = this.opts.host; } /** @@ -183,6 +185,24 @@ function Router(opts = {}) { * The [path-to-regexp](https://github.com/pillarjs/path-to-regexp) module is * used to convert paths to regular expressions. * + * + * ### Match host for each router instance + * + * ```javascript + * const router = new Router({ + * host: 'example.domain' // only match if request host exactly equal `example.domain` + * }); + * + * ``` + * + * OR host cloud be a regexp + * + * ```javascript + * const router = new Router({ + * host: /.*\.?example\.domain$/ // all host end with .example.domain would be matched + * }); + * ``` + * * @name get|put|post|patch|delete|del * @memberof module:koa-router.prototype * @param {String} path @@ -358,6 +378,12 @@ Router.prototype.routes = Router.prototype.middleware = function () { const dispatch = function dispatch(ctx, next) { debug('%s %s', ctx.method, ctx.path); + const hostMatched = router.matchHost(ctx.host); + + if (!hostMatched) { + return next(); + } + const path = router.opts.routerPath || ctx.routerPath || ctx.path; const matched = router.match(path, ctx.method); let layerChain; @@ -735,6 +761,32 @@ Router.prototype.match = function (path, method) { return matched; }; +/** + * Match given `input` to allowed host + * @param {String} input + * @returns {boolean} + */ + +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); + } +}; + /** * Run middleware for named route parameters. Useful for auto-loading or * validation. diff --git a/test/lib/router.js b/test/lib/router.js index e599de2..547bfb1 100644 --- a/test/lib/router.js +++ b/test/lib/router.js @@ -2410,4 +2410,85 @@ describe('Router', function () { done(); }); }); + + describe('Support host', function () { + it('should support host match', function (done) { + const app = new Koa(); + const router = new Router({ + host: 'test.domain' + }); + router.get('/', (ctx) => { + ctx.body = { + url: '/' + }; + }); + app.use(router.routes()); + + const server = http.createServer(app.callback()); + + request(server) + .get('/') + .set('Host', 'test.domain') + .expect(200) + .end(function (err, res) { + if (err) return done(err); + + request(server) + .get('/') + .set('Host', 'a.domain') + .expect(404) + .end(function (err, res) { + if (err) return done(err); + done(); + }); + }); + }); + it('should support host match regexp', function (done) { + const app = new Koa(); + const router = new Router({ + host: /^(.*\.)?test\.domain/ + }); + router.get('/', (ctx) => { + ctx.body = { + url: '/' + }; + }); + app.use(router.routes()); + const server = http.createServer(app.callback()); + + request(server) + .get('/') + .set('Host', 'test.domain') + .expect(200) + .end(function (err, res) { + if (err) return done(err); + + request(server) + .get('/') + .set('Host', 'www.test.domain') + .expect(200) + .end(function (err, res) { + if (err) return done(err); + + request(server) + .get('/') + .set('Host', 'any.sub.test.domain') + .expect(200) + .end(function (err, res) { + if (err) return done(err); + + request(server) + .get('/') + .set('Host', 'sub.anytest.domain') + .expect(404) + .end(function (err, res) { + if (err) return done(err); + + done(); + }); + }); + }); + }); + }); + }); });