From 774edb4be80fb8de6a7240f98fb6aeba49997594 Mon Sep 17 00:00:00 2001 From: thosakwe Date: Sun, 27 Nov 2016 19:49:27 -0500 Subject: [PATCH] New router... Again --- README.md | 2 +- lib/src/http/response_context.dart | 37 +++++++++++++++++------- lib/src/http/routable.dart | 5 ++-- lib/src/http/server.dart | 32 ++++++++++++--------- lib/src/http/service.dart | 2 -- pubspec.yaml | 2 +- test/controller_test.dart | 11 ++++--- test/hooked_test.dart | 4 +-- test/routing_test.dart | 46 ++++++++++++++---------------- 9 files changed, 78 insertions(+), 63 deletions(-) diff --git a/README.md b/README.md index 54d218a8..ca6e9370 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # angel_framework -![version 1.0.0-dev.25](https://img.shields.io/badge/version-1.0.0--dev.25-red.svg) +![version 1.0.0-dev.26](https://img.shields.io/badge/version-1.0.0--dev.26-red.svg) ![build status](https://travis-ci.org/angel-dart/framework.svg) Core libraries for the Angel Framework. \ No newline at end of file diff --git a/lib/src/http/response_context.dart b/lib/src/http/response_context.dart index fe175451..c3e870da 100644 --- a/lib/src/http/response_context.dart +++ b/lib/src/http/response_context.dart @@ -10,6 +10,8 @@ import '../extensible.dart'; import 'angel_base.dart'; import 'controller.dart'; +final RegExp _straySlashes = new RegExp(r'(^/+)|(/+$)'); + /// A convenience wrapper around an outgoing HTTP request. class ResponseContext extends Extensible { bool _isOpen = true; @@ -90,8 +92,15 @@ class ResponseContext extends Extensible { } /// Redirects to user to the given URL. - void redirect(String url, {int code: 301}) { - header(HttpHeaders.LOCATION, url); + /// + /// [url] can be a `String`, or a `List`. + /// If it is a `List`, a URI will be constructed + /// based on the provided params. + /// + /// See [Router]#navigate for more. :) + void redirect(url, {bool absolute: true, int code: 301}) { + header(HttpHeaders.LOCATION, + url is String ? url : app.navigate(url, absolute: absolute)); status(code ?? 301); write(''' @@ -115,18 +124,19 @@ class ResponseContext extends Extensible { /// Redirects to the given named [Route]. void redirectTo(String name, [Map params, int code]) { - _findRoute(Route route) { - for (Route child in route.children) { - final resolved = _findRoute(child); + Route _findRoute(Router r) { + for (Route route in r.routes) { + if (route is SymlinkRoute) { + final m = _findRoute(route.router); - if (resolved != null) return resolved; + if (m != null) return m; + } else if (route.name == name) return route; } - return route.children - .firstWhere((r) => r.name == name, orElse: () => null); + return null; } - Route matched = _findRoute(app.root); + Route matched = _findRoute(app); if (matched != null) { redirect(matched.makeUri(params), code: code); @@ -146,7 +156,8 @@ class ResponseContext extends Extensible { throw new Exception( "Controller redirects must take the form of 'Controller@action'. You gave: $action"); - Controller controller = app.controller(split[0]); + Controller controller = + app.controller(split[0].replaceAll(_straySlashes, '')); if (controller == null) throw new Exception("Could not find a controller named '${split[0]}'"); @@ -157,7 +168,11 @@ class ResponseContext extends Extensible { throw new Exception( "Controller '${split[0]}' does not contain any action named '${split[1]}'"); - redirect(matched.makeUri(params), code: code); + final head = + controller.exposeDecl.path.toString().replaceAll(_straySlashes, ''); + final tail = matched.makeUri(params).replaceAll(_straySlashes, ''); + + redirect('$head/$tail'.replaceAll(_straySlashes, ''), code: code); } /// Streams a file to this response as chunked data. diff --git a/lib/src/http/routable.dart b/lib/src/http/routable.dart index 7a25fc7f..2bc7f104 100644 --- a/lib/src/http/routable.dart +++ b/lib/src/http/routable.dart @@ -11,6 +11,7 @@ import 'metadata.dart'; import 'request_context.dart'; import 'response_context.dart'; import 'service.dart'; +final RegExp _straySlashes = new RegExp(r'(^/+)|(/+$)'); /// A function that intercepts a request and determines whether handling of it should continue. typedef Future RequestMiddleware(RequestContext req, ResponseContext res); @@ -23,7 +24,7 @@ typedef Future RawRequestHandler(HttpRequest request); /// A routable server that can handle dynamic requests. class Routable extends Router { - final Map _controllers = {}; + final Map _controllers = {}; final Map _services = {}; Routable({bool debug: false}) : super(debug: debug); @@ -36,7 +37,7 @@ class Routable extends Router { Map get services => _services; /// A set of [Controller] objects that have been loaded into the application. - Map get controllers => _controllers; + Map get controllers => _controllers; StreamController _onService = new StreamController.broadcast(); diff --git a/lib/src/http/server.dart b/lib/src/http/server.dart index 6d1bc003..5efb940b 100644 --- a/lib/src/http/server.dart +++ b/lib/src/http/server.dart @@ -4,6 +4,7 @@ import 'dart:async'; import 'dart:io'; import 'dart:math' show Random; import 'dart:mirrors'; +import 'package:angel_route/angel_route.dart'; import 'package:json_god/json_god.dart' as god; import 'package:shelf/shelf.dart' as shelf; import 'angel_base.dart'; @@ -185,23 +186,18 @@ class Angel extends AngelBase { if (requestedUrl.isEmpty) requestedUrl = '/'; - final resolved = resolveAll(requestedUrl, method: request.method); + final resolved = + resolveAll(requestedUrl, requestedUrl, method: request.method); + + for (final result in resolved) req.params.addAll(result.allParams); if (resolved.isNotEmpty) { - final route = resolved.first; - req.params.addAll(route?.parseParameters(requestedUrl) ?? {}); + final route = resolved.first.route; req.inject(Match, route.match(requestedUrl)); } - final pipeline = []..addAll(before); - - if (resolved.isNotEmpty) { - for (final route in resolved) { - pipeline.addAll(route.handlerSequence); - } - } - - pipeline.addAll(after); + final m = new MiddlewarePipeline(resolved); + final pipeline = []..addAll(before)..addAll(m.handlers)..addAll(after); _printDebug('Handler sequence on $requestedUrl: $pipeline'); @@ -304,7 +300,8 @@ class Angel extends AngelBase { Future configure(AngelConfigurer configurer) async { await configurer(this); - if (configurer is Controller) _onController.add(configurer); + if (configurer is Controller) + _onController.add(controllers[configurer.exposeDecl.path] = configurer); } /// Fallback when an error is thrown while handling a request. @@ -320,6 +317,15 @@ class Angel extends AngelBase { @override use(Pattern path, Routable routable, {bool hooked: true, String namespace: null}) { + if (routable is Angel) { + final head = path.toString().replaceAll(_straySlashes, ''); + + routable.controllers.forEach((k, v) { + final tail = k.toString().replaceAll(_straySlashes, ''); + controllers['$head/$tail'.replaceAll(_straySlashes, '')] = v; + }); + } + if (routable is Service) { routable.app = this; } diff --git a/lib/src/http/service.dart b/lib/src/http/service.dart index 723f7ae6..c286c09e 100644 --- a/lib/src/http/service.dart +++ b/lib/src/http/service.dart @@ -128,7 +128,5 @@ class Service extends Routable { ..addAll(handlers) ..addAll( (removeMiddleware == null) ? [] : removeMiddleware.handlers)); - - normalize(); } } diff --git a/pubspec.yaml b/pubspec.yaml index 38676067..883ce189 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: angel_framework -version: 1.0.0-dev.25 +version: 1.0.0-dev.26 description: Core libraries for the Angel framework. author: Tobe O homepage: https://github.com/angel-dart/angel_framework diff --git a/test/controller_test.dart b/test/controller_test.dart index 4490b3ea..dc63472f 100644 --- a/test/controller_test.dart +++ b/test/controller_test.dart @@ -12,10 +12,10 @@ class TodoController extends Controller { @Expose("/:id", middleware: const ["bar"]) Future fetchTodo( - int id, RequestContext req, ResponseContext res) async { + String id, RequestContext req, ResponseContext res) async { expect(req, isNotNull); expect(res, isNotNull); - return todos[id]; + return todos[int.parse(id)]; } @Expose("/namedRoute/:foo", as: "foo") @@ -32,7 +32,7 @@ main() { String url; setUp(() async { - app = new Angel(); + app = new Angel(debug: true); app.registerMiddleware("foo", (req, res) async => res.write("Hello, ")); app.registerMiddleware("bar", (req, res) async => res.write("world!")); app.get( @@ -57,7 +57,7 @@ main() { test("middleware", () async { var rgx = new RegExp("^Hello, world!"); var response = await client.get("$url/todos/0"); - print(response.body); + print('Response: ${response.body}'); expect(rgx.firstMatch(response.body).start, equals(0)); @@ -70,8 +70,7 @@ main() { test("named actions", () async { var response = await client.get("$url/redirect"); - print(response.body); - + print('Response: ${response.body}'); expect(response.body, equals("Hello, \"world!\"")); }); } diff --git a/test/hooked_test.dart b/test/hooked_test.dart index f9026d36..42ea966b 100644 --- a/test/hooked_test.dart +++ b/test/hooked_test.dart @@ -23,9 +23,7 @@ main() { app.use('/todos', new MemoryService()); Todos = app.service("todos"); - app - ..normalize() - ..dumpTree(showMatchers: true); + app.dumpTree(showMatchers: true); server = await app.startServer(); url = "http://${app.httpServer.address.host}:${app.httpServer.port}"; diff --git a/test/routing_test.dart b/test/routing_test.dart index d4249567..9a302949 100644 --- a/test/routing_test.dart +++ b/test/routing_test.dart @@ -19,7 +19,7 @@ class QueryService extends Service { } main() { - Angel angel; + Angel app; Angel nested; Angel todos; String url; @@ -27,11 +27,11 @@ main() { setUp(() async { final debug = true; - angel = new Angel(debug: debug); + app = new Angel(debug: debug); nested = new Angel(debug: debug); todos = new Angel(debug: debug); - angel + app ..registerMiddleware('interceptor', (req, res) async { res.write('Middleware'); return false; @@ -53,41 +53,39 @@ main() { return req.params; }); - angel.use('/nes', nested); - angel.get('/meta', testMiddlewareMetadata); - angel.get('/intercepted', 'This should not be shown', + app.use('/nes', nested); + app.get('/meta', testMiddlewareMetadata); + app.get('/intercepted', 'This should not be shown', middleware: ['interceptor']); - angel.get('/hello', 'world'); - angel.get('/name/:first/last/:last', (req, res) => req.params); - angel.post('/lambda', (req, res) => req.body); - angel.use('/todos/:id', todos); - angel + app.get('/hello', 'world'); + app.get('/name/:first/last/:last', (req, res) => req.params); + app.post('/lambda', (req, res) => req.body); + app.use('/todos/:id', todos); + app .get('/greet/:name', (RequestContext req, res) async => "Hello ${req.params['name']}") .as('Named routes'); - angel.get('/named', (req, ResponseContext res) async { + app.get('/named', (req, ResponseContext res) async { res.redirectTo('Named routes', {'name': 'tests'}); }); - angel.get('/log', (RequestContext req, res) async { + app.get('/log', (RequestContext req, res) async { print("Query: ${req.query}"); return "Logged"; }); - angel.use('/query', new QueryService()); - angel.get('*', 'MJ'); + app.use('/query', new QueryService()); + app.get('*', 'MJ'); - angel - ..normalize() - ..dumpTree(header: "DUMPING ROUTES:", showMatchers: true); + app.dumpTree(header: "DUMPING ROUTES:", showMatchers: true); client = new http.Client(); - await angel.startServer(InternetAddress.LOOPBACK_IP_V4, 0); - url = "http://${angel.httpServer.address.host}:${angel.httpServer.port}"; + await app.startServer(InternetAddress.LOOPBACK_IP_V4, 0); + url = "http://${app.httpServer.address.host}:${app.httpServer.port}"; }); tearDown(() async { - await angel.httpServer.close(force: true); - angel = null; + await app.httpServer.close(force: true); + app = null; nested = null; todos = null; client.close(); @@ -102,7 +100,7 @@ main() { test('Can match url with multiple parameters', () async { var response = await client.get('$url/name/HELLO/last/WORLD'); - print(response.body); + print('Response: ${response.body}'); var json = god.deserialize(response.body); expect(json, new isInstanceOf>()); expect(json['first'], equals('HELLO')); @@ -119,7 +117,7 @@ main() { var response = await client.get('$url/todos/1337/action/test'); var json = god.deserialize(response.body); print('JSON: $json'); - expect(json['id'], equals(1337)); + expect(json['id'], equals('1337')); expect(json['action'], equals('test')); });