find-my-way
Advanced tools
Comparing version 5.1.1 to 5.2.0
344
bench.js
'use strict' | ||
const Benchmark = require('benchmark') | ||
// The default number of samples for Benchmark seems to be low enough that it | ||
// can generate results with significant variance (~2%) for this benchmark | ||
// suite. This makes it sometimes a bit confusing to actually evaluate impact of | ||
// changes on performance. Setting the minimum of samples to 500 results in | ||
// significantly lower variance on my local setup for this tests suite, and | ||
// gives me higher confidence in benchmark results. | ||
Benchmark.options.minSamples = 500 | ||
const path = require('path') | ||
const { Worker } = require('worker_threads') | ||
const suite = Benchmark.Suite() | ||
const BENCH_THREAD_PATH = path.join(__dirname, 'bench-thread.js') | ||
const FindMyWay = require('./') | ||
const testCases = [ | ||
{ | ||
name: 'lookup root "/" route', | ||
setupURLs: [{ method: 'GET', url: '/' }], | ||
testingMethodName: 'lookup', | ||
args: [ | ||
{ method: 'GET', url: '/', headers: { host: 'fastify.io' } }, | ||
null | ||
] | ||
}, | ||
{ | ||
name: 'lookup static route', | ||
setupURLs: [{ method: 'GET', url: '/a' }], | ||
testingMethodName: 'lookup', | ||
args: [ | ||
{ method: 'GET', url: '/a', headers: { host: 'fastify.io' } }, | ||
null | ||
] | ||
}, | ||
{ | ||
name: 'lookup dynamic route', | ||
setupURLs: [{ method: 'GET', url: '/user/:id' }], | ||
testingMethodName: 'lookup', | ||
args: [ | ||
{ method: 'GET', url: '/user/tomas', headers: { host: 'fastify.io' } }, | ||
null | ||
] | ||
}, | ||
{ | ||
name: 'lookup dynamic multi-parametric route', | ||
setupURLs: [{ method: 'GET', url: '/customer/:name-:surname' }], | ||
testingMethodName: 'lookup', | ||
args: [ | ||
{ method: 'GET', url: '/customer/john-doe', headers: { host: 'fastify.io' } }, | ||
null | ||
] | ||
}, | ||
{ | ||
name: 'lookup dynamic multi-parametric route with regex', | ||
setupURLs: [{ method: 'GET', url: '/at/:hour(^\\d+)h:minute(^\\d+)m' }], | ||
testingMethodName: 'lookup', | ||
args: [ | ||
{ method: 'GET', url: '/at/12h00m', headers: { host: 'fastify.io' } }, | ||
null | ||
] | ||
}, | ||
{ | ||
name: 'lookup long static route', | ||
setupURLs: [{ method: 'GET', url: '/abc/def/ghi/lmn/opq/rst/uvz' }], | ||
testingMethodName: 'lookup', | ||
args: [ | ||
{ method: 'GET', url: '/abc/def/ghi/lmn/opq/rst/uvz', headers: { host: 'fastify.io' } }, | ||
null | ||
] | ||
}, | ||
{ | ||
name: 'lookup long dynamic route', | ||
setupURLs: [{ method: 'GET', url: '/user/:id/static' }], | ||
testingMethodName: 'lookup', | ||
args: [ | ||
{ | ||
method: 'GET', | ||
url: '/user/qwertyuiopasdfghjklzxcvbnm/static', | ||
headers: { host: 'fastify.io' } | ||
}, | ||
null | ||
] | ||
}, | ||
{ | ||
name: 'lookup static route on constrained router', | ||
setupURLs: [ | ||
{ method: 'GET', url: '/' }, | ||
{ method: 'GET', url: '/versioned', opts: { constraints: { version: '1.2.0' } } }, | ||
{ method: 'GET', url: '/versioned', opts: { constraints: { version: '2.0.0', host: 'example.com' } } }, | ||
{ method: 'GET', url: '/versioned', opts: { constraints: { version: '2.0.0', host: 'fastify.io' } } } | ||
], | ||
testingMethodName: 'lookup', | ||
args: [ | ||
{ | ||
method: 'GET', | ||
url: '/', | ||
headers: { host: 'fastify.io' } | ||
}, | ||
null | ||
] | ||
}, | ||
{ | ||
name: 'lookup static versioned route', | ||
setupURLs: [ | ||
{ method: 'GET', url: '/' }, | ||
{ method: 'GET', url: '/versioned', opts: { constraints: { version: '1.2.0' } } }, | ||
{ method: 'GET', url: '/versioned', opts: { constraints: { version: '2.0.0', host: 'example.com' } } }, | ||
{ method: 'GET', url: '/versioned', opts: { constraints: { version: '2.0.0', host: 'fastify.io' } } } | ||
], | ||
testingMethodName: 'lookup', | ||
args: [ | ||
{ | ||
method: 'GET', | ||
url: '/versioned', | ||
headers: { 'accept-version': '1.x', host: 'fastify.io' } | ||
}, | ||
null | ||
] | ||
}, | ||
{ | ||
name: 'lookup static constrained (version & host) route', | ||
setupURLs: [ | ||
{ method: 'GET', url: '/' }, | ||
{ method: 'GET', url: '/versioned', opts: { constraints: { version: '1.2.0' } } }, | ||
{ method: 'GET', url: '/versioned', opts: { constraints: { version: '2.0.0', host: 'example.com' } } }, | ||
{ method: 'GET', url: '/versioned', opts: { constraints: { version: '2.0.0', host: 'fastify.io' } } } | ||
], | ||
testingMethodName: 'lookup', | ||
args: [ | ||
{ | ||
method: 'GET', | ||
url: '/versioned', | ||
headers: { 'accept-version': '2.x', host: 'fastify.io' } | ||
}, | ||
null | ||
] | ||
}, | ||
{ | ||
name: 'find root "/" route', | ||
setupURLs: [{ method: 'GET', url: '/' }], | ||
testingMethodName: 'find', | ||
args: ['GET', '/'] | ||
}, | ||
{ | ||
name: 'find static route', | ||
setupURLs: [{ method: 'GET', url: '/a' }], | ||
testingMethodName: 'find', | ||
args: ['GET', '/a'] | ||
}, | ||
{ | ||
name: 'find dynamic route', | ||
setupURLs: [{ method: 'GET', url: '/user/:id' }], | ||
testingMethodName: 'find', | ||
args: ['GET', '/user/tomas'] | ||
}, | ||
{ | ||
name: 'find dynamic route with encoded parameter unoptimized', | ||
setupURLs: [{ method: 'GET', url: '/user/:id' }], | ||
testingMethodName: 'find', | ||
args: ['GET', '/user/maintainer%2Btomas'] | ||
}, | ||
{ | ||
name: 'find dynamic route with encoded parameter optimized', | ||
setupURLs: [{ method: 'GET', url: '/user/:id' }], | ||
testingMethodName: 'find', | ||
args: ['GET', '/user/maintainer%20tomas'] | ||
}, | ||
{ | ||
name: 'find dynamic multi-parametric route', | ||
setupURLs: [{ method: 'GET', url: '/customer/:name-:surname' }], | ||
testingMethodName: 'find', | ||
args: ['GET', '/customer/john-doe'] | ||
}, | ||
{ | ||
name: 'find dynamic multi-parametric route with regex', | ||
setupURLs: [{ method: 'GET', url: '/at/:hour(^\\d+)h:minute(^\\d+)m' }], | ||
testingMethodName: 'find', | ||
args: ['GET', '/at/12h00m'] | ||
}, | ||
{ | ||
name: 'find long static route', | ||
setupURLs: [{ method: 'GET', url: '/abc/def/ghi/lmn/opq/rst/uvz' }], | ||
testingMethodName: 'find', | ||
args: ['GET', '/abc/def/ghi/lmn/opq/rst/uvz'] | ||
}, | ||
{ | ||
name: 'find long dynamic route', | ||
setupURLs: [{ method: 'GET', url: '/user/:id/static' }], | ||
testingMethodName: 'find', | ||
args: ['GET', '/user/qwertyuiopasdfghjklzxcvbnm/static'] | ||
}, | ||
{ | ||
name: 'find long nested dynamic route', | ||
setupURLs: [{ method: 'GET', url: '/posts/:id/comments/:id/author' }], | ||
testingMethodName: 'find', | ||
args: ['GET', '/posts/10/comments/42/author'] | ||
}, | ||
{ | ||
name: 'find long nested dynamic route with encoded parameter unoptimized', | ||
setupURLs: [{ method: 'GET', url: '/posts/:id/comments/:id/author' }], | ||
testingMethodName: 'find', | ||
args: ['GET', '/posts/10%2C10/comments/42%2C42/author'] | ||
}, | ||
{ | ||
name: 'find long nested dynamic route with encoded parameter optimized', | ||
setupURLs: [{ method: 'GET', url: '/posts/:id/comments/:id/author' }], | ||
testingMethodName: 'find', | ||
args: ['GET', '/posts/10%2510/comments/42%2542/author'] | ||
}, | ||
{ | ||
name: 'find long nested dynamic route with other method', | ||
setupURLs: [{ method: 'POST', url: '/posts/:id/comments' }], | ||
testingMethodName: 'find', | ||
args: ['POST', '/posts/10/comments'] | ||
} | ||
] | ||
const findMyWay = new FindMyWay() | ||
findMyWay.on('GET', '/', () => true) | ||
findMyWay.on('GET', '/user/:id', () => true) | ||
findMyWay.on('GET', '/user/:id/static', () => true) | ||
findMyWay.on('POST', '/user/:id', () => true) | ||
findMyWay.on('PUT', '/user/:id', () => true) | ||
findMyWay.on('GET', '/customer/:name-:surname', () => true) | ||
findMyWay.on('POST', '/customer', () => true) | ||
findMyWay.on('GET', '/at/:hour(^\\d+)h:minute(^\\d+)m', () => true) | ||
findMyWay.on('GET', '/abc/def/ghi/lmn/opq/rst/uvz', () => true) | ||
async function runBenchmark (testCase) { | ||
const worker = new Worker(BENCH_THREAD_PATH, { | ||
workerData: testCase | ||
}) | ||
findMyWay.on('GET', '/products', () => true) | ||
findMyWay.on('GET', '/products/:id', () => true) | ||
findMyWay.on('GET', '/products/:id/options', () => true) | ||
return new Promise((resolve, reject) => { | ||
let result = null | ||
worker.on('error', reject) | ||
worker.on('message', (benchResult) => { | ||
result = benchResult | ||
}) | ||
worker.on('exit', (code) => { | ||
if (code === 0) { | ||
resolve(result) | ||
} else { | ||
reject(new Error(`Worker stopped with exit code ${code}`)) | ||
} | ||
}) | ||
}) | ||
} | ||
findMyWay.on('GET', '/posts', () => true) | ||
findMyWay.on('POST', '/posts', () => true) | ||
findMyWay.on('GET', '/posts/:id', () => true) | ||
findMyWay.on('GET', '/posts/:id/author', () => true) | ||
findMyWay.on('GET', '/posts/:id/comments', () => true) | ||
findMyWay.on('POST', '/posts/:id/comments', () => true) | ||
findMyWay.on('GET', '/posts/:id/comments/:id', () => true) | ||
findMyWay.on('GET', '/posts/:id/comments/:id/author', () => true) | ||
findMyWay.on('GET', '/posts/:id/counter', () => true) | ||
async function runBenchmarks () { | ||
for (const testCase of testCases) { | ||
const resultMessage = await runBenchmark(testCase) | ||
console.log(resultMessage) | ||
} | ||
} | ||
const constrained = new FindMyWay() | ||
constrained.on('GET', '/', () => true) | ||
constrained.on('GET', '/versioned', () => true) | ||
constrained.on('GET', '/versioned', { constraints: { version: '1.2.0' } }, () => true) | ||
constrained.on('GET', '/versioned', { constraints: { version: '2.0.0', host: 'example.com' } }, () => true) | ||
constrained.on('GET', '/versioned', { constraints: { version: '2.0.0', host: 'fastify.io' } }, () => true) | ||
suite | ||
.add('lookup static route', function () { | ||
findMyWay.lookup({ method: 'GET', url: '/', headers: { host: 'fastify.io' } }, null) | ||
}) | ||
.add('lookup dynamic route', function () { | ||
findMyWay.lookup({ method: 'GET', url: '/user/tomas', headers: { host: 'fastify.io' } }, null) | ||
}) | ||
.add('lookup dynamic multi-parametric route', function () { | ||
findMyWay.lookup({ method: 'GET', url: '/customer/john-doe', headers: { host: 'fastify.io' } }, null) | ||
}) | ||
.add('lookup dynamic multi-parametric route with regex', function () { | ||
findMyWay.lookup({ method: 'GET', url: '/at/12h00m', headers: { host: 'fastify.io' } }, null) | ||
}) | ||
.add('lookup long static route', function () { | ||
findMyWay.lookup({ method: 'GET', url: '/abc/def/ghi/lmn/opq/rst/uvz', headers: { host: 'fastify.io' } }, null) | ||
}) | ||
.add('lookup long dynamic route', function () { | ||
findMyWay.lookup({ method: 'GET', url: '/user/qwertyuiopasdfghjklzxcvbnm/static', headers: { host: 'fastify.io' } }, null) | ||
}) | ||
.add('lookup static route on constrained router', function () { | ||
constrained.lookup({ method: 'GET', url: '/', headers: { host: 'fastify.io' } }, null) | ||
}) | ||
.add('lookup static versioned route', function () { | ||
constrained.lookup({ method: 'GET', url: '/versioned', headers: { 'accept-version': '1.x', host: 'fastify.io' } }, null) | ||
}) | ||
.add('lookup static constrained (version & host) route', function () { | ||
constrained.lookup({ method: 'GET', url: '/versioned', headers: { 'accept-version': '2.x', host: 'fastify.io' } }, null) | ||
}) | ||
.add('find static route', function () { | ||
findMyWay.find('GET', '/', undefined) | ||
}) | ||
.add('find dynamic route', function () { | ||
findMyWay.find('GET', '/user/tomas', undefined) | ||
}) | ||
.add('find dynamic route with encoded parameter unoptimized', function () { | ||
findMyWay.find('GET', '/user/maintainer%2Btomas', undefined) | ||
}) | ||
.add('find dynamic route with encoded parameter optimized', function () { | ||
findMyWay.find('GET', '/user/maintainer%20tomas', undefined) | ||
}) | ||
.add('find dynamic multi-parametric route', function () { | ||
findMyWay.find('GET', '/customer/john-doe', undefined) | ||
}) | ||
.add('find dynamic multi-parametric route with regex', function () { | ||
findMyWay.find('GET', '/at/12h00m', undefined) | ||
}) | ||
.add('find long static route', function () { | ||
findMyWay.find('GET', '/abc/def/ghi/lmn/opq/rst/uvz', undefined) | ||
}) | ||
.add('find long dynamic route', function () { | ||
findMyWay.find('GET', '/user/qwertyuiopasdfghjklzxcvbnm/static', undefined) | ||
}) | ||
.add('find long nested dynamic route', function () { | ||
findMyWay.find('GET', '/posts/10/comments/42/author', undefined) | ||
}) | ||
.add('find long nested dynamic route with encoded parameter unoptimized', function () { | ||
findMyWay.find('GET', '/posts/10%2C10/comments/42%2C42/author', undefined) | ||
}) | ||
.add('find long nested dynamic route with encoded parameter optimized', function () { | ||
findMyWay.find('GET', '/posts/10%2510/comments/42%2542/author', undefined) | ||
}) | ||
.add('find long nested dynamic route with other method', function () { | ||
findMyWay.find('POST', '/posts/10/comments', undefined) | ||
}) | ||
.add('find long nested dynamic route', function () { | ||
findMyWay.find('GET', '/posts/10/comments/42/author', undefined) | ||
}) | ||
.add('find long nested dynamic route with other method', function () { | ||
findMyWay.find('POST', '/posts/10/comments', undefined) | ||
}) | ||
.on('cycle', function (event) { | ||
console.log(String(event.target)) | ||
}) | ||
.on('complete', function () {}) | ||
.run() | ||
runBenchmarks() |
@@ -27,2 +27,4 @@ 'use strict' | ||
this.wildcardChild = null | ||
this.parametricChild = null | ||
this.wildcardBrother = null | ||
this.parametricBrother = null | ||
@@ -38,6 +40,2 @@ this.constrainer = options.constrainer | ||
Node.prototype.getLabel = function () { | ||
return this.prefix[0] | ||
} | ||
Node.prototype.addChild = function (node) { | ||
@@ -47,3 +45,3 @@ var label = '' | ||
case this.types.STATIC: | ||
label = node.getLabel() | ||
label = node.prefix[0] | ||
break | ||
@@ -53,2 +51,3 @@ case this.types.PARAM: | ||
case this.types.MULTI_PARAM: | ||
this.parametricChild = node | ||
label = ':' | ||
@@ -70,7 +69,6 @@ break | ||
this.children[label] = node | ||
this.numberOfChildren++ | ||
const nodeChildren = Object.values(this.children) | ||
this.numberOfChildren = nodeChildren.length | ||
this._saveParametricBrother() | ||
this._saveWildcardBrother() | ||
@@ -82,7 +80,5 @@ return this | ||
let parametricBrother = this.parametricBrother | ||
for (const child of Object.values(this.children)) { | ||
if (child.prefix === ':') { | ||
parametricBrother = child | ||
break | ||
} | ||
if (this.parametricChild !== null) { | ||
this.parametricChild.parametricBrother = parametricBrother | ||
parametricBrother = this.parametricChild | ||
} | ||
@@ -93,3 +89,3 @@ | ||
for (const child of Object.values(this.children)) { | ||
if (child && child.kind === this.types.STATIC) { | ||
if (child && child !== parametricBrother) { | ||
child.parametricBrother = parametricBrother | ||
@@ -102,2 +98,20 @@ child._saveParametricBrother(parametricBrother) | ||
Node.prototype._saveWildcardBrother = function () { | ||
let wildcardBrother = this.wildcardBrother | ||
if (this.wildcardChild !== null) { | ||
this.wildcardChild.wildcardBrother = wildcardBrother | ||
wildcardBrother = this.wildcardChild | ||
} | ||
// Save the wildcard brother inside static children | ||
if (wildcardBrother) { | ||
for (const child of Object.values(this.children)) { | ||
if (child && child !== wildcardBrother) { | ||
child.wildcardBrother = wildcardBrother | ||
child._saveWildcardBrother(wildcardBrother) | ||
} | ||
} | ||
} | ||
} | ||
Node.prototype.reset = function (prefix) { | ||
@@ -112,2 +126,3 @@ this.prefix = prefix | ||
this.wildcardChild = null | ||
this.parametricChild = null | ||
this.hasConstraints = false | ||
@@ -137,2 +152,6 @@ this._decompileGetHandlerMatchingConstraints() | ||
if (this.parametricChild !== null) { | ||
newChild.parametricChild = this.parametricChild | ||
} | ||
this.reset(this.prefix.slice(0, length)) | ||
@@ -147,20 +166,12 @@ this.addChild(newChild) | ||
Node.prototype.findMatchingChild = function (derivedConstraints, path) { | ||
var child = this.children[path[0]] | ||
if (child !== undefined && (child.numberOfChildren > 0 || child.getMatchingHandler(derivedConstraints) !== null)) { | ||
if (path.slice(0, child.prefix.length) === child.prefix) { | ||
return child | ||
Node.prototype.findStaticMatchingChild = function (path, pathIndex) { | ||
const child = this.children[path[pathIndex]] | ||
if (child !== undefined) { | ||
for (let i = 0; i < child.prefix.length; i++) { | ||
if (path.charCodeAt(pathIndex + i) !== child.prefix.charCodeAt(i)) { | ||
return null | ||
} | ||
} | ||
} | ||
child = this.children[':'] | ||
if (child !== undefined && (child.numberOfChildren > 0 || child.getMatchingHandler(derivedConstraints) !== null)) { | ||
return child | ||
} | ||
child = this.children['*'] | ||
if (child !== undefined && (child.numberOfChildren > 0 || child.getMatchingHandler(derivedConstraints) !== null)) { | ||
return child | ||
} | ||
return null | ||
@@ -212,13 +223,17 @@ } | ||
Node.prototype.getMatchingHandler = function (derivedConstraints) { | ||
if (derivedConstraints === undefined) { | ||
return this.unconstrainedHandler | ||
} | ||
if (this.hasConstraints) { | ||
// This node is constrained, use the performant precompiled constraint matcher | ||
return this._getHandlerMatchingConstraints(derivedConstraints) | ||
} else { | ||
// This node doesn't have any handlers that are constrained, so it's handlers probably match. Some requests have constraint values that *must* match however, like version, so check for those before returning it. | ||
if (derivedConstraints && derivedConstraints.__hasMustMatchValues) { | ||
return null | ||
} else { | ||
return this.unconstrainedHandler | ||
} | ||
} | ||
// This node doesn't have any handlers that are constrained, so it's handlers probably match. Some requests have constraint values that *must* match however, like version, so check for those before returning it. | ||
if (!derivedConstraints.__hasMustMatchValues) { | ||
return this.unconstrainedHandler | ||
} | ||
return null | ||
} | ||
@@ -225,0 +240,0 @@ |
217
index.js
@@ -401,3 +401,3 @@ 'use strict' | ||
Router.prototype.find = function find (method, path, derivedConstraints) { | ||
var currentNode = this.trees[method] | ||
let currentNode = this.trees[method] | ||
if (currentNode === undefined) return null | ||
@@ -417,5 +417,2 @@ | ||
var originalPath = path | ||
var originalPathLength = path.length | ||
if (this.caseSensitive === false) { | ||
@@ -425,24 +422,22 @@ path = path.toLowerCase() | ||
var maxParamLength = this.maxParamLength | ||
var wildcardNode = null | ||
var pathLenWildcard = 0 | ||
var decoded = null | ||
var pindex = 0 | ||
var params = null | ||
var i = 0 | ||
var idxInOriginalPath = 0 | ||
const maxParamLength = this.maxParamLength | ||
let pathIndex = currentNode.prefix.length | ||
const params = [] | ||
const pathLen = path.length | ||
const parametricBrothersStack = [] | ||
const wildcardBrothersStack = [] | ||
while (true) { | ||
var pathLen = path.length | ||
var prefix = currentNode.prefix | ||
// found the route | ||
if (pathIndex === pathLen) { | ||
const handle = currentNode.getMatchingHandler(derivedConstraints) | ||
// found the route | ||
if (pathLen === 0 || path === prefix) { | ||
var handle = derivedConstraints !== undefined ? currentNode.getMatchingHandler(derivedConstraints) : currentNode.unconstrainedHandler | ||
if (handle !== null && handle !== undefined) { | ||
var paramsObj = {} | ||
const paramsObj = {} | ||
if (handle.paramsLength > 0) { | ||
var paramNames = handle.params | ||
const paramNames = handle.params | ||
for (i = 0; i < handle.paramsLength; i++) { | ||
for (let i = 0; i < handle.paramsLength; i++) { | ||
paramsObj[paramNames[i]] = params[i] | ||
@@ -460,77 +455,61 @@ } | ||
var prefixLen = prefix.length | ||
var len = 0 | ||
var previousPath = path | ||
let node = currentNode.findStaticMatchingChild(path, pathIndex) | ||
// search for the longest common prefix | ||
i = pathLen < prefixLen ? pathLen : prefixLen | ||
while (len < i && path.charCodeAt(len) === prefix.charCodeAt(len)) len++ | ||
if (currentNode.parametricChild !== null) { | ||
if (node === null) { | ||
node = currentNode.parametricChild | ||
} else { | ||
parametricBrothersStack.push({ | ||
brotherPathIndex: pathIndex, | ||
paramsCount: params.length | ||
}) | ||
} | ||
} | ||
if (len === prefixLen) { | ||
path = path.slice(len) | ||
pathLen = path.length | ||
idxInOriginalPath += len | ||
if (currentNode.wildcardChild !== null) { | ||
if (node === null) { | ||
node = currentNode.wildcardChild | ||
} else { | ||
wildcardBrothersStack.push({ | ||
brotherPathIndex: pathIndex, | ||
paramsCount: params.length | ||
}) | ||
} | ||
} | ||
var node = currentNode.findMatchingChild(derivedConstraints, path) | ||
if (node === null) { | ||
let brotherNodeState | ||
node = currentNode.parametricBrother | ||
if (node === null) { | ||
return this._getWildcardNode(wildcardNode, originalPath, pathLenWildcard, derivedConstraints, params) | ||
node = currentNode.wildcardBrother | ||
if (node === null) { | ||
return null | ||
} | ||
brotherNodeState = wildcardBrothersStack.pop() | ||
} else { | ||
brotherNodeState = parametricBrothersStack.pop() | ||
} | ||
var goBack = previousPath.charCodeAt(0) === 47 ? previousPath : '/' + previousPath | ||
if (originalPath.indexOf(goBack) === -1) { | ||
// we need to know the outstanding path so far from the originalPath since the last encountered "/" and assign it to previousPath. | ||
// e.g originalPath: /aa/bbb/cc, path: bb/cc | ||
// outstanding path: /bbb/cc | ||
var pathDiff = originalPath.slice(0, originalPathLength - pathLen) | ||
previousPath = pathDiff.slice(pathDiff.lastIndexOf('/') + 1, pathDiff.length) + path | ||
} | ||
idxInOriginalPath = idxInOriginalPath - | ||
(previousPath.length - path.length) | ||
path = previousPath | ||
pathLen = previousPath.length | ||
len = prefixLen | ||
pathIndex = brotherNodeState.brotherPathIndex | ||
params.splice(brotherNodeState.paramsCount) | ||
} | ||
var kind = node.kind | ||
currentNode = node | ||
const kind = node.kind | ||
// static route | ||
if (kind === NODE_TYPES.STATIC) { | ||
// if exist, save the wildcard child | ||
if (currentNode.wildcardChild !== null) { | ||
wildcardNode = currentNode.wildcardChild | ||
pathLenWildcard = pathLen | ||
} | ||
currentNode = node | ||
pathIndex += node.prefix.length | ||
continue | ||
} | ||
if (len !== prefixLen) { | ||
return this._getWildcardNode(wildcardNode, originalPath, pathLenWildcard, derivedConstraints, params) | ||
} | ||
let paramEndIndex = pathIndex | ||
// if exist, save the wildcard child | ||
if (currentNode.wildcardChild !== null) { | ||
wildcardNode = currentNode.wildcardChild | ||
pathLenWildcard = pathLen | ||
} | ||
// parametric route | ||
if (kind === NODE_TYPES.PARAM) { | ||
currentNode = node | ||
i = path.indexOf('/') | ||
if (i === -1) i = pathLen | ||
if (i > maxParamLength) return null | ||
decoded = sanitizedUrl.sliceParameter(idxInOriginalPath, idxInOriginalPath + i) | ||
if (decoded === null) { | ||
return this._onBadUrl(originalPath.slice(idxInOriginalPath, idxInOriginalPath + i)) | ||
for (; paramEndIndex < pathLen; paramEndIndex++) { | ||
if (path.charCodeAt(paramEndIndex) === 47) { | ||
break | ||
} | ||
} | ||
params || (params = []) | ||
params[pindex++] = decoded | ||
path = path.slice(i) | ||
idxInOriginalPath += i | ||
continue | ||
} | ||
@@ -540,11 +519,3 @@ | ||
if (kind === NODE_TYPES.MATCH_ALL) { | ||
decoded = sanitizedUrl.sliceParameter(idxInOriginalPath) | ||
if (decoded === null) { | ||
return this._onBadUrl(originalPath.slice(idxInOriginalPath)) | ||
} | ||
params || (params = []) | ||
params[pindex] = decoded | ||
currentNode = node | ||
path = '' | ||
continue | ||
paramEndIndex = pathLen | ||
} | ||
@@ -554,16 +525,10 @@ | ||
if (kind === NODE_TYPES.REGEX) { | ||
currentNode = node | ||
i = path.indexOf('/') | ||
if (i === -1) i = pathLen | ||
if (i > maxParamLength) return null | ||
decoded = sanitizedUrl.sliceParameter(idxInOriginalPath, idxInOriginalPath + i) | ||
if (decoded === null) { | ||
return this._onBadUrl(originalPath.slice(idxInOriginalPath, idxInOriginalPath + i)) | ||
for (; paramEndIndex < pathLen; paramEndIndex++) { | ||
if (path.charCodeAt(paramEndIndex) === 47) { | ||
break | ||
} | ||
} | ||
if (!node.regex.test(decoded)) return null | ||
params || (params = []) | ||
params[pindex++] = decoded | ||
path = path.slice(i) | ||
idxInOriginalPath += i | ||
continue | ||
if (!node.regex.test(path.slice(pathIndex, paramEndIndex))) { | ||
return null | ||
} | ||
} | ||
@@ -573,56 +538,28 @@ | ||
if (kind === NODE_TYPES.MULTI_PARAM) { | ||
currentNode = node | ||
i = 0 | ||
if (node.regex !== null) { | ||
var matchedParameter = path.match(node.regex) | ||
const matchedParameter = node.regex.exec(path.slice(pathIndex)) | ||
if (matchedParameter === null) return null | ||
i = matchedParameter[1].length | ||
paramEndIndex = pathIndex + matchedParameter[1].length | ||
} else { | ||
while (i < pathLen && path.charCodeAt(i) !== 47 && path.charCodeAt(i) !== 45 && path.charCodeAt(i) !== 46) i++ | ||
if (i > maxParamLength) return null | ||
for (; paramEndIndex < pathLen; paramEndIndex++) { | ||
const charCode = path.charCodeAt(paramEndIndex) | ||
if (charCode === 47 || charCode === 45 || charCode === 46) { | ||
break | ||
} | ||
} | ||
} | ||
decoded = sanitizedUrl.sliceParameter(idxInOriginalPath, idxInOriginalPath + i) | ||
if (decoded === null) { | ||
return this._onBadUrl(originalPath.slice(idxInOriginalPath, idxInOriginalPath + i)) | ||
} | ||
params || (params = []) | ||
params[pindex++] = decoded | ||
path = path.slice(i) | ||
idxInOriginalPath += i | ||
continue | ||
} | ||
wildcardNode = null | ||
} | ||
} | ||
if (paramEndIndex > pathIndex + maxParamLength) { | ||
return null | ||
} | ||
Router.prototype._getWildcardNode = function (node, sanitizedUrl, len, derivedConstraints, params) { | ||
if (node === null) return null | ||
var decoded = sanitizedUrl.slice(-len) | ||
if (decoded === null) { | ||
return this._onBadUrl(sanitizedUrl.slice(-len)) | ||
} | ||
var handle = derivedConstraints !== undefined ? node.getMatchingHandler(derivedConstraints) : node.unconstrainedHandler | ||
if (handle !== null && handle !== undefined) { | ||
var paramsObj = {} | ||
if (handle.paramsLength > 0 && params !== null) { | ||
var paramNames = handle.params | ||
for (i = 0; i < handle.paramsLength; i++) { | ||
paramsObj[paramNames[i]] = params[i] | ||
} | ||
const decoded = sanitizedUrl.sliceParameter(pathIndex, paramEndIndex) | ||
if (decoded === null) { | ||
return this._onBadUrl(path.slice(pathIndex, paramEndIndex)) | ||
} | ||
// we must override params[*] to decoded | ||
paramsObj['*'] = decoded | ||
return { | ||
handler: handle.handler, | ||
params: paramsObj, | ||
store: handle.store | ||
} | ||
params.push(decoded) | ||
pathIndex = paramEndIndex | ||
} | ||
return null | ||
} | ||
@@ -629,0 +566,0 @@ |
{ | ||
"name": "find-my-way", | ||
"version": "5.1.1", | ||
"version": "5.2.0", | ||
"description": "Crazy fast http radix based router", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
@@ -578,1 +578,27 @@ 'use strict' | ||
}) | ||
test('Wildcard node with constraints', t => { | ||
t.plan(1) | ||
const findMyWay = FindMyWay({ | ||
defaultRoute: (req, res) => { | ||
t.fail('we should not be here, the url is: ' + req.url) | ||
} | ||
}) | ||
findMyWay.on('GET', '*', { constraints: { host: 'fastify.io' } }, (req, res, params) => { | ||
t.equal(params['*'], '/foo1/foo3') | ||
}) | ||
findMyWay.on('GET', '/foo1/*', { constraints: { host: 'something-else.io' } }, (req, res, params) => { | ||
t.fail('we should not be here, the url is: ' + req.url) | ||
}) | ||
findMyWay.on('GET', '/foo1/foo2', (req, res, params) => { | ||
t.fail('we should not be here, the url is: ' + req.url) | ||
}) | ||
findMyWay.lookup( | ||
{ method: 'GET', url: '/foo1/foo3', headers: { host: 'fastify.io' } }, | ||
null | ||
) | ||
}) |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
268174
66
6871