Skip to content

Commit

Permalink
Merge pull request #34 from delvedor/find-refactor
Browse files Browse the repository at this point in the history
Find refactor
  • Loading branch information
delvedor authored Oct 12, 2017
2 parents c378eb5 + ba5fe5f commit c116ccf
Show file tree
Hide file tree
Showing 5 changed files with 608 additions and 60 deletions.
31 changes: 31 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,37 @@ In this case as parameter separator it's possible to use whatever character is n

Having a route with multiple parameters may affect negatively the performance, so prefer single parameter approach whenever possible, especially on routes which are on the hot path of your application.

<a name="match-order"></a>
##### Match order
The routes are matched in the following order:
```
static
parametric
wildcards
parametric(regex)
multi parametric(regex)
```
This means that if you register two routes, the first static and the second dynamic with a shared part of the path, the static route will always take precedence.
For example:
```js
const assert = require('assert')
const router = require('find-my-way')({
defaultRoute: (req, res) => {
assert(req.url === '/example/shared/nested/oops')
}
})

router.on('GET', '/example/shared/nested/test', (req, res, params) => {
assert.fail('We should not be here')
})

router.on('GET', '/example/:param/nested/oops', (req, res, params) => {
assert.fail('We should not be here')
})

router.lookup({ method: 'GET', url: '/example/shared/nested/oops' }, null)
```

<a name="shorthand-methods"></a>
##### Shorthand methods
If you want an even nicer api, you can also use the shorthand methods to declare your routes.
Expand Down
95 changes: 62 additions & 33 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ function Router (opts) {
this.tree = new Node()
}

Router.prototype.on = function (method, path, handler, store) {
Router.prototype.on = function on (method, path, handler, store) {
if (Array.isArray(method)) {
for (var k = 0; k < method.length; k++) {
this.on(method[k], path, handler, store)
Expand All @@ -64,7 +64,7 @@ Router.prototype.on = function (method, path, handler, store) {
if (path.charCodeAt(i) === 58) {
var nodeType = 1
j = i + 1
this._insert(method, path.slice(0, i), 0, null, null, null)
this._insert(method, path.slice(0, i), 0, null, null, null, null)

// isolate the parameter name
var isRegex = false
Expand Down Expand Up @@ -104,23 +104,23 @@ Router.prototype.on = function (method, path, handler, store) {
i--
// wildcard route
} else if (path.charCodeAt(i) === 42) {
this._insert(method, path.slice(0, i), 0, null, null, null)
this._insert(method, path.slice(0, i), 0, null, null, null, null)
params.push('*')
return this._insert(method, path.slice(0, len), 2, params, handler, store)
return this._insert(method, path.slice(0, len), 2, params, handler, store, null)
}
}
// static route
this._insert(method, path, 0, params, handler, store)
this._insert(method, path, 0, params, handler, store, null)
}

Router.prototype._insert = function (method, path, kind, params, handler, store, regex) {
Router.prototype._insert = function _insert (method, path, kind, params, handler, store, regex) {
var currentNode = this.tree
var prefix = ''
var pathLen = 0
var prefixLen = 0
var len = 0
var max = 0
var node = null
var currentNode = this.tree

while (true) {
prefix = currentNode.prefix
Expand All @@ -134,16 +134,20 @@ Router.prototype._insert = function (method, path, kind, params, handler, store,

if (len < prefixLen) {
// split the node in the radix tree and add it to the parent
node = new Node(prefix.slice(len), currentNode.children, currentNode.kind, currentNode.map, currentNode.regex)
node = new Node(prefix.slice(len), currentNode.children, currentNode.kind, Object.assign({}, currentNode.map), currentNode.regex)
if (currentNode.wildcardChild !== null) {
node.wildcardChild = currentNode.wildcardChild
}

// reset the parent
currentNode.children = [node]
currentNode.numberOfChildren = 1
currentNode.prefix = prefix.slice(0, len)
currentNode.label = currentNode.prefix[0]
currentNode.map = null
currentNode.map = {}
currentNode.kind = 0
currentNode.regex = null
currentNode.wildcardChild = null

if (len === pathLen) {
// add the handler to the parent node
Expand Down Expand Up @@ -179,45 +183,43 @@ Router.prototype._insert = function (method, path, kind, params, handler, store,
}
}

Router.prototype.lookup = function (req, res) {
Router.prototype.lookup = function lookup (req, res) {
var handle = this.find(req.method, sanitizeUrl(req.url))
if (!handle) return this._defaultRoute(req, res)
return handle.handler(req, res, handle.params, handle.store)
}

Router.prototype.find = function (method, path) {
Router.prototype.find = function find (method, path) {
var currentNode = this.tree
var node = null
var kind = 0
var wildcardNode = null
var pathLenWildcard = 0
var originalPath = path
var decoded = null
var pindex = 0
var params = []
var pathLen = 0
var prefix = ''
var prefixLen = 0
var len = 0
var i = 0

while (true) {
pathLen = path.length
prefix = currentNode.prefix
prefixLen = prefix.length
len = 0
var pathLen = path.length
var prefix = currentNode.prefix
var prefixLen = prefix.length
var len = 0

// found the route
if (pathLen === 0 || path === prefix) {
var handle = currentNode.getHandler(method)
if (handle) {
var paramNames = handle.params
var paramsObj = {}
if (handle !== undefined) {
if (handle.paramsLength > 0) {
var paramNames = handle.params

for (i = 0; i < paramNames.length; i++) {
paramsObj[paramNames[i]] = params[i]
for (i = 0; i < handle.paramsLength; i++) {
handle.paramsObj[paramNames[i]] = params[i]
}
}

return {
handler: handle.handler,
params: paramsObj,
params: handle.paramsObj,
store: handle.store
}
}
Expand All @@ -232,17 +234,27 @@ Router.prototype.find = function (method, path) {
pathLen = path.length
}

node = currentNode.find(path[0], method)
if (!node) return null
kind = node.kind
// if exist, save the wildcard child
if (currentNode.wildcardChild !== null) {
wildcardNode = currentNode.wildcardChild
pathLenWildcard = pathLen
}

var node = currentNode.find(path[0], method)
if (node === null) {
return getWildcardNode(wildcardNode, method, originalPath, pathLenWildcard)
}
var kind = node.kind

// static route
if (kind === 0) {
currentNode = node
continue
}

if (len !== prefixLen) return null
if (len !== prefixLen) {
return getWildcardNode(wildcardNode, method, originalPath, pathLenWildcard)
}

// parametric route
if (kind === 1) {
Expand Down Expand Up @@ -279,7 +291,7 @@ Router.prototype.find = function (method, path) {
if (errored) {
return null
}
if (!node.regex.test(decoded)) return
if (!node.regex.test(decoded)) return null
params[pindex++] = decoded
path = path.slice(i)
continue
Expand All @@ -291,7 +303,7 @@ Router.prototype.find = function (method, path) {
i = 0
if (node.regex) {
var matchedParameter = path.match(node.regex)
if (!matchedParameter) return
if (!matchedParameter) return null
i = matchedParameter[1].length
} else {
while (i < pathLen && path.charCodeAt(i) !== 47 && path.charCodeAt(i) !== 45) i++
Expand Down Expand Up @@ -380,3 +392,20 @@ function fastDecode (path) {
errored = true
}
}

function getWildcardNode (node, method, path, len) {
if (node === null) return null
var decoded = fastDecode(path.slice(-len))
if (errored) {
return null
}
var handle = node.getHandler(method)
if (handle !== undefined) {
return {
handler: handle.handler,
params: { '*': decoded },
store: handle.store
}
}
return null
}
46 changes: 19 additions & 27 deletions node.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
param: 1,
matchAll: 2,
regex: 3
multi-param: 4
It's used for a parameter, that is followed by another parameter in the same part
*/

function Node (prefix, children, kind, map, regex) {
Expand All @@ -14,21 +16,17 @@ function Node (prefix, children, kind, map, regex) {
this.children = children || []
this.numberOfChildren = this.children.length
this.kind = kind || 0
this.map = map || null
this.map = map || {}
this.regex = regex || null
this.wildcardChild = null
}

Node.prototype.add = function (node) {
if (node.kind === 0) {
for (var i = 0; i < this.numberOfChildren; i++) {
if (this.children[i].kind > 0) {
this.children.splice(i, 0, node)
this.numberOfChildren++
return
}
}
if (node.kind === 2) {
this.wildcardChild = node
}
this.children.push(node)
this.children.sort((n1, n2) => n1.kind - n2.kind)
this.numberOfChildren++
}

Expand All @@ -42,44 +40,38 @@ Node.prototype.findByLabel = function (label) {
return null
}

// Check in two different places the numberOfChildren and the map object
// gives us around ~5% more speed
Node.prototype.find = function (label, method) {
for (var i = 0; i < this.numberOfChildren; i++) {
var child = this.children[i]
if (child.numberOfChildren !== 0) {
if (child.label === label && child.kind === 0) {
return child
}
if (child.kind > 0) {
return child
}
}

if (child.map && child.map[method]) {
if (child.numberOfChildren !== 0 || child.map[method]) {
if (child.label === label && child.kind === 0) {
return child
}
if (child.kind > 0) {
return child
}
if (child.kind !== 0) return child
}
}
return null
}

Node.prototype.setHandler = function (method, handler, params, store) {
if (!handler) return
this.map = this.map || {}

var paramsObj = {}
for (var i = 0; i < params.length; i++) {
paramsObj[params[i]] = ''
}

this.map[method] = {
handler: handler,
params: params,
store: store || null
store: store || null,
paramsLength: params.length,
paramsObj: paramsObj
}
}

Node.prototype.getHandler = function (method) {
return this.map ? this.map[method] : null
return this.map[method]
}

Node.prototype.prettyPrint = function (prefix, tail) {
Expand Down
Loading

0 comments on commit c116ccf

Please sign in to comment.