diff --git a/README.md b/README.md index a4476da9..3578bd38 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # angel_framework -[![pub 1.0.0-dev.73](https://img.shields.io/badge/pub-1.0.0--dev.73-red.svg)](https://pub.dartlang.org/packages/angel_framework) +[![pub 1.0.0-dev.74](https://img.shields.io/badge/pub-1.0.0--dev.74-red.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/hooked_service.dart b/lib/src/http/hooked_service.dart index 9cd3eb1f..dadda43d 100644 --- a/lib/src/http/hooked_service.dart +++ b/lib/src/http/hooked_service.dart @@ -11,30 +11,34 @@ import 'service.dart'; /// Wraps another service in a service that broadcasts events on actions. class HookedService extends Service { + final List> _ctrl = []; + /// Tbe service that is proxied by this hooked one. final Service inner; - HookedServiceEventDispatcher beforeIndexed = + final HookedServiceEventDispatcher beforeIndexed = new HookedServiceEventDispatcher(); - HookedServiceEventDispatcher beforeRead = new HookedServiceEventDispatcher(); - HookedServiceEventDispatcher beforeCreated = + final HookedServiceEventDispatcher beforeRead = new HookedServiceEventDispatcher(); - HookedServiceEventDispatcher beforeModified = + final HookedServiceEventDispatcher beforeCreated = new HookedServiceEventDispatcher(); - HookedServiceEventDispatcher beforeUpdated = + final HookedServiceEventDispatcher beforeModified = new HookedServiceEventDispatcher(); - HookedServiceEventDispatcher beforeRemoved = + final HookedServiceEventDispatcher beforeUpdated = new HookedServiceEventDispatcher(); - HookedServiceEventDispatcher afterIndexed = + final HookedServiceEventDispatcher beforeRemoved = new HookedServiceEventDispatcher(); - HookedServiceEventDispatcher afterRead = new HookedServiceEventDispatcher(); - HookedServiceEventDispatcher afterCreated = + final HookedServiceEventDispatcher afterIndexed = new HookedServiceEventDispatcher(); - HookedServiceEventDispatcher afterModified = + final HookedServiceEventDispatcher afterRead = new HookedServiceEventDispatcher(); - HookedServiceEventDispatcher afterUpdated = + final HookedServiceEventDispatcher afterCreated = new HookedServiceEventDispatcher(); - HookedServiceEventDispatcher afterRemoved = + final HookedServiceEventDispatcher afterModified = + new HookedServiceEventDispatcher(); + final HookedServiceEventDispatcher afterUpdated = + new HookedServiceEventDispatcher(); + final HookedServiceEventDispatcher afterRemoved = new HookedServiceEventDispatcher(); HookedService(Service this.inner) { @@ -61,6 +65,25 @@ class HookedService extends Service { .fold({}, (map, key) => map..[key] = params[key]); } + /// Closes any open [StreamController]s on this instance. **Internal use only**. + Future close() async { + _ctrl.forEach((c) => c.close()); + beforeIndexed._close(); + beforeRead._close(); + beforeCreated._close(); + beforeModified._close(); + beforeUpdated._close(); + beforeRemoved._close(); + afterIndexed._close(); + afterRead._close(); + afterCreated._close(); + afterModified._close(); + afterUpdated._close(); + afterRemoved._close(); + + if (inner is HookedService) inner.close(); + } + /// Adds hooks to this instance. void addHooks() { Hooks hooks = getAnnotation(inner, Hooks); @@ -309,6 +332,54 @@ class HookedService extends Service { afterRemoved.listen(listener); } + /// Returns a [Stream] of all events fired before every service method. + /// + /// *NOTE*: Only use this if you do not plan to modify events. There is no guarantee + /// that events coming out of this [Stream] will see changes you make within the [Stream] + /// callback. + Stream beforeAllStream() { + var ctrl = new StreamController(); + _ctrl.add(ctrl); + before(HookedServiceEvent.ALL, ctrl.add); + return ctrl.stream; + } + + /// Returns a [Stream] of all events fired after every service method. + /// + /// *NOTE*: Only use this if you do not plan to modify events. There is no guarantee + /// that events coming out of this [Stream] will see changes you make within the [Stream] + /// callback. + Stream afterAllStream() { + var ctrl = new StreamController(); + _ctrl.add(ctrl); + before(HookedServiceEvent.ALL, ctrl.add); + return ctrl.stream; + } + + /// Returns a [Stream] of all events fired before every service method specified. + /// + /// *NOTE*: Only use this if you do not plan to modify events. There is no guarantee + /// that events coming out of this [Stream] will see changes you make within the [Stream] + /// callback. + Stream beforeStream(Iterable eventNames) { + var ctrl = new StreamController(); + _ctrl.add(ctrl); + before(eventNames, ctrl.add); + return ctrl.stream; + } + + /// Returns a [Stream] of all events fired AFTER every service method specified. + /// + /// *NOTE*: Only use this if you do not plan to modify events. There is no guarantee + /// that events coming out of this [Stream] will see changes you make within the [Stream] + /// callback. + Stream afterStream(Iterable eventNames) { + var ctrl = new StreamController(); + _ctrl.add(ctrl); + after(eventNames, ctrl.add); + return ctrl.stream; + } + /// Runs the [listener] before [create], [modify] and [update]. void beforeModify(HookedServiceEventListener listener) { beforeCreated.listen(listener); @@ -549,6 +620,14 @@ class HookedServiceEvent { static const String MODIFIED = "modified"; static const String UPDATED = "updated"; static const String REMOVED = "removed"; + static const List ALL = const [ + INDEXED, + READ, + CREATED, + MODIFIED, + UPDATED, + REMOVED + ]; /// Use this to end processing of an event. void cancel([result]) { @@ -601,7 +680,12 @@ typedef HookedServiceEventListener(HookedServiceEvent event); /// Can be listened to, but events may be canceled. class HookedServiceEventDispatcher { - List listeners = []; + final List> _ctrl = []; + final List listeners = []; + + void _close() { + _ctrl.forEach((c) => c.close()); + } /// Fires an event, and returns it once it is either canceled, or all listeners have run. Future _emit(HookedServiceEvent event) async { @@ -616,6 +700,17 @@ class HookedServiceEventDispatcher { return event; } + /// Returns a [Stream] containing all events fired by this dispatcher. + /// + /// *NOTE*: Callbacks on the returned [Stream] cannot be guaranteed to run before other [listeners]. + /// Use this only if you need a read-only stream of events. + Stream asStream() { + var ctrl = new StreamController(); + _ctrl.add(ctrl); + listen(ctrl.add); + return ctrl.stream; + } + /// Registers the listener to be called whenever an event is triggered. void listen(HookedServiceEventListener listener) { listeners.add(listener); diff --git a/lib/src/http/server.dart b/lib/src/http/server.dart index 7022c76d..093569e9 100644 --- a/lib/src/http/server.dart +++ b/lib/src/http/server.dart @@ -11,6 +11,7 @@ import 'angel_base.dart'; import 'angel_http_exception.dart'; import 'controller.dart'; import 'fatal_error.dart'; +import 'hooked_service.dart'; import 'request_context.dart'; import 'response_context.dart'; import 'routable.dart'; @@ -93,6 +94,11 @@ class Angel extends AngelBase { /// If the server is never started, they will never be called. final List justBeforeStart = []; + /// Plug-ins to be called right before server shutdown + /// + /// If the server is never [close]d, they will never be called. + final List justBeforeStop = []; + /// Always run before responses are sent. /// /// These will only not run if an [AngelFatalError] occurs, @@ -180,6 +186,31 @@ class Angel extends AngelBase { container.singleton(this); } + /// Shuts down the server, and closes any open [StreamController]s. + Future close() async { + HttpServer server; + + if (httpServer != null) { + server = httpServer; + await httpServer.close(force: true); + } + + _afterProcessed.close(); + _beforeProcessed.close(); + _fatalErrorStream.close(); + _onController.close(); + + await Future.forEach(services.keys, (Service service) async { + if (service is HookedService) { + await service.close(); + } + }); + + for (var plugin in justBeforeStop) await plugin(this); + + return server; + } + @override void dumpTree( {callback(String tree), diff --git a/pubspec.yaml b/pubspec.yaml index 4b2de8eb..f32f63ab 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: angel_framework -version: 1.0.0-dev.73 +version: 1.0.0-dev.74 description: A high-powered HTTP server with DI, routing and more. author: Tobe O homepage: https://github.com/angel-dart/angel_framework diff --git a/test/hooked_test.dart b/test/hooked_test.dart index e9608f4b..2371498e 100644 --- a/test/hooked_test.dart +++ b/test/hooked_test.dart @@ -24,6 +24,10 @@ main() { app.use('/books', new BookService()); Todos = app.service("todos"); + Todos.beforeAllStream().listen((e) { + print('Fired ${e.eventName}! Data: ${e.data}; Params: ${e.params}'); + }); + app.fatalErrorStream.listen((e) => throw e.error); server = await app.startServer();