| 1 | function compile(pattern) { |
| 2 | const params = []; |
| 3 | let re = '^'; |
| 4 | let i = 0; |
| 5 | while (i < pattern.length) { |
| 6 | const c = pattern[i]; |
| 7 | if (c === ':') { |
| 8 | let end = i + 1; |
| 9 | while (end < pattern.length && /[a-zA-Z0-9_]/.test(pattern[end])) end++; |
| 10 | params.push(pattern.slice(i + 1, end)); |
| 11 | re += '([^/]+)'; |
| 12 | i = end; |
| 13 | } else if (c === '*') { |
| 14 | params.push('rest'); |
| 15 | re += '(.*)'; |
| 16 | i += 1; |
| 17 | } else { |
| 18 | re += c.replace(/[.+?^${}()|[\]\\]/g, '\\$&'); |
| 19 | i += 1; |
| 20 | } |
| 21 | } |
| 22 | re += '$'; |
| 23 | return { regex: new RegExp(re), params }; |
| 24 | } |
| 25 | |
| 26 | class Router { |
| 27 | constructor() { this.routes = []; } |
| 28 | add(method, pattern, handler) { |
| 29 | const { regex, params } = compile(pattern); |
| 30 | this.routes.push({ |
| 31 | method: method.toUpperCase(), |
| 32 | pattern, regex, params, handler |
| 33 | }); |
| 34 | } |
| 35 | get(p, h) { this.add('GET', p, h); } |
| 36 | post(p, h) { this.add('POST', p, h); } |
| 37 | put(p, h) { this.add('PUT', p, h); } |
| 38 | delete(p, h) { this.add('DELETE', p, h); } |
| 39 | any(method, p, h) { this.add(method, p, h); } |
| 40 | match(method, urlPath) { |
| 41 | const m = method.toUpperCase(); |
| 42 | for (const r of this.routes) { |
| 43 | if (r.method !== m && r.method !== 'ANY') continue; |
| 44 | const found = r.regex.exec(urlPath); |
| 45 | if (!found) continue; |
| 46 | const params = {}; |
| 47 | for (let i = 0; i < r.params.length; i++) { |
| 48 | params[r.params[i]] = decodeURIComponent(found[i + 1]); |
| 49 | } |
| 50 | return { handler: r.handler, params }; |
| 51 | } |
| 52 | return null; |
| 53 | } |
| 54 | } |
| 55 | |
| 56 | module.exports = { Router, compile }; |
| 57 | |