http-hash-router 模块基本使用方法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 var http = require ('http' );var HttpHashRouter = require ('http-hash-router' );var router = HttpHashRouter();router.set('/health' , function health (req, res ) { res.end('OK' ); }); var server = http.createServer(function handler (req, res ) { router(req, res, {}, onError); function onError (err ) { if (err) { res.statusCode = err.statusCode || 500 ; res.end(err.message); } } }); server.listen(3000 );
http-hash HttpHash对象维护这样一个routerNode树,结点结构定义如下:1 2 3 4 5 6 7 8 9 function RouteNode(parent, segment, isSplat) { this .parent = parent || null ; this .segment = segment || null ; this .handler = null ; this .staticPaths = {}; this .variablePaths = null ; this .isSplat = !!isSplat; this .src = null ; }
并提供了set,get两个方法
router.set(pathname, handler), 根据pathname, 遍历每层路径元素,不断在hash中插入路由结点
router.get(pathname), 同样通过可变路径优先找到访问的路径,调用相应结点的handler
set(pathname, handler) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 function set (pathname, handler) { var pathSegments = pathname.split('/' ); var hash = this ._hash; var lastIndex = pathSegments.length - 1 ; var splatIndex = pathname.indexOf('*' ); var hasSplat = splatIndex >= 0 ; if (hasSplat && splatIndex !== pathname.length - 1 ) { throw SplatError(pathname); } for (var i = 0 ; i < pathSegments.length; i++) { var segment = pathSegments[i]; if (!segment) { continue ; } if (hasSplat && i === lastIndex) { hash = ( hash.variablePaths || (hash.variablePaths = new RouteNode (hash, segment, true )) ); if (!hash.isSplat) { throw RouteConflictError(pathname, hash); } } else if (segment.indexOf(':' ) === 0 ) { segment = segment.slice(1 ); hash = ( hash.variablePaths || (hash.variablePaths = new RouteNode (hash, segment)) ); if (hash.segment !== segment || hash.isSplat) { throw RouteConflictError(pathname, hash); } } else if (segment === '__proto__' ) { hash = ( ( hash.hasOwnProperty('proto' ) && hash.proto ) || (hash.proto = new RouteNode (hash, segment)) ); } else { hash = ( ( hash.staticPaths.hasOwnProperty(segment) && hash.staticPaths[segment] ) || (hash.staticPaths[segment] = new RouteNode (hash, segment)) ); } } if (!hash.handler) { hash.src = pathname; hash.handler = handler; } else { throwRouteConflictError(pathname, hash); } }
插入结点逻辑:
将整个路径以/分割成一个数组,遍历每层路径元素,通过判断*和:[key]插入可变路径结点,否则插入静态路径结点
同一层路径结点的静态路径结点,即兄弟结点可以有多个
同一层路径结点的可变路径结点只有一个
抛出冲突异常的几个地方
重复set了相同的路径
同一层路径下set了两个不一样的通配符(如:一个是*
,一个是:[key]
),避免同时出现/api/*
和 /api/:name
get(pathname) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 function get (pathname ) { var pathSegments = pathname.split('/' ); var hash = this ._hash; var splat = null ; var params = {}; var variablePaths; for (var i = 0 ; i < pathSegments.length; i++) { var segment = pathSegments[i]; if (!segment && !hash.isSplat) { continue ; } else if ( segment === '__proto__' && hash.hasOwnProperty('proto' ) ) { hash = hash.proto; } else if (hash.staticPaths.hasOwnProperty(segment)) { hash = hash.staticPaths[segment]; } else if ((variablePaths = hash.variablePaths)) { if (variablePaths.isSplat) { splat = pathSegments.slice(i).join ('/' ); hash = variablePaths; break ; } else { params [variablePaths.segment] = segment; hash = variablePaths; } } else { hash = null ; break ; } } return new RouteResult(hash, params , splat); }
查找逻辑:
优先从静态路径中查找,__proto__ > staticPaths > variablePaths
因此可以通过设置静态路由覆盖可变路径,如 api/review
覆盖api/:name
splat保存指代*
的路径内容,params保存:[key]
的路径内容
总结 通过对http-hash-router源码阅读,以后碰到类似路径冲突的异常,能快速定位到问题,在set路由也会更留心,避免此类错误发生。此外,有了对http-hash-router在查找路由过程,优先于先找静态路径的认识,在解决路由冲突的问题上,提供了新的解决思路:通过set静态路径覆盖可变路径的方案