diff --git a/.idea/libraries/Dart_Packages.xml b/.idea/libraries/Dart_Packages.xml index c12960e2..14dea75e 100644 --- a/.idea/libraries/Dart_Packages.xml +++ b/.idea/libraries/Dart_Packages.xml @@ -96,7 +96,7 @@ - @@ -124,7 +124,7 @@ - @@ -212,6 +212,13 @@ + + + + + + @@ -341,7 +348,7 @@ - @@ -396,11 +403,11 @@ - + - + @@ -413,6 +420,7 @@ + @@ -431,7 +439,7 @@ - + diff --git a/.idea/workspace.xml b/.idea/workspace.xml index e22f2392..80f2a018 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -2,19 +2,16 @@ - - + - - - - - + + + + - - + @@ -38,21 +35,11 @@ - - - - - - - - - - - - + + @@ -60,24 +47,14 @@ - - - - - - - - - - - - + + - + @@ -88,7 +65,7 @@ - + @@ -97,33 +74,73 @@ - - + + - - - - - - - - - - - - - - + + - - + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -156,10 +173,21 @@ close( status createdAt + preInj + p + printDebug + d + debug + _printDebug + use( _isClosed + // _printDebug + + C:\Users\thosa\Source\Angel\framework\lib + @@ -220,7 +249,6 @@ - @@ -303,6 +331,7 @@ + @@ -397,13 +426,19 @@ false - - - - - - - + + + + + + + + + + @@ -429,7 +464,10 @@ - + + + + 1481237183504 @@ -508,43 +546,50 @@ - - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - @@ -559,25 +604,25 @@ - + + - - + - + + - @@ -600,23 +645,14 @@ - - - - - - - - - - - @@ -629,9 +665,6 @@ - - - @@ -697,9 +730,6 @@ - - - @@ -824,66 +854,25 @@ - - - - - - - - - - - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - @@ -891,19 +880,6 @@ - - - - - - - - - - - - - @@ -911,7 +887,6 @@ - @@ -919,9 +894,6 @@ - - - @@ -929,7 +901,6 @@ - @@ -937,31 +908,21 @@ - - + - - - - - - - - - @@ -969,30 +930,90 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - + - - - - - - - - - - + + @@ -1000,10 +1021,10 @@ - - + + - + diff --git a/README.md b/README.md index 94276549..c4be25d2 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # angel_framework -[![pub 1.0.2+5](https://img.shields.io/badge/pub-1.0.2+5-brightgreen.svg)](https://pub.dartlang.org/packages/angel_framework) +[![pub 1.0.3](https://img.shields.io/badge/pub-1.0.3-brightgreen.svg)](https://pub.dartlang.org/packages/angel_framework) [![build status](https://travis-ci.org/angel-dart/framework.svg)](https://travis-ci.org/angel-dart/framework) A high-powered HTTP server with support for dependency injection, sophisticated routing and more. diff --git a/lib/src/http/controller.dart b/lib/src/http/controller.dart index 88927505..32b5c1bd 100644 --- a/lib/src/http/controller.dart +++ b/lib/src/http/controller.dart @@ -143,13 +143,14 @@ RequestHandler createDynamicHandler(handler, injection.optional.addAll(optional ?? []); return handleContained(handler, injection); } - /// Handles a request with a DI-enabled handler. RequestHandler handleContained(handler, InjectionRequest injection) { return (RequestContext req, ResponseContext res) async { List args = []; void inject(requirement) { + var propFromApp; + if (requirement == RequestContext) { args.add(req); } else if (requirement == ResponseContext) { @@ -161,6 +162,8 @@ RequestHandler handleContained(handler, InjectionRequest injection) { args.add(req.injections[requirement]); else if (req.properties.containsKey(requirement)) args.add(req.properties[requirement]); + else if ((propFromApp = req.app.findProperty(requirement)) != null) + args.add(propFromApp); else if (injection.optional.contains(requirement)) args.add(null); else { @@ -174,7 +177,10 @@ RequestHandler handleContained(handler, InjectionRequest injection) { String key = requirement.first; Type type = requirement.last; - if (req.params.containsKey(key) || req.injections.containsKey(key)) { + if (req.params.containsKey(key) || + req.injections.containsKey(key) || + req.properties.containsKey(key) || + req.app.properties.containsKey(key)) { inject(key); } else inject(type); diff --git a/lib/src/http/routable.dart b/lib/src/http/routable.dart index 53ac7756..c01cfd61 100644 --- a/lib/src/http/routable.dart +++ b/lib/src/http/routable.dart @@ -124,8 +124,15 @@ class Routable extends Router { Map copiedMiddleware = new Map.from(router.requestMiddleware); for (String middlewareName in copiedMiddleware.keys) { - requestMiddleware["$middlewarePrefix$middlewareName"] = - copiedMiddleware[middlewareName]; + requestMiddleware.putIfAbsent("$middlewarePrefix$middlewareName", + () => copiedMiddleware[middlewareName]); + } + + // Also copy properties... + Map copiedProperties = new Map.from(router.properties); + for (String propertyName in copiedProperties.keys) { + properties.putIfAbsent("$middlewarePrefix$propertyName", + () => copiedMiddleware[propertyName]); } // _router.dumpTree(header: 'Mounting on "$path":'); diff --git a/lib/src/http/server.dart b/lib/src/http/server.dart index 1980106a..cf2ad6f8 100644 --- a/lib/src/http/server.dart +++ b/lib/src/http/server.dart @@ -49,6 +49,11 @@ class Angel extends AngelBase { final Map _preContained = {}; ResponseSerializer _serializer; + /// A [Map] of dependency data obtained via reflection. + /// + /// You may modify this [Map] yourself if you intend to avoid reflection entirely. + Map get preContained => _preContained; + /// Determines whether to allow HTTP request method overrides. bool allowMethodOverrides = true; @@ -135,10 +140,6 @@ class Angel extends AngelBase { _fatalErrorStream.add(new AngelFatalError(error: e, stack: st)); } - void _printDebug(x) { - if (debug) print(x); - } - /// Starts the server. /// /// Returns false on failure; otherwise, returns the HttpServer. @@ -285,8 +286,9 @@ class Angel extends AngelBase { return result; } - if (requestMiddleware.containsKey(handler)) { - return await getHandlerResult(requestMiddleware[handler], req, res); + var middleware = (req.app ?? this).findMiddleware(handler); + if (middleware != null) { + return await getHandlerResult(middleware, req, res); } return handler; @@ -321,6 +323,18 @@ class Angel extends AngelBase { new ResponseContext(response, this) ..serializer = (_serializer ?? god.serialize); + /// Attempts to find a middleware by the given name within this application. + findMiddleware(key) { + if (requestMiddleware.containsKey(key)) return requestMiddleware[key]; + return parent != null ? parent.findMiddleware(key) : null; + } + + /// Attempts to find a property by the given name within this application. + findProperty(key) { + if (properties.containsKey(key)) return properties[key]; + return parent != null ? parent.findProperty(key) : null; + } + /// Handles a single request. Future handleRequest(HttpRequest request) async { try { @@ -347,24 +361,24 @@ class Angel extends AngelBase { var pipeline = []..addAll(before)..addAll(m.handlers)..addAll(after); - _printDebug('Handler sequence on $requestedUrl: $pipeline'); + // _printDebug('Handler sequence on $requestedUrl: $pipeline'); for (var handler in pipeline) { try { - _printDebug('Executing handler: $handler'); + // _printDebug('Executing handler: $handler'); var result = await executeHandler(handler, req, res); - _printDebug('Result: $result'); + // _printDebug('Result: $result'); if (!result) { - _printDebug('Last executed handler: $handler'); + // _printDebug('Last executed handler: $handler'); break; } else { - _printDebug( - 'Handler completed successfully, did not terminate response: $handler'); + // _printDebug( + // 'Handler completed successfully, did not terminate response: $handler'); } } catch (e, st) { - _printDebug('Caught error in handler $handler: $e'); - _printDebug(st); + // _printDebug('Caught error in handler $handler: $e'); + // _printDebug(st); if (e is AngelHttpException) { // Special handling for AngelHttpExceptions :) @@ -413,8 +427,10 @@ class Angel extends AngelBase { /// * Preprocesses all dependency injection, and eliminates the burden of reflecting handlers /// at run-time. /// * [flatten]s the route tree into a linear one. - void optimizeForProduction() { - if (isProduction == true) { + /// + /// You may [force] the optimization to run, if you are not running in production. + void optimizeForProduction({bool force: false}) { + if (isProduction == true || force == true) { _add(v) { if (v is Function && !_preContained.containsKey(v)) { _preContained[v] = preInject(v); @@ -428,10 +444,10 @@ class Angel extends AngelBase { 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); + router.routes.forEach((r) { + r.handlers.forEach(_add); + if (r is SymlinkRoute) _walk(r.router); + }); } if (_flattened == null) _flattened = flatten(this); diff --git a/lib/src/http/service.dart b/lib/src/http/service.dart index 162f675b..97565064 100644 --- a/lib/src/http/service.dart +++ b/lib/src/http/service.dart @@ -37,6 +37,13 @@ class Providers { /// /// Heavily inspired by FeathersJS. <3 class Service extends Routable { + /// A [List] of keys that services should ignore, should they see them in the query. + static const List SPECIAL_QUERY_KEYS = const [ + r'$limit', + r'$sort', + 'page' + ]; + /// The [Angel] app powering this service. AngelBase app; @@ -99,9 +106,8 @@ class Service extends Routable { req.serviceParams ])); }, - middleware: [] - ..addAll(handlers) - ..addAll((indexMiddleware == null) ? [] : indexMiddleware.handlers)); + middleware: []..addAll(handlers)..addAll( + (indexMiddleware == null) ? [] : indexMiddleware.handlers)); Middleware createMiddleware = getAnnotation(this.create, Middleware); post('/', (req, ResponseContext res) async { @@ -115,30 +121,29 @@ class Service extends Routable { res.statusCode = 201; return r; }, - middleware: [] - ..addAll(handlers) - ..addAll( - (createMiddleware == null) ? [] : createMiddleware.handlers)); + middleware: []..addAll(handlers)..addAll( + (createMiddleware == null) ? [] : createMiddleware.handlers)); Middleware readMiddleware = getAnnotation(this.read, Middleware); get( '/:id', - (req, res) async => await this.read( + (req, res) async => + await this.read( toId(req.params['id']), mergeMap([ {'query': req.query}, restProvider, req.serviceParams ])), - middleware: [] - ..addAll(handlers) - ..addAll((readMiddleware == null) ? [] : readMiddleware.handlers)); + middleware: []..addAll(handlers)..addAll( + (readMiddleware == null) ? [] : readMiddleware.handlers)); Middleware modifyMiddleware = getAnnotation(this.modify, Middleware); patch( '/:id', - (req, res) async => await this.modify( + (req, res) async => + await this.modify( toId(req.params['id']), await req.lazyBody(), mergeMap([ @@ -146,15 +151,14 @@ class Service extends Routable { restProvider, req.serviceParams ])), - middleware: [] - ..addAll(handlers) - ..addAll( - (modifyMiddleware == null) ? [] : modifyMiddleware.handlers)); + middleware: []..addAll(handlers)..addAll( + (modifyMiddleware == null) ? [] : modifyMiddleware.handlers)); Middleware updateMiddleware = getAnnotation(this.update, Middleware); post( '/:id', - (req, res) async => await this.update( + (req, res) async => + await this.update( toId(req.params['id']), await req.lazyBody(), mergeMap([ @@ -162,13 +166,12 @@ class Service extends Routable { restProvider, req.serviceParams ])), - middleware: [] - ..addAll(handlers) - ..addAll( - (updateMiddleware == null) ? [] : updateMiddleware.handlers)); + middleware: []..addAll(handlers)..addAll( + (updateMiddleware == null) ? [] : updateMiddleware.handlers)); put( '/:id', - (req, res) async => await this.update( + (req, res) async => + await this.update( toId(req.params['id']), await req.lazyBody(), mergeMap([ @@ -176,38 +179,34 @@ class Service extends Routable { restProvider, req.serviceParams ])), - middleware: [] - ..addAll(handlers) - ..addAll( - (updateMiddleware == null) ? [] : updateMiddleware.handlers)); + middleware: []..addAll(handlers)..addAll( + (updateMiddleware == null) ? [] : updateMiddleware.handlers)); Middleware removeMiddleware = getAnnotation(this.remove, Middleware); delete( '/', - (req, res) async => await this.remove( + (req, res) async => + await this.remove( null, mergeMap([ {'query': req.query}, restProvider, req.serviceParams ])), - middleware: [] - ..addAll(handlers) - ..addAll( - (removeMiddleware == null) ? [] : removeMiddleware.handlers)); + middleware: []..addAll(handlers)..addAll( + (removeMiddleware == null) ? [] : removeMiddleware.handlers)); delete( '/:id', - (req, res) async => await this.remove( + (req, res) async => + await this.remove( toId(req.params['id']), mergeMap([ {'query': req.query}, restProvider, req.serviceParams ])), - middleware: [] - ..addAll(handlers) - ..addAll( - (removeMiddleware == null) ? [] : removeMiddleware.handlers)); + middleware: []..addAll(handlers)..addAll( + (removeMiddleware == null) ? [] : removeMiddleware.handlers)); // REST compliance put('/', () => throw new AngelHttpException.notFound()); diff --git a/pubspec.yaml b/pubspec.yaml index 6aa20635..2c23d8b6 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -15,5 +15,6 @@ dependencies: random_string: ^0.0.1 mime: ^0.9.3 dev_dependencies: + mock_request: ^1.0.0 http: ^0.11.3 test: ^0.12.13 diff --git a/test/all.dart b/test/all.dart index da1c11ae..c68df6c9 100644 --- a/test/all.dart +++ b/test/all.dart @@ -2,6 +2,7 @@ import 'controller_test.dart' as controller; import 'di_test.dart' as di; import 'general_test.dart' as general; import 'hooked_test.dart' as hooked; +import 'precontained_test.dart' as precontained; import 'routing_test.dart' as routing; import 'serialize_test.dart' as serialize; import 'services_test.dart' as services; @@ -14,6 +15,7 @@ main() { group('di', di.main); group('general', general.main); group('hooked', hooked.main); + group('precontained', precontained.main); group('routing', routing.main); group('serialize', serialize.main); group('services', services.main); diff --git a/test/precontained_test.dart b/test/precontained_test.dart new file mode 100644 index 00000000..319d1f8c --- /dev/null +++ b/test/precontained_test.dart @@ -0,0 +1,24 @@ +import 'dart:convert'; +import 'package:angel_framework/angel_framework.dart'; +import 'package:mock_request/mock_request.dart'; +import 'package:test/test.dart'; + +main() { + test('preinjects functions', () async { + var app = new Angel() + ..properties['foo'] = 'bar' + ..get('/foo', echoAppFoo); + app.optimizeForProduction(force: true); + print(app.preContained); + expect(app.preContained, contains(echoAppFoo)); + + var rq = new MockHttpRequest('GET', new Uri(path: '/foo')); + rq.close(); + await app.handleRequest(rq); + var rs = rq.response; + var body = await rs.transform(UTF8.decoder).join(); + expect(body, JSON.encode('bar')); + }); +} + +echoAppFoo(String foo) => foo;