library angel_framework.http.routable; import 'dart:async'; import 'package:angel_route/angel_route.dart'; import '../util.dart'; import 'hooked_service.dart'; import 'metadata.dart'; import 'request_context.dart'; import 'response_context.dart'; import 'service.dart'; final RegExp _straySlashes = new RegExp(r'(^/+)|(/+$)'); /// A function that receives an incoming [RequestContext] and responds to it. typedef FutureOr RequestHandler(RequestContext req, ResponseContext res); /// Sequentially runs a list of [handlers] of middleware, and returns early if any does not /// return `true`. Works well with [Router].chain. RequestHandler waterfall(Iterable handlers) { return (req, res) { Future Function() runPipeline; for (var handler in handlers) { if (handler == null) break; if (runPipeline == null) runPipeline = () => Future.sync(() => handler(req, res)); else { var current = runPipeline; runPipeline = () => current().then((result) => !res.isOpen ? new Future.value(result) : req.app.executeHandler(handler, req, res)); } } runPipeline ??= () => new Future.value(); return runPipeline(); }; } /// A routable server that can handle dynamic requests. class Routable extends Router { final Map _services = {}; final Map configuration = {}; Routable() : super(); void close() { _services.clear(); configuration.clear(); _onService.close(); } /// A set of [Service] objects that have been mapped into routes. Map get services => _services; StreamController _onService = new StreamController.broadcast(); /// Fired whenever a service is added to this instance. /// /// **NOTE**: This is a broadcast stream. Stream get onService => _onService.stream; /// Retrieves the service assigned to the given path. Service service(Pattern path) => _services[path] ?? _services[path.toString().replaceAll(_straySlashes, '')]; @override Route addRoute( String method, String path, RequestHandler handler, {Iterable middleware: const []}) { final handlers = []; // Merge @Middleware declaration, if any Middleware middlewareDeclaration = getAnnotation(handler, Middleware); if (middlewareDeclaration != null) { handlers.addAll(middlewareDeclaration.handlers); } final handlerSequence = []; handlerSequence.addAll(middleware ?? []); handlerSequence.addAll(handlers); return super.addRoute(method, path.toString(), handler, middleware: handlerSequence); } /// Mounts a [service] at the given [path]. /// /// Returns a [HookedService] that can be used to hook into /// events dispatched by this service. HookedService use(String path, Service service) { var hooked = new HookedService(service); _services[path .toString() .trim() .replaceAll(new RegExp(r'(^/+)|(/+$)'), '')] = hooked; hooked.addRoutes(); mount(path.toString(), hooked); service.onHooked(hooked); _onService.add(hooked); return hooked; } }