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
+
@@ -168,23 +196,24 @@
@@ -220,7 +249,6 @@
-
@@ -303,6 +331,7 @@
+
@@ -397,13 +426,19 @@
false
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -429,7 +464,10 @@
-
+
+
+
+
1481237183504
@@ -508,43 +546,50 @@
1493247351000
-
+
+ 1495888785100
+
+
+
+ 1495888785100
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
+
@@ -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;