diff --git a/lib/src/http/controller.dart b/lib/src/http/controller.dart index fdd82143..d04011b7 100644 --- a/lib/src/http/controller.dart +++ b/lib/src/http/controller.dart @@ -3,12 +3,11 @@ library angel_framework.http.controller; import 'dart:async'; import 'dart:mirrors'; import 'package:angel_route/angel_route.dart'; -import 'angel_http_exception.dart'; import 'metadata.dart'; import 'request_context.dart'; import 'response_context.dart'; import 'routable.dart'; -import 'server.dart' show Angel; +import 'server.dart' show Angel, preInject; /// Contains a list of the data required for a DI-enabled method to run. /// @@ -30,7 +29,6 @@ class Controller { final bool debug; List middleware = []; Map routeMappings = {}; - Expose exposeDecl; Controller({this.debug: false}); @@ -39,9 +37,7 @@ class Controller { // Load global expose decl ClassMirror classMirror = reflectClass(this.runtimeType); - Expose exposeDecl = classMirror.metadata - .map((m) => m.reflectee) - .firstWhere((r) => r is Expose, orElse: () => null); + Expose exposeDecl = findExpose(); if (exposeDecl == null) { throw new Exception( @@ -83,7 +79,6 @@ class Controller { var reflectedMethod = instanceMirror.getField(methodName).reflectee; var middleware = []..addAll(handlers)..addAll(exposeDecl.middleware); - var injection = new InjectionRequest(); // Check if normal if (method.parameters.length == 2 && @@ -95,26 +90,8 @@ class Controller { return; } - // Load parameters - for (var parameter in method.parameters) { - var name = MirrorSystem.getName(parameter.simpleName); - var type = parameter.type.reflectedType; - - if (type == RequestContext || type == ResponseContext) { - injection.required.add(type); - } else if (name == 'req') { - injection.required.add(RequestContext); - } else if (name == 'res') { - injection.required.add(ResponseContext); - } else if (type == dynamic) { - injection.required.add(name); - } else { - injection.required.add([name, type]); - } - } - routable.addRoute(exposeDecl.method, exposeDecl.path, - handleContained(reflectedMethod, injection), + handleContained(reflectedMethod, preInject(reflectedMethod)), middleware: middleware); } }; @@ -122,6 +99,12 @@ class Controller { /// Used to add additional routes to the router from within a [Controller]. void configureRoutes(Routable routable) {} + + /// Finds the [Expose] declaration for this class. + Expose findExpose() => reflectClass(runtimeType) + .metadata + .map((m) => m.reflectee) + .firstWhere((r) => r is Expose, orElse: () => null); } /// Handles a request with a DI-enabled handler. @@ -140,7 +123,7 @@ RequestHandler handleContained(handler, InjectionRequest injection) { .containsKey(requirement)) args.add(req.injections[requirement]); else { - throw new Exception( + throw new ArgumentError( "Cannot resolve parameter '$requirement' within handler."); } args.add(req.params[requirement]); diff --git a/lib/src/http/server.dart b/lib/src/http/server.dart index 26bd7eac..23ce0063 100644 --- a/lib/src/http/server.dart +++ b/lib/src/http/server.dart @@ -126,6 +126,7 @@ class Angel extends AngelBase { Future startServer([InternetAddress address, int port]) async { final host = address ?? InternetAddress.LOOPBACK_IP_V4; this.httpServer = await _serverGenerator(host, port ?? 0); + preprocessRoutes(); return httpServer..listen(handleRequest); } @@ -284,6 +285,27 @@ class Angel extends AngelBase { } } + /// Preprocesses all routes, and eliminates the burden of reflecting handlers + /// at run-time. + void preprocessRoutes() { + _add(v) { + if (v is Function && !_preContained.containsKey(v)) { + _preContained[v] = preInject(v); + } + } + + void _walk(Router router) { + router.requestMiddleware.forEach((k, v) => _add(v)); + router.middleware.forEach(_add); + router.routes + .where((r) => r is SymlinkRoute) + .map((SymlinkRoute r) => r.router) + .forEach(_walk); + } + + _walk(this); + } + /// Run a function after injecting from service container. /// If this function has been reflected before, then /// the execution will be faster, as the injection requirements were stored beforehand. @@ -299,26 +321,7 @@ class Angel extends AngelBase { /// Runs with DI, and *always* reflects. Prefer [runContained]. Future runReflected( Function handler, RequestContext req, ResponseContext res) async { - ClosureMirror closureMirror = reflect(handler); - var injection = new InjectionRequest(); - - // Load parameters - for (var parameter in closureMirror.function.parameters) { - var name = MirrorSystem.getName(parameter.simpleName); - var type = parameter.type.reflectedType; - - if (type == RequestContext || type == ResponseContext) { - injection.required.add(type); - } else if (name == 'req') { - injection.required.add(RequestContext); - } else if (name == 'res') { - injection.required.add(ResponseContext); - } else if (type == dynamic) { - injection.required.add(name); - } else { - injection.required.add([name, type]); - } - } + var injection = preInjection(handler); _preContained[handler] = injection; return handleContained(handler, injection); @@ -330,13 +333,13 @@ class Angel extends AngelBase { await configurer(this); if (configurer is Controller) - _onController.add(controllers[configurer.exposeDecl.path] = configurer); + _onController.add(controllers[configurer.findExpose().path] = configurer); } /// Fallback when an error is thrown while handling a request. void failSilently(HttpRequest request, ResponseContext res) {} - /// Starts the server. + /// Starts the server, wrapped in a [runZoned] call. void listen({InternetAddress address, int port: 3000}) { runZoned(() async { await startServer(address, port); @@ -433,3 +436,29 @@ class Angel extends AngelBase { return app; } } + +/// Predetermines what needs to be injected for a handler to run. +InjectionRequest preInject(Function handler) { + ClosureMirror closureMirror = reflect(handler); + var injection = new InjectionRequest(); + + // Load parameters + for (var parameter in closureMirror.function.parameters) { + var name = MirrorSystem.getName(parameter.simpleName); + var type = parameter.type.reflectedType; + + if (type == RequestContext || type == ResponseContext) { + injection.required.add(type); + } else if (name == 'req') { + injection.required.add(RequestContext); + } else if (name == 'res') { + injection.required.add(ResponseContext); + } else if (type == dynamic) { + injection.required.add(name); + } else { + injection.required.add([name, type]); + } + } + + return injection; +}