From f2ea859b9229c1c929dfdf5a1a0837a831cf8cb4 Mon Sep 17 00:00:00 2001 From: thosakwe Date: Mon, 28 Aug 2017 11:29:27 -0400 Subject: [PATCH] 1.0.9+1 --- .idea/libraries/Dart_Packages.xml | 100 ++++--- .idea/workspace.xml | 449 +++++++++++++++++------------- CHANGELOG.md | 6 + lib/src/http/controller.dart | 6 +- lib/src/http/server.dart | 119 ++++---- pubspec.yaml | 3 +- test/all.dart | 4 + test/primitives_test.dart | 67 +++++ test/repeat_request_test.dart | 29 ++ 9 files changed, 485 insertions(+), 298 deletions(-) create mode 100644 test/primitives_test.dart create mode 100644 test/repeat_request_test.dart diff --git a/.idea/libraries/Dart_Packages.xml b/.idea/libraries/Dart_Packages.xml index 264fd455..8712f48d 100644 --- a/.idea/libraries/Dart_Packages.xml +++ b/.idea/libraries/Dart_Packages.xml @@ -47,7 +47,7 @@ - @@ -68,14 +68,14 @@ - - @@ -96,7 +96,7 @@ - @@ -138,7 +138,7 @@ - @@ -170,6 +170,13 @@ + + + + + + @@ -208,7 +215,7 @@ - @@ -222,14 +229,21 @@ - + + + + + + - @@ -243,14 +257,14 @@ - - @@ -268,6 +282,13 @@ + + + + + + @@ -278,21 +299,21 @@ - - - @@ -327,7 +348,7 @@ - @@ -355,14 +376,21 @@ - + + + + + + - @@ -383,7 +411,7 @@ - @@ -403,55 +431,59 @@ - + - - + + - + - + + - + - - + + + - - + + + - - - + + + - + - - + + + - + diff --git a/.idea/workspace.xml b/.idea/workspace.xml index 580b63af..6877262e 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -1,11 +1,16 @@ - + + + + - - - + + + + + @@ -33,8 +38,8 @@ - - + + @@ -42,6 +47,18 @@ + + + + + + + + + + + + @@ -64,11 +81,21 @@ - - + + - - + + + + + + + + + + + + @@ -86,54 +113,22 @@ - - + + - - + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + @@ -151,8 +146,6 @@ - streamFil` - g group(' group( group\' @@ -168,7 +161,6 @@ group\('[^']+' group\('[^']+', group\('[^']+', ( - group\('[^']+', group\('[^']+', \ group\('[^']+', \() group\('[^']+', \( @@ -176,11 +168,14 @@ group\('[^']+', \(\) as group\('[^']+', \(\) a group\('[^']+', \(\) - group\('[^']+', \(\) group\('[^']+', \(\)\s group\('[^']+', \(\)\s* group\('[^']+', \(\)\s*a group\('[^']+', \(\)\s*as + _hand + _handle + handleContaine + _handlerC _isClosed @@ -229,7 +224,6 @@ @@ -347,10 +344,64 @@ - + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -605,7 +668,8 @@ - + + 1481237183504 @@ -814,39 +878,39 @@ - + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - @@ -863,14 +927,13 @@ - + - @@ -883,6 +946,7 @@ + @@ -893,7 +957,6 @@ - @@ -918,27 +981,14 @@ - - - - - - - - - - - - - - - @@ -964,7 +1014,6 @@ - @@ -993,7 +1042,6 @@ - @@ -1001,7 +1049,6 @@ - @@ -1009,9 +1056,6 @@ - - - @@ -1078,7 +1122,6 @@ - @@ -1118,7 +1161,6 @@ - @@ -1151,7 +1193,6 @@ - @@ -1162,7 +1203,6 @@ - @@ -1180,19 +1220,6 @@ - - - - - - - - - - - - - @@ -1200,15 +1227,6 @@ - - - - - - - - - @@ -1216,7 +1234,6 @@ - @@ -1224,15 +1241,6 @@ - - - - - - - - - @@ -1240,25 +1248,22 @@ - - - - + - + - + @@ -1274,23 +1279,12 @@ - + - - - - - - - - - - - @@ -1301,19 +1295,13 @@ - + - - - - - - - - - - - + + + + + @@ -1327,15 +1315,80 @@ - + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index b44c2ec5..94a98d92 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +# 1.0.9+1 +* Closed [#162](https://github.com/angel-dart/framework/issues/162), fixing a caching bug +that would cause multiple requests to the same URL to fail. +* Resolved dependency injection of primitives (namely `String`), no longer triggering +errors about `String has no constructor`, etc. + # 1.0.9 * Closed [#161](https://github.com/angel-dart/framework/issues/161). `addCreated`/`addUpdatedAt` no longer crash when `serialize` is `false`. diff --git a/lib/src/http/controller.dart b/lib/src/http/controller.dart index beb760dd..811df1de 100644 --- a/lib/src/http/controller.dart +++ b/lib/src/http/controller.dart @@ -10,6 +10,8 @@ import 'response_context.dart'; import 'routable.dart'; import 'server.dart' show Angel, preInject; +const List _primitiveTypes = const [String, int, num, double, Null]; + /// Contains a list of the data required for a DI-enabled method to run. /// /// This improves performance by removing the necessity to reflect a method @@ -186,11 +188,11 @@ RequestHandler handleContained(handler, InjectionRequest injection) { requirement.last is Type) { String key = requirement.first; Type type = requirement.last; - if (req.params.containsKey(key) || req.injections.containsKey(key) || req.properties.containsKey(key) || - req.app.properties.containsKey(key)) { + req.app.properties.containsKey(key) || + _primitiveTypes.contains(type)) { inject(key); } else inject(type); diff --git a/lib/src/http/server.dart b/lib/src/http/server.dart index 0f6dc170..53a445eb 100644 --- a/lib/src/http/server.dart +++ b/lib/src/http/server.dart @@ -10,6 +10,7 @@ export 'package:container/container.dart'; import 'package:flatten/flatten.dart'; import 'package:json_god/json_god.dart' as god; import 'package:meta/meta.dart'; +import 'package:tuple/tuple.dart'; import '../safe_stream_controller.dart'; import 'angel_base.dart'; import 'angel_http_exception.dart'; @@ -28,8 +29,8 @@ final RegExp _straySlashes = new RegExp(r'(^/+)|(/+$)'); typedef Future ServerGenerator(InternetAddress address, int port); /// Handles an [AngelHttpException]. -typedef Future AngelErrorHandler(AngelHttpException err, RequestContext req, - ResponseContext res); +typedef Future AngelErrorHandler( + AngelHttpException err, RequestContext req, ResponseContext res); /// A function that configures an [Angel] server in some way. typedef Future AngelConfigurer(Angel app); @@ -37,16 +38,16 @@ typedef Future AngelConfigurer(Angel app); /// A powerful real-time/REST/MVC server class. class Angel extends AngelBase { final SafeCtrl _afterProcessed = - new SafeCtrl.broadcast(); + new SafeCtrl.broadcast(); final SafeCtrl _beforeProcessed = - new SafeCtrl.broadcast(); + new SafeCtrl.broadcast(); final SafeCtrl _fatalErrorStream = - new SafeCtrl.broadcast(); + new SafeCtrl.broadcast(); final SafeCtrl _onController = - new SafeCtrl.broadcast(); + new SafeCtrl.broadcast(); final List _children = []; - final Map _handlerCache = {}; + final Map> _handlerCache = {}; Router _flattened; bool _isProduction; @@ -213,17 +214,17 @@ class Angel extends AngelBase { await service.close(); }); - for (var plugin in justBeforeStop) - await plugin(this); + for (var plugin in justBeforeStop) await plugin(this); return server; } @override - void dumpTree({callback(String tree), - String header: 'Dumping route tree:', - String tab: ' ', - bool showMatchers: false}) { + void dumpTree( + {callback(String tree), + String header: 'Dumping route tree:', + String tab: ' ', + bool showMatchers: false}) { if (isProduction) { if (_flattened == null) _flattened = flatten(this); @@ -232,8 +233,8 @@ class Angel extends AngelBase { header: header?.isNotEmpty == true ? header : (isProduction - ? 'Dumping flattened route tree:' - : 'Dumping route tree:'), + ? 'Dumping flattened route tree:' + : 'Dumping route tree:'), tab: tab ?? ' ', showMatchers: showMatchers == true); } else { @@ -242,8 +243,8 @@ class Angel extends AngelBase { header: header?.isNotEmpty == true ? header : (isProduction - ? 'Dumping flattened route tree:' - : 'Dumping route tree:'), + ? 'Dumping flattened route tree:' + : 'Dumping route tree:'), tab: tab ?? ' ', showMatchers: showMatchers == true); } @@ -264,8 +265,8 @@ class Angel extends AngelBase { _serializer = serializer; } - Future getHandlerResult(handler, RequestContext req, - ResponseContext res) async { + Future getHandlerResult( + handler, RequestContext req, ResponseContext res) async { /*if (handler is RequestMiddleware) { var result = await handler(req, res); @@ -303,8 +304,8 @@ class Angel extends AngelBase { } /// Runs some [handler]. Returns `true` if request execution should continue. - Future executeHandler(handler, RequestContext req, - ResponseContext res) async { + Future executeHandler( + handler, RequestContext req, ResponseContext res) async { var result = await getHandlerResult(handler, req, res); if (result is bool) { @@ -326,7 +327,7 @@ class Angel extends AngelBase { } Future createResponseContext(HttpResponse response, - [RequestContext correspondingRequest]) => + [RequestContext correspondingRequest]) => new Future.value( new ResponseContext(response, this, correspondingRequest) ..serializer = (_serializer ?? god.serialize) @@ -403,27 +404,25 @@ class Angel extends AngelBase { if (requestedUrl.isEmpty) requestedUrl = '/'; - var pipeline = _handlerCache.putIfAbsent(requestedUrl, () { - Router r = - isProduction ? (_flattened ?? (_flattened = flatten(this))) : this; + var tuple = _handlerCache.putIfAbsent(requestedUrl, () { + Router r = isProduction ? (_flattened ??= flatten(this)) : this; var resolved = - r.resolveAll(requestedUrl, requestedUrl, method: req.method); - - for (var result in resolved) - req.params.addAll(result.allParams); - - if (resolved.isNotEmpty) { - var route = resolved.first.route; - req.inject(Match, route.match(requestedUrl)); - } - - var m = new MiddlewarePipeline(resolved); - req.inject(MiddlewarePipeline, m); - - return new List.from(before) - ..addAll(m.handlers)..addAll(after); + r.resolveAll(requestedUrl, requestedUrl, method: req.method); + return new Tuple3( + new MiddlewarePipeline(resolved), + resolved.fold({}, (out, r) => out..addAll(r.allParams)), + resolved.isEmpty ? null : resolved.first.route.match(requestedUrl), + ); }); + req.inject(MiddlewarePipeline, tuple.item1); + req.params.addAll(tuple.item2); + req.inject(Match, tuple.item3); + + var pipeline = new List.from(before) + ..addAll(tuple.item1.handlers) + ..addAll(after); + for (var handler in pipeline) { try { if (!await executeHandler(handler, req, res)) break; @@ -464,9 +463,7 @@ class Angel extends AngelBase { void _walk(Router router) { if (router is Angel) { - router - ..before.forEach(_add) - ..after.forEach(_add); + router..before.forEach(_add)..after.forEach(_add); } router.requestMiddleware.forEach((k, v) => _add(v)); @@ -488,8 +485,8 @@ class Angel extends AngelBase { /// 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. - Future runContained(Function handler, RequestContext req, - ResponseContext res) { + Future runContained( + Function handler, RequestContext req, ResponseContext res) { if (_preContained.containsKey(handler)) { return handleContained(handler, _preContained[handler])(req, res); } @@ -498,23 +495,23 @@ class Angel extends AngelBase { } /// Runs with DI, and *always* reflects. Prefer [runContained]. - Future runReflected(Function handler, RequestContext req, - ResponseContext res) async { + Future runReflected( + Function handler, RequestContext req, ResponseContext res) async { var h = - handleContained(handler, _preContained[handler] = preInject(handler)); + handleContained(handler, _preContained[handler] = preInject(handler)); return await h(req, res); // return await closureMirror.apply(args).reflectee; } /// Use [sendResponse] instead. @deprecated - Future sendRequest(HttpRequest request, RequestContext req, - ResponseContext res) => + Future sendRequest( + HttpRequest request, RequestContext req, ResponseContext res) => sendResponse(request, req, res); /// Sends a response. - Future sendResponse(HttpRequest request, RequestContext req, - ResponseContext res, + Future sendResponse( + HttpRequest request, RequestContext req, ResponseContext res, {bool ignoreFinalizers: false}) { _afterProcessed.add(request); @@ -524,7 +521,7 @@ class Angel extends AngelBase { Future finalizers = ignoreFinalizers == true ? new Future.value() : responseFinalizers.fold( - new Future.value(), (out, f) => out.then((_) => f(req, res))); + new Future.value(), (out, f) => out.then((_) => f(req, res))); if (res.isOpen) res.end(); @@ -540,7 +537,7 @@ class Angel extends AngelBase { if (res.encoders.isNotEmpty) { var allowedEncodings = - (req.headers[HttpHeaders.ACCEPT_ENCODING] ?? []).map((str) { + (req.headers[HttpHeaders.ACCEPT_ENCODING] ?? []).map((str) { // Ignore quality specifications in accept-encoding // ex. gzip;q=0.8 if (!str.contains(';')) return str; @@ -558,8 +555,7 @@ class Angel extends AngelBase { } if (encoder != null) { - request.response.headers - .set(HttpHeaders.CONTENT_ENCODING, key); + request.response.headers.set(HttpHeaders.CONTENT_ENCODING, key); outputBuffer = res.encoders[key].convert(outputBuffer); break; } @@ -580,9 +576,7 @@ class Angel extends AngelBase { await configurer(this); if (configurer is Controller) - _onController.add(controllers[configurer - .findExpose() - .path] = configurer); + _onController.add(controllers[configurer.findExpose().path] = configurer); } /// Starts the server, wrapped in a [runZoned] call. @@ -686,9 +680,8 @@ class Angel extends AngelBase { /// An instance mounted on a server started by the [serverGenerator]. factory Angel.custom(ServerGenerator serverGenerator, - {@deprecated bool debug: false}) => - new Angel() - .._serverGenerator = serverGenerator; + {@deprecated bool debug: false}) => + new Angel().._serverGenerator = serverGenerator; factory Angel.fromSecurityContext(SecurityContext context, {@deprecated bool debug: false}) { @@ -709,7 +702,7 @@ class Angel extends AngelBase { factory Angel.secure(String certificateChainPath, String serverKeyPath, {bool debug: false, String password}) { var certificateChain = - Platform.script.resolve(certificateChainPath).toFilePath(); + Platform.script.resolve(certificateChainPath).toFilePath(); var serverKey = Platform.script.resolve(serverKeyPath).toFilePath(); var serverContext = new SecurityContext(); serverContext.useCertificateChain(certificateChain, password: password); diff --git a/pubspec.yaml b/pubspec.yaml index 0be3cf64..4fa991bc 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: angel_framework -version: 1.0.9 +version: 1.0.10 description: A high-powered HTTP server with DI, routing and more. author: Tobe O homepage: https://github.com/angel-dart/angel_framework @@ -17,6 +17,7 @@ dependencies: meta: ^1.0.0 mime: ^0.9.3 random_string: ^0.0.1 + tuple: ^1.0.0 dev_dependencies: matcher: ^0.12.0 mock_request: ^1.0.0 diff --git a/test/all.dart b/test/all.dart index f2d089d6..392fb5e4 100644 --- a/test/all.dart +++ b/test/all.dart @@ -7,6 +7,8 @@ import 'exception_test.dart' as exception; import 'general_test.dart' as general; import 'hooked_test.dart' as hooked; import 'precontained_test.dart' as precontained; +import 'primitives_test.dart' as primitives; +import 'repeat_request_test.dart' as repeat_request; import 'routing_test.dart' as routing; import 'serialize_test.dart' as serialize; import 'server_test.dart' as server; @@ -28,6 +30,8 @@ main() { group('general', general.main); group('hooked', hooked.main); group('precontained', precontained.main); + group('primitives', primitives.main); + group('repeat request', repeat_request.main); group('routing', routing.main); group('serialize', serialize.main); group('server', server.main); diff --git a/test/primitives_test.dart b/test/primitives_test.dart new file mode 100644 index 00000000..172542cf --- /dev/null +++ b/test/primitives_test.dart @@ -0,0 +1,67 @@ +import 'dart:convert'; +import 'dart:io'; +import 'package:angel_framework/angel_framework.dart'; +import 'package:mock_request/mock_request.dart'; +import 'package:test/test.dart'; + +main() { + Angel app; + + setUp(() { + app = new Angel()..inject('global', 305); // Pitbull! + + app.get('/string/:string', (String string) => string); + + app.get( + '/num/parsed/:num', + waterfall([ + (RequestContext req) { + req.params['n'] = num.parse(req.params['num']); + return true; + }, + (num n) => n, + ])); + + app.get('/num/global', (num global) => global); + + app.fatalErrorStream.listen((e) { + stderr..writeln(e.error)..writeln(e.stack); + }); + }); + + tearDown(() => app.close()); + + test('String type annotation', () async { + var rq = new MockHttpRequest('GET', Uri.parse('/string/hello'))..close(); + await app.handleRequest(rq); + var rs = await rq.response.transform(UTF8.decoder).join(); + expect(rs, JSON.encode('hello')); + }); + + test('Primitive after parsed param injection', () async { + var rq = new MockHttpRequest('GET', Uri.parse('/num/parsed/24'))..close(); + await app.handleRequest(rq); + var rs = await rq.response.transform(UTF8.decoder).join(); + expect(rs, JSON.encode(24)); + }); + + test('globally-injected primitive', () async { + var rq = new MockHttpRequest('GET', Uri.parse('/num/global'))..close(); + await app.handleRequest(rq); + var rs = await rq.response.transform(UTF8.decoder).join(); + expect(rs, JSON.encode(305)); + }); + + test('unparsed primitive throws error', () async { + try { + var rq = new MockHttpRequest('GET', Uri.parse('/num/unparsed/32')) + ..close(); + var req = await app.createRequestContext(rq); + var res = await app.createResponseContext(rq.response, req); + await app.runContained((num unparsed) => unparsed, req, res); + throw new StateError('ArgumentError should be thrown if a parameter cannot be resolved.'); + } on ArgumentError { + // Success + } + }); +} diff --git a/test/repeat_request_test.dart b/test/repeat_request_test.dart new file mode 100644 index 00000000..8f1d1c43 --- /dev/null +++ b/test/repeat_request_test.dart @@ -0,0 +1,29 @@ +import 'dart:async'; +import 'dart:convert'; +import 'package:angel_framework/angel_framework.dart'; +import 'package:mock_request/mock_request.dart'; +import 'package:test/test.dart'; + +main() { + MockHttpRequest mk(int id) { + return new MockHttpRequest('GET', Uri.parse('/test/$id'))..close(); + } + + test('can request the same url twice', () async { + var app = new Angel()..get('/test/:id', (id) => 'Hello $id'); + var rq1 = mk(1), rq2 = mk(2), rq3 = mk(1); + await Future.wait([rq1, rq2, rq3].map(app.handleRequest)); + var body1 = await rq1.response.transform(UTF8.decoder).join(), + body2 = await rq2.response.transform(UTF8.decoder).join(), + body3 = await rq3.response.transform(UTF8.decoder).join(); + print('Response #1: $body1'); + print('Response #2: $body2'); + print('Response #3: $body3'); + expect( + body1, + allOf( + isNot(body2), + equals(body3), + )); + }); +}