trek-router
Version:
A fast HTTP router
266 lines (224 loc) • 5.95 kB
JavaScript
/*!
* router
* Copyright(c) 2015-2017 Fangdun Cai
* MIT Licensed
*/
'use strict'
// Static Param Any `*` `/` `:`
const [SKIND, PKIND, AKIND, STAR, SLASH, COLON] = [0, 1, 2, 42, 47, 58]
class Node {
constructor(prefix = '/', children = [], kind = SKIND, map = Object.create(null)) {
this.label = prefix.charCodeAt(0)
this.prefix = prefix
this.children = children
this.kind = kind
this.map = map
}
addChild(n) {
this.children.push(n)
}
findChild(c, t, l, e, i = 0) {
for (l = this.children.length; i < l; i++) {
e = this.children[i]
if (c === e.label && t === e.kind) {
return e
}
}
}
findChildWithLabel(c, l, e, i = 0) {
for (l = this.children.length; i < l; i++) {
e = this.children[i]
if (c === e.label) {
return e
}
}
}
findChildByKind(t, l, e, i = 0) {
for (l = this.children.length; i < l; i++) {
e = this.children[i]
if (t === e.kind) {
return e
}
}
}
addHandler(method, handler, pnames) {
this.map[method] = {handler, pnames}
}
findHandler(method) {
return this.map[method]
}
}
class Router {
constructor() {
this.tree = new Node()
}
add(method, path, handler) {
// Pnames: Param names
let [i, l, pnames, ch, j] = [0, path.length, []]
for (; i < l; ++i) {
ch = path.charCodeAt(i)
if (ch === COLON) {
j = i + 1
this.insert(method, path.substring(0, i), SKIND)
while (i < l && path.charCodeAt(i) !== SLASH) {
i++
}
pnames.push(path.substring(j, i))
path = path.substring(0, j) + path.substring(i)
i = j
l = path.length
if (i === l) {
this.insert(method, path.substring(0, i), PKIND, pnames, handler)
return
}
this.insert(method, path.substring(0, i), PKIND, pnames)
} else if (ch === STAR) {
this.insert(method, path.substring(0, i), SKIND)
pnames.push('*')
this.insert(method, path.substring(0, l), AKIND, pnames, handler)
return
}
}
this.insert(method, path, SKIND, pnames, handler)
}
insert(method, path, t, pnames, handler) {
// Current node as root
let [cn, prefix, sl, pl, l, max, n, c] = [this.tree]
while (true) {
prefix = cn.prefix
sl = path.length
pl = prefix.length
l = 0
// LCP
max = sl < pl ? sl : pl
while (l < max && path.charCodeAt(l) === prefix.charCodeAt(l)) {
l++
}
/*
If (l === 0) {
// At root node
cn.label = search.charCodeAt(0)
cn.prefix = search
if (handler !== undefined) {
cn.addHandler(method, { pnames, handler })
}
} else if (l < pl) {
*/
if (l < pl) {
// Split node
n = new Node(prefix.substring(l), cn.children, cn.kind, cn.map)
cn.children = [n] // Add to parent
// Reset parent node
cn.label = prefix.charCodeAt(0)
cn.prefix = prefix.substring(0, l)
cn.map = Object.create(null)
cn.kind = SKIND
if (l === sl) {
// At parent node
cn.addHandler(method, handler, pnames)
cn.kind = t
} else {
// Create child node
n = new Node(path.substring(l), [], t)
n.addHandler(method, handler, pnames)
cn.addChild(n)
}
} else if (l < sl) {
path = path.substring(l)
c = cn.findChildWithLabel(path.charCodeAt(0))
if (c !== undefined) {
// Go deeper
cn = c
continue
}
// Create child node
n = new Node(path, [], t)
n.addHandler(method, handler, pnames)
cn.addChild(n)
} else if (handler !== undefined) {
// Node already exists
cn.addHandler(method, handler, pnames)
}
return
}
}
find(method, path, cn, n = 0, result = [undefined, []]) {
cn = cn || this.tree // Current node as root
const sl = path.length
const prefix = cn.prefix
const pvalues = result[1] // Params
let i, pl, l, max, c
let preSearch // Pre search
// Search order static > param > match-any
if (sl === 0 || path === prefix) {
// Found
const r = cn.findHandler(method)
if ((result[0] = r && r.handler) !== undefined) {
const pnames = r.pnames
if (pnames !== undefined) {
for (i = 0, l = pnames.length; i < l; ++i) {
pvalues[i] = {
name: pnames[i],
value: pvalues[i]
}
}
}
}
return result
}
pl = prefix.length
l = 0
// LCP
max = sl < pl ? sl : pl
while (l < max && path.charCodeAt(l) === prefix.charCodeAt(l)) {
l++
}
if (l === pl) {
path = path.substring(l)
}
preSearch = path
// Static node
c = cn.findChild(path.charCodeAt(0), SKIND)
if (c !== undefined) {
this.find(method, path, c, n, result)
if (result[0] !== undefined) {
return result
}
path = preSearch
}
// Not found node
if (l !== pl) {
return result
}
// Param node
c = cn.findChildByKind(PKIND)
if (c !== undefined) {
l = path.length
i = 0
while (i < l && path.charCodeAt(i) !== SLASH) {
i++
}
pvalues[n] = path.substring(0, i)
n++
preSearch = path
path = path.substring(i)
this.find(method, path, c, n, result)
if (result[0] !== undefined) {
return result
}
n--
pvalues.pop()
path = preSearch
}
// Any node
c = cn.findChildByKind(AKIND)
if (c !== undefined) {
pvalues[n] = path
path = '' // End search
this.find(method, path, c, n, result)
}
return result
}
}
Router.Node = Node
module.exports = Router