@zenweb/router
Version:
Zenweb Router module
297 lines (296 loc) • 9.82 kB
JavaScript
"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;