From 685205adde2f49a255e4b574f3222721ef665053 Mon Sep 17 00:00:00 2001 From: Tobe O Date: Sun, 26 Nov 2017 00:33:17 -0500 Subject: [PATCH] Optimizations + caching --- lib/src/route.dart | 3 +- lib/src/router.dart | 76 +++++++++++++++++++++++++++++++++++---------- pubspec.yaml | 2 +- 3 files changed, 63 insertions(+), 18 deletions(-) diff --git a/lib/src/route.dart b/lib/src/route.dart index fc43da91..0e45e32e 100644 --- a/lib/src/route.dart +++ b/lib/src/route.dart @@ -39,6 +39,7 @@ String _pathify(String path) { /// Represents a virtual location within an application. class Route { + final Map _cache = {}; final List _children = []; final List _handlers = []; RegExp _head; @@ -329,7 +330,7 @@ class Route { /// Attempts to match a path against this route. Match match(String path) => - matcher.firstMatch(path.replaceAll(_straySlashes, '')); + _cache.putIfAbsent(path, () => matcher.firstMatch(path.replaceAll(_straySlashes, ''))); /// Extracts route parameters from a given path. Map parseParameters(String requestPath) { diff --git a/lib/src/router.dart b/lib/src/router.dart index d3f3e31f..d1bf3a47 100644 --- a/lib/src/router.dart +++ b/lib/src/router.dart @@ -16,10 +16,12 @@ final RegExp _straySlashes = new RegExp(r'(^/+)|(/+$)'); /// An abstraction over complex [Route] trees. Use this instead of the raw API. :) class Router { - final List<_ChainedRouter> _chained = []; + final Map> _cache = {}; + //final List<_ChainedRouter> _chained = []; final List _middleware = []; final Map _mounted = {}; final List _routes = []; + bool _useCache = false; /// Set to `true` to print verbose debug output when interacting with this route. bool debug = false; @@ -53,11 +55,19 @@ class Router { /// Not recommended. Router({this.debug: false}); + /// Enables the use of a cache to eliminate the overhead of consecutive resolutions of the same path. + void enableCache() { + _useCache = true; + } + /// Adds a route that responds to the given path /// for requests with the given method (case-insensitive). /// Provide '*' as the method to respond to all methods. Route addRoute(String method, Pattern path, Object handler, {List middleware: const []}) { + if (_useCache == true) + throw new StateError('Cannot add routes after caching is enabled.'); + // Check if any mounted routers can match this final handlers = [handler]; @@ -258,26 +268,22 @@ class Router { requestMiddleware[name] = middleware; } - RoutingResult _dumpResult(String path, RoutingResult result) { - // _printDebug('Resolved "/$path" to ${result.route}'); - return result; - } - /// Finds the first [Route] that matches the given path, /// with the given method. bool resolve(String absolute, String relative, List out, {String method: 'GET', bool strip: true}) { - final cleanAbsolute = - strip == false ? absolute : stripStraySlashes(absolute); + //final cleanAbsolute = + // strip == false ? absolute : stripStraySlashes(absolute); final cleanRelative = strip == false ? relative : stripStraySlashes(relative); - final segments = cleanRelative.split('/').where((str) => str.isNotEmpty); + //final segments = cleanRelative.split('/').where((str) => str.isNotEmpty); bool success = false; //print( // 'Now resolving $method "/$cleanRelative", absolute: $cleanAbsolute'); //print('Path segments: ${segments.toList()}'); for (Route route in routes) { + /* // No longer necessary if (route is SymlinkRoute && route._head != null && segments.isNotEmpty) { final s = []; @@ -313,18 +319,17 @@ class Router { } } } + */ if (route.method == '*' || route.method == method) { final match = route.match(cleanRelative); if (match != null) { - var result = _dumpResult( - cleanRelative, - new RoutingResult( - match: match, - params: route.parseParameters(cleanRelative), - shallowRoute: route, - shallowRouter: this)); + var result = new RoutingResult( + match: match, + params: route.parseParameters(cleanRelative), + shallowRoute: route, + shallowRouter: this); out.add(result); success = true; } @@ -345,6 +350,16 @@ class Router { /// with the given method. Iterable resolveAll(String absolute, String relative, {String method: 'GET', bool strip: true}) { + if (_useCache == true) { + return _cache.putIfAbsent(absolute, + () => _resolveAll(absolute, relative, method: method, strip: strip)); + } + + return _resolveAll(absolute, relative, method: method, strip: strip); + } + + Iterable _resolveAll(String absolute, String relative, + {String method: 'GET', bool strip: true}) { final List results = []; resolve(absolute, relative, results, method: method, strip: strip); @@ -475,3 +490,32 @@ class _ChainedRouter extends Router { return piped; } } + +/// Optimizes a router by condensing all its routes into one level. +Router flatten(Router router) { + var flattened = new Router(debug: router.debug == true) + ..requestMiddleware.addAll(router.requestMiddleware) + ..enableCache(); + + for (var route in router.routes) { + if (route is SymlinkRoute) { + var base = route.path.replaceAll(_straySlashes, ''); + var child = flatten(route.router); + flattened.requestMiddleware.addAll(child.requestMiddleware); + + for (var route in child.routes) { + var path = route.path.replaceAll(_straySlashes, ''); + var joined = '$base/$path'.replaceAll(_straySlashes, ''); + flattened.addRoute(route.method, joined.replaceAll(_straySlashes, ''), + route.handlers.last, + middleware: + route.handlers.take(route.handlers.length - 1).toList()); + } + } else { + flattened.addRoute(route.method, route.path, route.handlers.last, + middleware: route.handlers.take(route.handlers.length - 1).toList()); + } + } + + return flattened; +} diff --git a/pubspec.yaml b/pubspec.yaml index 6e73e1a1..e8212fa9 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: angel_route description: A powerful, isomorphic routing library for Dart. -version: 2.0.0 +version: 2.0.2 author: Tobe O homepage: https://github.com/angel-dart/angel_route dev_dependencies: