107 lines
3.2 KiB
Dart
107 lines
3.2 KiB
Dart
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<RequestHandler> 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<RequestHandler> {
|
|
final Map<Pattern, Service> _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<Pattern, Service> get services => _services;
|
|
|
|
StreamController<Service> _onService =
|
|
new StreamController<Service>.broadcast();
|
|
|
|
/// Fired whenever a service is added to this instance.
|
|
///
|
|
/// **NOTE**: This is a broadcast stream.
|
|
Stream<Service> 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<RequestHandler> addRoute(
|
|
String method, String path, RequestHandler handler,
|
|
{Iterable<RequestHandler> middleware: const <RequestHandler>[]}) {
|
|
final handlers = <RequestHandler>[];
|
|
// Merge @Middleware declaration, if any
|
|
Middleware middlewareDeclaration = getAnnotation(handler, Middleware);
|
|
if (middlewareDeclaration != null) {
|
|
handlers.addAll(middlewareDeclaration.handlers);
|
|
}
|
|
|
|
final handlerSequence = <RequestHandler>[];
|
|
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;
|
|
}
|
|
}
|