2016-02-28 13:11:17 +00:00
|
|
|
part of angel_framework.http;
|
|
|
|
|
2016-04-21 20:37:02 +00:00
|
|
|
typedef Route RouteAssigner(Pattern path, handler, {List middleware});
|
2016-04-18 03:27:23 +00:00
|
|
|
|
2016-05-02 22:28:14 +00:00
|
|
|
_matchingAnnotation(List<InstanceMirror> metadata, Type T) {
|
|
|
|
for (InstanceMirror metaDatum in metadata) {
|
|
|
|
if (metaDatum.hasReflectee) {
|
|
|
|
var reflectee = metaDatum.reflectee;
|
|
|
|
if (reflectee.runtimeType == T) {
|
|
|
|
return reflectee;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
_getAnnotation(obj, Type T) {
|
|
|
|
if (obj is Function || obj is Future) {
|
|
|
|
MethodMirror methodMirror = (reflect(obj) as ClosureMirror).function;
|
|
|
|
return _matchingAnnotation(methodMirror.metadata, T);
|
|
|
|
} else {
|
|
|
|
ClassMirror classMirror = reflectClass(obj.runtimeType);
|
|
|
|
return _matchingAnnotation(classMirror.metadata, T);
|
|
|
|
}
|
|
|
|
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2016-02-28 13:11:17 +00:00
|
|
|
/// A routable server that can handle dynamic requests.
|
2016-04-21 20:37:02 +00:00
|
|
|
class Routable extends Extensible {
|
2016-02-28 13:11:17 +00:00
|
|
|
/// Additional filters to be run on designated requests.
|
2016-05-02 22:28:14 +00:00
|
|
|
Map <String, RequestMiddleware> requestMiddleware = {};
|
2016-02-28 13:11:17 +00:00
|
|
|
|
|
|
|
/// Dynamic request paths that this server will respond to.
|
2016-04-18 03:27:23 +00:00
|
|
|
List<Route> routes = [];
|
|
|
|
|
|
|
|
/// A set of [Service] objects that have been mapped into routes.
|
|
|
|
Map <Pattern, Service> services = {};
|
|
|
|
|
2016-06-27 00:20:42 +00:00
|
|
|
/// A set of [Controller] objects that have been loaded into the application.
|
|
|
|
Map<String, Controller> controllers = {};
|
|
|
|
|
2016-04-18 03:27:23 +00:00
|
|
|
/// Assigns a middleware to a name for convenience.
|
2016-05-02 22:28:14 +00:00
|
|
|
registerMiddleware(String name, RequestMiddleware middleware) {
|
|
|
|
this.requestMiddleware[name] = middleware;
|
2016-04-18 03:27:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Retrieves the service assigned to the given path.
|
|
|
|
Service service(Pattern path) => services[path];
|
|
|
|
|
2016-06-27 00:20:42 +00:00
|
|
|
/// Retrieves the controller with the given name.
|
|
|
|
Controller controller(String name) => controllers[name];
|
|
|
|
|
2016-05-02 22:28:14 +00:00
|
|
|
/// Incorporates another [Routable]'s routes into this one's.
|
|
|
|
///
|
|
|
|
/// If `hooked` is set to `true` and a [Service] is provided,
|
|
|
|
/// then that service will be wired to a [HookedService] proxy.
|
|
|
|
/// If a `middlewareNamespace` is provided, then any middleware
|
|
|
|
/// from the provided [Routable] will be prefixed by that namespace,
|
|
|
|
/// with a dot.
|
|
|
|
/// For example, if the [Routable] has a middleware 'y', and the `middlewareNamespace`
|
|
|
|
/// is 'x', then that middleware will be available as 'x.y' in the main application.
|
|
|
|
/// These namespaces can be nested.
|
|
|
|
use(Pattern path, Routable routable,
|
2016-06-21 04:19:43 +00:00
|
|
|
{bool hooked: true, String middlewareNamespace: null}) {
|
|
|
|
Routable _routable = routable;
|
|
|
|
|
|
|
|
// 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.
|
|
|
|
if (_routable is Service) {
|
|
|
|
Hooked hookedDeclaration = _getAnnotation(_routable, Hooked);
|
|
|
|
Service service = (hookedDeclaration != null || hooked)
|
|
|
|
? new HookedService(_routable)
|
|
|
|
: _routable;
|
|
|
|
services[path.toString().trim().replaceAll(
|
|
|
|
new RegExp(r'(^\/+)|(\/+$)'), '')] = service;
|
|
|
|
_routable = service;
|
|
|
|
}
|
2016-07-01 19:18:37 +00:00
|
|
|
|
|
|
|
if (_routable is Angel) {
|
|
|
|
all(path, (RequestContext req, ResponseContext res) async {
|
|
|
|
req.app = _routable;
|
|
|
|
res.app = _routable;
|
|
|
|
return true;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2016-06-21 04:19:43 +00:00
|
|
|
for (Route route in _routable.routes) {
|
2016-04-18 03:27:23 +00:00
|
|
|
Route provisional = new Route('', path);
|
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
|
|
|
if (route.path == '/') {
|
|
|
|
route.path = '';
|
|
|
|
route.matcher = new RegExp(r'^\/?$');
|
|
|
|
}
|
2016-04-18 03:27:23 +00:00
|
|
|
route.matcher = new RegExp(route.matcher.pattern.replaceAll(
|
|
|
|
new RegExp('^\\^'),
|
|
|
|
provisional.matcher.pattern.replaceAll(new RegExp(r'\$$'), '')));
|
|
|
|
route.path = "$path${route.path}";
|
|
|
|
|
|
|
|
routes.add(route);
|
|
|
|
}
|
|
|
|
|
2016-05-02 22:28:14 +00:00
|
|
|
// Let's copy middleware, heeding the optional middleware namespace.
|
|
|
|
String middlewarePrefix = "";
|
|
|
|
if (middlewareNamespace != null)
|
|
|
|
middlewarePrefix = "$middlewareNamespace.";
|
|
|
|
|
2016-06-21 04:19:43 +00:00
|
|
|
for (String middlewareName in _routable.requestMiddleware.keys) {
|
2016-05-02 22:28:14 +00:00
|
|
|
requestMiddleware["$middlewarePrefix$middlewareName"] =
|
2016-06-21 04:19:43 +00:00
|
|
|
_routable.requestMiddleware[middlewareName];
|
2016-05-02 22:28:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Copy services, too. :)
|
2016-06-21 04:19:43 +00:00
|
|
|
for (Pattern servicePath in _routable.services.keys) {
|
2016-05-02 22:28:14 +00:00
|
|
|
String newServicePath = path.toString().trim().replaceAll(
|
|
|
|
new RegExp(r'(^\/+)|(\/+$)'), '') + '/$servicePath';
|
2016-06-21 04:19:43 +00:00
|
|
|
services[newServicePath] = _routable.services[servicePath];
|
2016-04-18 03:27:23 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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
|
|
|
/// Adds a route that responds to the given path
|
|
|
|
/// for requests with the given method (case-insensitive).
|
|
|
|
/// Provide '*' as the method to respond to all methods.
|
2016-05-02 22:28:14 +00:00
|
|
|
Route addRoute(String method, Pattern path, Object handler,
|
|
|
|
{List middleware}) {
|
|
|
|
List handlers = [];
|
|
|
|
|
|
|
|
// Merge @Middleware declaration, if any
|
|
|
|
Middleware middlewareDeclaration = _getAnnotation(
|
|
|
|
handler, Middleware);
|
|
|
|
if (middlewareDeclaration != null) {
|
|
|
|
handlers.addAll(middlewareDeclaration.handlers);
|
|
|
|
}
|
|
|
|
|
|
|
|
handlers
|
|
|
|
..addAll(middleware ?? [])
|
|
|
|
..add(handler);
|
|
|
|
var route = new Route(method.toUpperCase().trim(), path, handlers);
|
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
|
|
|
routes.add(route);
|
|
|
|
return route;
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Adds a route that responds to any request matching the given path.
|
2016-05-02 22:28:14 +00:00
|
|
|
Route all(Pattern path, Object handler, {List middleware}) {
|
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
|
|
|
return addRoute('*', path, handler, middleware: middleware);
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Adds a route that responds to a GET request.
|
2016-05-02 22:28:14 +00:00
|
|
|
Route get(Pattern path, Object handler, {List middleware}) {
|
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
|
|
|
return addRoute('GET', path, handler, middleware: middleware);
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Adds a route that responds to a POST request.
|
2016-05-02 22:28:14 +00:00
|
|
|
Route post(Pattern path, Object handler, {List middleware}) {
|
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
|
|
|
return addRoute('POST', path, handler, middleware: middleware);
|
|
|
|
}
|
2016-05-02 22:28:14 +00:00
|
|
|
|
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
|
|
|
/// Adds a route that responds to a PATCH request.
|
2016-05-02 22:28:14 +00:00
|
|
|
Route patch(Pattern path, Object handler, {List middleware}) {
|
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
|
|
|
return addRoute('PATCH', path, handler, middleware: middleware);
|
|
|
|
}
|
2016-05-02 22:28:14 +00:00
|
|
|
|
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
|
|
|
/// Adds a route that responds to a DELETE request.
|
2016-05-02 22:28:14 +00:00
|
|
|
Route delete(Pattern path, Object handler, {List middleware}) {
|
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
|
|
|
return addRoute('DELETE', path, handler, middleware: middleware);
|
|
|
|
}
|
2016-04-18 03:27:23 +00:00
|
|
|
|
|
|
|
Routable() {
|
|
|
|
}
|
|
|
|
|
2016-02-28 13:11:17 +00:00
|
|
|
}
|