find-my-way
Version:
Crazy fast http radix based router
229 lines (185 loc) • 5.58 kB
JavaScript
'use strict'
const HandlerStorage = require('./handler-storage')
const NODE_TYPES = {
STATIC: 0,
PARAMETRIC: 1,
WILDCARD: 2
}
class Node {
constructor () {
this.isLeafNode = false
this.routes = null
this.handlerStorage = null
}
addRoute (route, constrainer) {
if (this.routes === null) {
this.routes = []
}
if (this.handlerStorage === null) {
this.handlerStorage = new HandlerStorage()
}
this.isLeafNode = true
this.routes.push(route)
this.handlerStorage.addHandler(constrainer, route)
}
}
class ParentNode extends Node {
constructor () {
super()
this.staticChildren = {}
}
findStaticMatchingChild (path, pathIndex) {
const staticChild = this.staticChildren[path.charAt(pathIndex)]
if (staticChild === undefined || !staticChild.matchPrefix(path, pathIndex)) {
return null
}
return staticChild
}
getStaticChild (path, pathIndex = 0) {
if (path.length === pathIndex) {
return this
}
const staticChild = this.findStaticMatchingChild(path, pathIndex)
if (staticChild) {
return staticChild.getStaticChild(path, pathIndex + staticChild.prefix.length)
}
return null
}
createStaticChild (path) {
if (path.length === 0) {
return this
}
let staticChild = this.staticChildren[path.charAt(0)]
if (staticChild) {
let i = 1
for (; i < staticChild.prefix.length; i++) {
if (path.charCodeAt(i) !== staticChild.prefix.charCodeAt(i)) {
staticChild = staticChild.split(this, i)
break
}
}
return staticChild.createStaticChild(path.slice(i))
}
const label = path.charAt(0)
this.staticChildren[label] = new StaticNode(path)
return this.staticChildren[label]
}
}
class StaticNode extends ParentNode {
constructor (prefix) {
super()
this.prefix = prefix
this.wildcardChild = null
this.parametricChildren = []
this.kind = NODE_TYPES.STATIC
this._compilePrefixMatch()
}
getParametricChild (regex) {
const regexpSource = regex && regex.source
const parametricChild = this.parametricChildren.find(child => {
const childRegexSource = child.regex && child.regex.source
return childRegexSource === regexpSource
})
if (parametricChild) {
return parametricChild
}
return null
}
createParametricChild (regex, staticSuffix, nodePath) {
let parametricChild = this.getParametricChild(regex)
if (parametricChild) {
parametricChild.nodePaths.add(nodePath)
return parametricChild
}
parametricChild = new ParametricNode(regex, staticSuffix, nodePath)
this.parametricChildren.push(parametricChild)
this.parametricChildren.sort((child1, child2) => {
if (!child1.isRegex) return 1
if (!child2.isRegex) return -1
if (child1.staticSuffix === null) return 1
if (child2.staticSuffix === null) return -1
if (child2.staticSuffix.endsWith(child1.staticSuffix)) return 1
if (child1.staticSuffix.endsWith(child2.staticSuffix)) return -1
return 0
})
return parametricChild
}
getWildcardChild () {
return this.wildcardChild
}
createWildcardChild () {
this.wildcardChild = this.getWildcardChild() || new WildcardNode()
return this.wildcardChild
}
split (parentNode, length) {
const parentPrefix = this.prefix.slice(0, length)
const childPrefix = this.prefix.slice(length)
this.prefix = childPrefix
this._compilePrefixMatch()
const staticNode = new StaticNode(parentPrefix)
staticNode.staticChildren[childPrefix.charAt(0)] = this
parentNode.staticChildren[parentPrefix.charAt(0)] = staticNode
return staticNode
}
getNextNode (path, pathIndex, nodeStack, paramsCount) {
let node = this.findStaticMatchingChild(path, pathIndex)
let parametricBrotherNodeIndex = 0
if (node === null) {
if (this.parametricChildren.length === 0) {
return this.wildcardChild
}
node = this.parametricChildren[0]
parametricBrotherNodeIndex = 1
}
if (this.wildcardChild !== null) {
nodeStack.push({
paramsCount,
brotherPathIndex: pathIndex,
brotherNode: this.wildcardChild
})
}
for (let i = this.parametricChildren.length - 1; i >= parametricBrotherNodeIndex; i--) {
nodeStack.push({
paramsCount,
brotherPathIndex: pathIndex,
brotherNode: this.parametricChildren[i]
})
}
return node
}
_compilePrefixMatch () {
if (this.prefix.length === 1) {
this.matchPrefix = () => true
return
}
const lines = []
for (let i = 1; i < this.prefix.length; i++) {
const charCode = this.prefix.charCodeAt(i)
lines.push(`path.charCodeAt(i + ${i}) === ${charCode}`)
}
this.matchPrefix = new Function('path', 'i', `return ${lines.join(' && ')}`) // eslint-disable-line
}
}
class ParametricNode extends ParentNode {
constructor (regex, staticSuffix, nodePath) {
super()
this.isRegex = !!regex
this.regex = regex || null
this.staticSuffix = staticSuffix || null
this.kind = NODE_TYPES.PARAMETRIC
this.nodePaths = new Set([nodePath])
}
getNextNode (path, pathIndex) {
return this.findStaticMatchingChild(path, pathIndex)
}
}
class WildcardNode extends Node {
constructor () {
super()
this.kind = NODE_TYPES.WILDCARD
}
getNextNode () {
return null
}
}
module.exports = { StaticNode, ParametricNode, WildcardNode, NODE_TYPES }