2016-09-15 19:53:01 +00:00
|
|
|
library angel_framework.http.routable;
|
|
|
|
|
|
|
|
import 'dart:async';
|
|
|
|
import 'dart:io';
|
|
|
|
import 'dart:mirrors';
|
|
|
|
import '../extensible.dart';
|
|
|
|
import '../util.dart';
|
|
|
|
import 'angel_base.dart';
|
|
|
|
import 'controller.dart';
|
|
|
|
import 'hooked_service.dart';
|
|
|
|
import 'metadata.dart';
|
|
|
|
import 'request_context.dart';
|
|
|
|
import 'response_context.dart';
|
|
|
|
import 'route.dart';
|
|
|
|
import 'service.dart';
|
2016-02-28 13:11:17 +00:00
|
|
|
|
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-09-15 19:53:01 +00:00
|
|
|
/// A function that intercepts a request and determines whether handling of it should continue.
|
|
|
|
typedef Future<bool> RequestMiddleware(RequestContext req, ResponseContext res);
|
|
|
|
|
|
|
|
/// A function that receives an incoming [RequestContext] and responds to it.
|
|
|
|
typedef Future RequestHandler(RequestContext req, ResponseContext res);
|
2016-05-02 22:28:14 +00:00
|
|
|
|
2016-09-15 19:53:01 +00:00
|
|
|
/// A function that handles an [HttpRequest].
|
|
|
|
typedef Future RawRequestHandler(HttpRequest request);
|
2016-05-02 22:28:14 +00:00
|
|
|
|
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-07-05 22:11:54 +00:00
|
|
|
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;
|
|
|
|
|
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.
|
2016-07-05 22:11:54 +00:00
|
|
|
void 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) {
|
2016-09-15 19:53:01 +00:00
|
|
|
Hooked hookedDeclaration = getAnnotation(_routable, Hooked);
|
2016-06-21 04:19:43 +00:00
|
|
|
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
|
|
|
|
2016-09-15 19:53:01 +00:00
|
|
|
if (_routable is AngelBase) {
|
2016-07-01 19:18:37 +00:00
|
|
|
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
|
|
|
}
|
2016-07-05 22:11:54 +00:00
|
|
|
|
|
|
|
if (routable is Service)
|
|
|
|
_onService.add(routable);
|
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
|
2016-09-15 19:53:01 +00:00
|
|
|
Middleware middlewareDeclaration = getAnnotation(
|
2016-05-02 22:28:14 +00:00
|
|
|
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
|
|
|
}
|