2016-09-15 19:53:01 +00:00
|
|
|
library angel_framework.http.routable;
|
|
|
|
|
|
|
|
import 'dart:async';
|
2018-08-20 19:50:29 +00:00
|
|
|
|
2016-10-22 20:41:36 +00:00
|
|
|
import 'package:angel_route/angel_route.dart';
|
2018-08-20 19:50:29 +00:00
|
|
|
|
2016-09-15 19:53:01 +00:00
|
|
|
import '../util.dart';
|
|
|
|
import 'hooked_service.dart';
|
|
|
|
import 'metadata.dart';
|
|
|
|
import 'request_context.dart';
|
|
|
|
import 'response_context.dart';
|
2018-06-23 03:29:38 +00:00
|
|
|
import 'server.dart';
|
2016-09-15 19:53:01 +00:00
|
|
|
import 'service.dart';
|
2016-12-10 14:05:40 +00:00
|
|
|
|
2016-11-28 00:49:27 +00:00
|
|
|
final RegExp _straySlashes = new RegExp(r'(^/+)|(/+$)');
|
2016-02-28 13:11:17 +00:00
|
|
|
|
2016-09-15 19:53:01 +00:00
|
|
|
/// A function that receives an incoming [RequestContext] and responds to it.
|
2018-06-23 03:29:38 +00:00
|
|
|
typedef FutureOr RequestHandler(RequestContext req, ResponseContext res);
|
2016-05-02 22:28:14 +00:00
|
|
|
|
2017-03-28 23:29:22 +00:00
|
|
|
/// Sequentially runs a list of [handlers] of middleware, and returns early if any does not
|
2017-01-14 00:36:38 +00:00
|
|
|
/// return `true`. Works well with [Router].chain.
|
2018-08-20 19:50:29 +00:00
|
|
|
RequestHandler waterfall(Iterable<RequestHandler> handlers) {
|
2018-06-08 07:06:26 +00:00
|
|
|
return (req, res) {
|
2018-08-20 19:50:29 +00:00
|
|
|
Future Function() runPipeline;
|
2018-06-08 07:06:26 +00:00
|
|
|
|
2017-01-14 00:36:38 +00:00
|
|
|
for (var handler in handlers) {
|
2018-06-08 07:06:26 +00:00
|
|
|
if (handler == null) break;
|
|
|
|
|
|
|
|
if (runPipeline == null)
|
2018-08-20 19:50:29 +00:00
|
|
|
runPipeline = () => Future.sync(() => handler(req, res));
|
2018-06-08 07:06:26 +00:00
|
|
|
else {
|
|
|
|
var current = runPipeline;
|
2018-08-20 19:50:29 +00:00
|
|
|
runPipeline = () => current().then((result) => !res.isOpen
|
2018-06-23 03:29:38 +00:00
|
|
|
? new Future.value(result)
|
|
|
|
: req.app.executeHandler(handler, req, res));
|
2018-06-08 07:06:26 +00:00
|
|
|
}
|
2017-01-14 00:36:38 +00:00
|
|
|
}
|
|
|
|
|
2018-08-20 19:50:29 +00:00
|
|
|
runPipeline ??= () => new Future.value();
|
2018-06-08 07:06:26 +00:00
|
|
|
return runPipeline();
|
2017-01-14 00:36:38 +00:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2016-02-28 13:11:17 +00:00
|
|
|
/// A routable server that can handle dynamic requests.
|
2018-08-20 20:21:06 +00:00
|
|
|
class Routable extends Router<RequestHandler> {
|
2016-10-22 20:41:36 +00:00
|
|
|
final Map<Pattern, Service> _services = {};
|
2017-09-24 19:43:14 +00:00
|
|
|
final Map configuration = {};
|
2016-10-22 20:41:36 +00:00
|
|
|
|
2017-10-04 14:09:12 +00:00
|
|
|
Routable() : super();
|
2016-02-28 13:11:17 +00:00
|
|
|
|
2018-06-08 07:06:26 +00:00
|
|
|
void close() {
|
2017-10-28 08:50:16 +00:00
|
|
|
_services.clear();
|
|
|
|
configuration.clear();
|
|
|
|
_onService.close();
|
|
|
|
}
|
|
|
|
|
2016-04-18 03:27:23 +00:00
|
|
|
/// A set of [Service] objects that have been mapped into routes.
|
2016-11-23 09:10:47 +00:00
|
|
|
Map<Pattern, Service> get services => _services;
|
2016-04-18 03:27:23 +00:00
|
|
|
|
2016-10-22 20:41:36 +00:00
|
|
|
StreamController<Service> _onService =
|
|
|
|
new StreamController<Service>.broadcast();
|
2016-07-05 22:11:54 +00:00
|
|
|
|
|
|
|
/// Fired whenever a service is added to this instance.
|
|
|
|
///
|
|
|
|
/// **NOTE**: This is a broadcast stream.
|
|
|
|
Stream<Service> get onService => _onService.stream;
|
|
|
|
|
2016-04-18 03:27:23 +00:00
|
|
|
/// Retrieves the service assigned to the given path.
|
2017-01-28 03:47:00 +00:00
|
|
|
Service service(Pattern path) =>
|
|
|
|
_services[path] ??
|
|
|
|
_services[path.toString().replaceAll(_straySlashes, '')];
|
2016-04-18 03:27:23 +00:00
|
|
|
|
2016-10-22 20:41:36 +00:00
|
|
|
@override
|
2018-08-20 20:21:06 +00:00
|
|
|
Route<RequestHandler> addRoute(
|
|
|
|
String method, String path, RequestHandler handler,
|
|
|
|
{Iterable<RequestHandler> middleware: const <RequestHandler>[]}) {
|
|
|
|
final handlers = <RequestHandler>[];
|
2016-10-22 20:41:36 +00:00
|
|
|
// Merge @Middleware declaration, if any
|
|
|
|
Middleware middlewareDeclaration = getAnnotation(handler, Middleware);
|
|
|
|
if (middlewareDeclaration != null) {
|
|
|
|
handlers.addAll(middlewareDeclaration.handlers);
|
|
|
|
}
|
|
|
|
|
2018-08-20 20:21:06 +00:00
|
|
|
final handlerSequence = <RequestHandler>[];
|
2016-11-23 09:10:47 +00:00
|
|
|
handlerSequence.addAll(middleware ?? []);
|
|
|
|
handlerSequence.addAll(handlers);
|
|
|
|
|
2018-06-23 03:29:38 +00:00
|
|
|
return super.addRoute(method, path.toString(), handler,
|
|
|
|
middleware: handlerSequence);
|
2016-10-22 20:41:36 +00:00
|
|
|
}
|
|
|
|
|
2017-09-22 04:48:22 +00:00
|
|
|
/// Mounts the given [router] on this instance.
|
|
|
|
///
|
|
|
|
/// The [router] may only omitted when called via
|
|
|
|
/// an [Angel] instance.
|
|
|
|
///
|
2018-08-20 20:58:37 +00:00
|
|
|
/// Returns a [HookedService] (if one was mounted).
|
|
|
|
HookedService use(path, [Router<RequestHandler> router, String namespace = null]) {
|
2018-08-20 20:21:06 +00:00
|
|
|
Router<RequestHandler> _router = router;
|
2018-08-20 20:58:37 +00:00
|
|
|
HookedService service;
|
2016-06-21 04:19:43 +00:00
|
|
|
|
|
|
|
// If we need to hook this service, do it here. It has to be first, or
|
|
|
|
// else all routes will point to the old service.
|
2016-10-22 20:41:36 +00:00
|
|
|
if (router is Service) {
|
2017-09-22 04:48:22 +00:00
|
|
|
_router = service = new HookedService(router);
|
2016-10-22 20:41:36 +00:00
|
|
|
_services[path
|
|
|
|
.toString()
|
|
|
|
.trim()
|
|
|
|
.replaceAll(new RegExp(r'(^/+)|(/+$)'), '')] = service;
|
2016-11-23 09:10:47 +00:00
|
|
|
service.addRoutes();
|
2017-02-23 00:37:15 +00:00
|
|
|
|
|
|
|
if (_router is HookedService && _router != router)
|
|
|
|
router.onHooked(_router);
|
2016-06-21 04:19:43 +00:00
|
|
|
}
|
2016-07-01 19:18:37 +00:00
|
|
|
|
2016-10-22 20:41:36 +00:00
|
|
|
final handlers = [];
|
2016-11-23 09:10:47 +00:00
|
|
|
|
2018-08-20 02:55:54 +00:00
|
|
|
if (_router is Angel) {
|
2018-06-08 07:06:26 +00:00
|
|
|
handlers.add((RequestContext req, ResponseContext res) {
|
2018-06-23 03:29:38 +00:00
|
|
|
req.app = _router as Angel;
|
|
|
|
res.app = _router as Angel;
|
2016-07-01 19:18:37 +00:00
|
|
|
return true;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2016-05-02 22:28:14 +00:00
|
|
|
// Let's copy middleware, heeding the optional middleware namespace.
|
2016-10-22 20:41:36 +00:00
|
|
|
String middlewarePrefix = namespace != null ? "$namespace." : "";
|
2016-05-02 22:28:14 +00:00
|
|
|
|
2017-06-06 12:42:33 +00:00
|
|
|
// Also copy properties...
|
2017-08-03 16:40:21 +00:00
|
|
|
if (router is Routable) {
|
2017-09-24 19:43:14 +00:00
|
|
|
Map copiedProperties = new Map.from(router.configuration);
|
2017-08-03 16:40:21 +00:00
|
|
|
for (String propertyName in copiedProperties.keys) {
|
2017-09-24 19:43:14 +00:00
|
|
|
configuration.putIfAbsent("$middlewarePrefix$propertyName",
|
2018-08-20 20:00:18 +00:00
|
|
|
() => copiedProperties[propertyName]);
|
2017-08-03 16:40:21 +00:00
|
|
|
}
|
2016-05-02 22:28:14 +00:00
|
|
|
}
|
|
|
|
|
2016-11-23 09:10:47 +00:00
|
|
|
// _router.dumpTree(header: 'Mounting on "$path":');
|
|
|
|
// root.child(path, debug: debug, handlers: handlers).addChild(router.root);
|
2018-06-23 03:29:38 +00:00
|
|
|
var mounted = mount(path.toString(), _router);
|
2016-04-18 03:27:23 +00:00
|
|
|
|
2017-08-03 16:40:21 +00:00
|
|
|
if (_router is Routable) {
|
2016-10-22 20:41:36 +00:00
|
|
|
// Copy services, too. :)
|
|
|
|
for (Pattern servicePath in _router._services.keys) {
|
|
|
|
String newServicePath =
|
|
|
|
path.toString().trim().replaceAll(new RegExp(r'(^/+)|(/+$)'), '') +
|
|
|
|
'/$servicePath';
|
|
|
|
_services[newServicePath] = _router._services[servicePath];
|
|
|
|
}
|
2016-05-02 22:28:14 +00:00
|
|
|
}
|
|
|
|
|
2017-08-03 16:40:21 +00:00
|
|
|
if (service != null) {
|
|
|
|
if (_onService.hasListener) _onService.add(service);
|
|
|
|
}
|
2017-09-22 04:48:22 +00:00
|
|
|
|
2018-08-20 20:58:37 +00:00
|
|
|
return service;
|
Angel.secure, fallback routes, 404, app.addRoute, app.all, services are a go (just missing params, i.e. $sort?), now have service.app, app.before, app.after, angel.configure now uses futures, errors are implemented
2016-04-29 00:01:58 +00:00
|
|
|
}
|
2016-10-22 20:41:36 +00:00
|
|
|
}
|