UNPKG

@zenweb/router

Version:

Zenweb Router module

297 lines (296 loc) 9.82 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Router = exports.RouterDuplicated = void 0; const koa_compose_1 = __importDefault(require("koa-compose")); const param_1 = require("./param"); const utils_1 = require("./utils"); class RouterDuplicated extends Error { constructor(path, method) { super(`Router URI '${method} ${path}' duplicated`); this.path = path; this.method = method; } } exports.RouterDuplicated = RouterDuplicated; class Router { constructor(option) { this.option = option; /** * 数量统计 */ this._counts = { statics: 0, params: 0, wildcards: 0, regexs: 0, }; } _registerStatic(path, method, handler) { if (!this._staticPaths) { this._staticPaths = {}; } const key = `${method} ${path}`; if (key in this._staticPaths) { throw new RouterDuplicated(path, method); } this._staticPaths[key] = handler; this._counts.statics++; } _registerRegex(prefix, path, method, handler) { if (!this._regexPaths) { this._regexPaths = {}; } if (!(method in this._regexPaths)) { this._regexPaths[method] = []; } // 检查重复项 if (this._regexPaths[method].findIndex(i => i.regex.source === path.source) > -1) { throw new RouterDuplicated(path.source, method); } this._regexPaths[method].push({ prefix, regex: path, handler }); this._counts.regexs++; } _registerWildcard(path, method, handler) { if (!this._wildcardPaths) { this._wildcardPaths = {}; } if (!(method in this._wildcardPaths)) { this._wildcardPaths[method] = []; } const [prefix, suffix] = path.split('*', 2); // 检查重复项 if (this._wildcardPaths[method].findIndex(i => i.prefix === prefix && i.suffix === suffix) > -1) { throw new RouterDuplicated(path, method); } if (prefix && suffix) { // 优先两端匹配 this._wildcardPaths[method].unshift({ prefix, suffix, handler }); } else { this._wildcardPaths[method].push({ prefix, suffix, handler }); } this._counts.wildcards++; } /** * 通配符匹配 */ _matchByWildcard(path, method) { if (!this._wildcardPaths) return; const list = [...(this._wildcardPaths[method] || []), ...(this._wildcardPaths['ALL'] || [])]; for (const r of list) { if (path.startsWith(r.prefix) && path.endsWith(r.suffix)) { return { params: { wildcard: path.substring(r.prefix.length, path.length - r.suffix.length) }, handler: r.handler, }; } } } _registerParam(path, method, handler) { if (!this._paramPath) { this._paramPath = new param_1.ParamPath(); } this._paramPath.addRoute(`${method} ${path}`, handler); this._counts.params++; } /** * 使用中间件 * - 匹配的路由将使用中间件 */ use(...middlewares) { if (!this._middlewares) { this._middlewares = []; } this._middlewares.push(...middlewares); return this; } /** * 添加子路由 * - 子路由继承父路由的中间件 */ add(...routers) { if (!this._routers) { this._routers = []; } this._routers.push(...routers); return this; } /** * 注册路由 * - 静态路径 * - 参数化路径 * - 路径中包含 `:` * - 通配符路径 (两端匹配优先) * - 两端匹配: /prefix/*\/suffix * - 前缀匹配: /prefix/* * - 后缀匹配: *\/auffix * - 正则表达式路径 * - /\\/some\\/(\d+)/i */ register(opt) { for (let _path of Array.isArray(opt.path) ? opt.path : [opt.path]) { const middleware = Array.isArray(opt.middleware) ? (0, koa_compose_1.default)(opt.middleware) : opt.middleware; const methods = Array.isArray(opt.method) ? opt.method : [opt.method]; if (_path instanceof RegExp) { // 正则匹配路径 for (const m of methods) { this._registerRegex(opt.prefix, _path, m, middleware); } } else { if (opt.prefix) { _path = opt.prefix + _path; } if (_path.includes(':')) { // 解析参数化路径 /param/:id/sub/:subid for (const m of methods) { this._registerParam(_path, m, middleware); } } else if (_path.includes('*')) { // 通配符路径 for (const m of methods) { this._registerWildcard(_path, m, middleware); } } else { // 普通静态路径 for (const m of methods) { this._registerStatic(_path, m, middleware); } } } } return this; } all(path, ...middleware) { return this.register({ path, method: 'ALL', middleware }); } get(path, ...middleware) { return this.register({ path, method: 'GET', middleware }); } post(path, ...middleware) { return this.register({ path, method: 'POST', middleware }); } put(path, ...middleware) { return this.register({ path, method: 'PUT', middleware }); } patch(path, ...middleware) { return this.register({ path, method: 'PATCH', middleware }); } delete(path, ...middleware) { return this.register({ path, method: 'DELETE', middleware }); } /** * 静态匹配查找 */ _matchByStatic(path, method) { if (!this._staticPaths) return; const handler = this._staticPaths[`${method} ${path}`] || this._staticPaths[`ALL ${path}`]; //@ts-ignore if (handler) { return { params: {}, handler }; } } /** * 参数化路径匹配 */ _matchByParam(path, method) { if (!this._paramPath) return; return this._paramPath.match(`${method} ${path}`) || this._paramPath.match(`ALL ${path}`); } /** * 正则匹配查找 */ _matchByRegex(path, method) { if (!this._regexPaths) return; const list = [...(this._regexPaths[method] || []), ...(this._regexPaths['ALL'] || [])]; for (const r of list) { if (r.prefix) { if (!path.startsWith(r.prefix)) { continue; } path = path.substring(r.prefix.length); } const result = r.regex.exec(path); if (result) { return { params: (0, utils_1.regexResultToParams)(result), handler: r.handler }; } } } /** * 路径匹配查找 * - 匹配顺序: 静态 > 参数 > 通配符 > 正则 * - 方法顺序: 指定方法 > ALL */ match(path, method) { if (this.option?.prefix) { if (!path.startsWith(this.option.prefix)) { return; } let sublen = this.option.prefix.length; if (this.option.prefix.endsWith('/')) { // 保留前缀结尾 / 以匹配子路由 sublen--; } path = path.substring(sublen); } const m = method ? method.toUpperCase() : 'GET'; if (this._routers && this._routers.length > 0) { for (const r of this._routers) { const match = r.match(path, m); if (match) { if (this._middlewares) { return { params: match.params, handler: (0, koa_compose_1.default)([...this._middlewares, match.handler]) }; } return match; } } } return this.matchSelf(path, m); } /** * 匹配自身路由,忽略 prefix,不检查子路由 */ matchSelf(path, method) { return this._matchByStatic(path, method) || this._matchByParam(path, method) || this._matchByWildcard(path, method) || this._matchByRegex(path, method); } server() { const router = this; return function routerMatch(ctx, next) { const match = router.match(ctx.path, ctx.method); if (match) { ctx.params = match.params; return match.handler(ctx, next); } return next(); }; } getCount() { const out = Object.assign({}, this._counts, { subRouters: 0 }); if (this._routers && this._routers.length > 0) { for (const r of this._routers) { out.subRouters++; const sc = r.getCount(); out.statics += sc.statics; out.params += sc.params; out.wildcards += sc.wildcards; out.regexs += sc.regexs; out.subRouters += sc.subRouters; } } return out; } } exports.Router = Router;