Route API change is breaking, haha.
This commit is contained in:
parent
1bb077a3d9
commit
551a7f086f
14 changed files with 447 additions and 571 deletions
|
@ -1,28 +1,3 @@
|
|||
library angel_framework.extensible;
|
||||
|
||||
import 'dart:mirrors';
|
||||
|
||||
/// Supports accessing members of a Map as though they were actual members.
|
||||
class Extensible {
|
||||
/// A set of custom properties that can be assigned to the server.
|
||||
///
|
||||
/// Useful for configuration and extension.
|
||||
Map properties = {};
|
||||
|
||||
noSuchMethod(Invocation invocation) {
|
||||
if (invocation.memberName != null) {
|
||||
String name = MirrorSystem.getName(invocation.memberName);
|
||||
if (properties.containsKey(name)) {
|
||||
if (invocation.isGetter)
|
||||
return properties[name];
|
||||
else if (invocation.isMethod) {
|
||||
return Function.apply(
|
||||
properties[name], invocation.positionalArguments,
|
||||
invocation.namedArguments);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
super.noSuchMethod(invocation);
|
||||
}
|
||||
}
|
||||
export 'package:angel_route/src/extensible.dart';
|
|
@ -8,7 +8,10 @@ import 'routable.dart';
|
|||
typedef Future<String> ViewGenerator(String path, [Map data]);
|
||||
|
||||
class AngelBase extends Routable {
|
||||
AngelBase({bool debug: false}):super(debug: debug);
|
||||
|
||||
Container _container = new Container();
|
||||
|
||||
/// A [Container] used to inject dependencies.
|
||||
Container get container => _container;
|
||||
|
||||
|
|
|
@ -2,22 +2,36 @@ library angel_framework.http.controller;
|
|||
|
||||
import 'dart:async';
|
||||
import 'dart:mirrors';
|
||||
import 'package:angel_route/angel_route.dart';
|
||||
import 'angel_base.dart';
|
||||
import 'angel_http_exception.dart';
|
||||
import 'metadata.dart';
|
||||
import 'request_context.dart';
|
||||
import 'response_context.dart';
|
||||
import 'routable.dart';
|
||||
import 'route.dart';
|
||||
|
||||
class Controller {
|
||||
AngelBase app;
|
||||
List middleware = [];
|
||||
List<Route> routes = [];
|
||||
Map<String, Route> routeMappings = {};
|
||||
Expose exposeDecl;
|
||||
|
||||
Controller() {
|
||||
Future call(AngelBase app) async {
|
||||
this.app = app;
|
||||
app.use(exposeDecl.path, generateRoutable());
|
||||
|
||||
TypeMirror typeMirror = reflectType(this.runtimeType);
|
||||
String name = exposeDecl.as;
|
||||
|
||||
if (name == null || name.isEmpty)
|
||||
name = MirrorSystem.getName(typeMirror.simpleName);
|
||||
|
||||
app.controllers[name] = this;
|
||||
}
|
||||
|
||||
Routable generateRoutable() {
|
||||
final routable = new Routable();
|
||||
|
||||
// Load global expose decl
|
||||
ClassMirror classMirror = reflectClass(this.runtimeType);
|
||||
|
||||
|
@ -28,25 +42,29 @@ class Controller {
|
|||
}
|
||||
}
|
||||
|
||||
if (exposeDecl == null)
|
||||
if (exposeDecl == null) {
|
||||
throw new Exception(
|
||||
"All controllers must carry an @Expose() declaration.");
|
||||
else routes.add(
|
||||
new Route(
|
||||
"*", "*", []..addAll(exposeDecl.middleware)..addAll(middleware)));
|
||||
}
|
||||
|
||||
final handlers = []..addAll(exposeDecl.middleware)..addAll(middleware);
|
||||
|
||||
InstanceMirror instanceMirror = reflect(this);
|
||||
classMirror.instanceMembers.forEach((Symbol key,
|
||||
MethodMirror methodMirror) {
|
||||
if (methodMirror.isRegularMethod && key != #toString &&
|
||||
key != #noSuchMethod && key != #call && key != #equals &&
|
||||
classMirror.instanceMembers
|
||||
.forEach((Symbol key, MethodMirror methodMirror) {
|
||||
if (methodMirror.isRegularMethod &&
|
||||
key != #toString &&
|
||||
key != #noSuchMethod &&
|
||||
key != #call &&
|
||||
key != #equals &&
|
||||
key != #==) {
|
||||
InstanceMirror exposeMirror = methodMirror.metadata.firstWhere((
|
||||
mirror) => mirror.reflectee is Expose, orElse: () => null);
|
||||
InstanceMirror exposeMirror = methodMirror.metadata.firstWhere(
|
||||
(mirror) => mirror.reflectee is Expose,
|
||||
orElse: () => null);
|
||||
|
||||
if (exposeMirror != null) {
|
||||
RequestHandler handler = (RequestContext req,
|
||||
ResponseContext res) async {
|
||||
RequestHandler handler =
|
||||
(RequestContext req, ResponseContext res) async {
|
||||
List args = [];
|
||||
|
||||
// Load parameters, and execute
|
||||
|
@ -56,7 +74,8 @@ class Controller {
|
|||
args.add(req);
|
||||
else if (parameter.type.reflectedType == ResponseContext)
|
||||
args.add(res);
|
||||
else {String name = MirrorSystem.getName(parameter.simpleName);
|
||||
else {
|
||||
String name = MirrorSystem.getName(parameter.simpleName);
|
||||
var arg = req.params[name];
|
||||
|
||||
if (arg == null) {
|
||||
|
@ -73,47 +92,31 @@ class Controller {
|
|||
}
|
||||
|
||||
if (!exposeMirror.reflectee.allowNull.contain(name))
|
||||
throw new AngelHttpException.BadRequest(message: "Missing parameter '$name'");
|
||||
|
||||
} else args.add(arg);
|
||||
throw new AngelHttpException.BadRequest(
|
||||
message: "Missing parameter '$name'");
|
||||
} else
|
||||
args.add(arg);
|
||||
}
|
||||
}
|
||||
|
||||
return await instanceMirror
|
||||
.invoke(key, args)
|
||||
.reflectee;
|
||||
return await instanceMirror.invoke(key, args).reflectee;
|
||||
};
|
||||
Route route = new Route(
|
||||
exposeMirror.reflectee.method,
|
||||
exposeMirror.reflectee.path,
|
||||
[]
|
||||
..addAll(exposeMirror.reflectee.middleware)
|
||||
..add(handler));
|
||||
routes.add(route);
|
||||
|
||||
final route = routable.addRoute(exposeMirror.reflectee.method,
|
||||
exposeMirror.reflectee.path, handler,
|
||||
middleware: []
|
||||
..addAll(handlers)
|
||||
..addAll(exposeMirror.reflectee.middleware));
|
||||
|
||||
String name = exposeMirror.reflectee.as;
|
||||
|
||||
if (name == null || name.isEmpty)
|
||||
name = MirrorSystem.getName(key);
|
||||
if (name == null || name.isEmpty) name = MirrorSystem.getName(key);
|
||||
|
||||
routeMappings[name] = route;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return routable;
|
||||
}
|
||||
|
||||
Future call(AngelBase app) async {
|
||||
this.app = app;
|
||||
app.use(exposeDecl.path, generateRoutable());
|
||||
|
||||
TypeMirror typeMirror = reflectType(this.runtimeType);
|
||||
String name = exposeDecl.as;
|
||||
|
||||
if (name == null || name.isEmpty)
|
||||
name = MirrorSystem.getName(typeMirror.simpleName);
|
||||
|
||||
app.controllers[name] = this;
|
||||
}
|
||||
|
||||
Routable generateRoutable() => new Routable()..routes.addAll(routes);
|
||||
}
|
|
@ -4,7 +4,6 @@ import 'dart:async';
|
|||
import 'package:merge_map/merge_map.dart';
|
||||
import '../util.dart';
|
||||
import 'metadata.dart';
|
||||
import 'route.dart';
|
||||
import 'service.dart';
|
||||
|
||||
/// Wraps another service in a service that broadcasts events on actions.
|
||||
|
@ -37,28 +36,31 @@ class HookedService extends Service {
|
|||
|
||||
HookedService(Service this.inner) {
|
||||
// Clone app instance
|
||||
if (inner.app != null)
|
||||
this.app = inner.app;
|
||||
if (inner.app != null) this.app = inner.app;
|
||||
|
||||
routes.clear();
|
||||
// Set up our routes. We still need to copy middleware from inner service
|
||||
Map restProvider = {'provider': Providers.REST};
|
||||
|
||||
// Add global middleware if declared on the instance itself
|
||||
Middleware before = getAnnotation(inner, Middleware);
|
||||
if (before != null) {
|
||||
routes.add(new Route("*", "*", before.handlers));
|
||||
}
|
||||
final handlers = [];
|
||||
|
||||
if (before != null) handlers.add(before.handlers);
|
||||
|
||||
Middleware indexMiddleware = getAnnotation(inner.index, Middleware);
|
||||
get('/', (req, res) async {
|
||||
return await this.index(mergeMap([req.query, restProvider]));
|
||||
}, middleware: (indexMiddleware == null) ? [] : indexMiddleware.handlers);
|
||||
},
|
||||
middleware: []
|
||||
..addAll(handlers)
|
||||
..addAll((indexMiddleware == null) ? [] : indexMiddleware.handlers));
|
||||
|
||||
Middleware createMiddleware = getAnnotation(inner.create, Middleware);
|
||||
post('/', (req, res) async => await this.create(req.body, restProvider),
|
||||
middleware:
|
||||
(createMiddleware == null) ? [] : createMiddleware.handlers);
|
||||
middleware: []
|
||||
..addAll(handlers)
|
||||
..addAll(
|
||||
(createMiddleware == null) ? [] : createMiddleware.handlers));
|
||||
|
||||
Middleware readMiddleware = getAnnotation(inner.read, Middleware);
|
||||
|
||||
|
@ -66,31 +68,39 @@ class HookedService extends Service {
|
|||
'/:id',
|
||||
(req, res) async => await this
|
||||
.read(req.params['id'], mergeMap([req.query, restProvider])),
|
||||
middleware: (readMiddleware == null) ? [] : readMiddleware.handlers);
|
||||
middleware: []
|
||||
..addAll(handlers)
|
||||
..addAll((readMiddleware == null) ? [] : readMiddleware.handlers));
|
||||
|
||||
Middleware modifyMiddleware = getAnnotation(inner.modify, Middleware);
|
||||
patch(
|
||||
'/:id',
|
||||
(req, res) async =>
|
||||
await this.modify(req.params['id'], req.body, restProvider),
|
||||
middleware:
|
||||
(modifyMiddleware == null) ? [] : modifyMiddleware.handlers);
|
||||
middleware: []
|
||||
..addAll(handlers)
|
||||
..addAll(
|
||||
(modifyMiddleware == null) ? [] : modifyMiddleware.handlers));
|
||||
|
||||
Middleware updateMiddleware = getAnnotation(inner.update, Middleware);
|
||||
post(
|
||||
'/:id',
|
||||
(req, res) async =>
|
||||
await this.update(req.params['id'], req.body, restProvider),
|
||||
middleware:
|
||||
(updateMiddleware == null) ? [] : updateMiddleware.handlers);
|
||||
middleware: []
|
||||
..addAll(handlers)
|
||||
..addAll(
|
||||
(updateMiddleware == null) ? [] : updateMiddleware.handlers));
|
||||
|
||||
Middleware removeMiddleware = getAnnotation(inner.remove, Middleware);
|
||||
delete(
|
||||
'/:id',
|
||||
(req, res) async => await this
|
||||
.remove(req.params['id'], mergeMap([req.query, restProvider])),
|
||||
middleware:
|
||||
(removeMiddleware == null) ? [] : removeMiddleware.handlers);
|
||||
middleware: []
|
||||
..addAll(handlers)
|
||||
..addAll(
|
||||
(removeMiddleware == null) ? [] : removeMiddleware.handlers));
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
/// Various libraries useful for creating highly-extensible servers.
|
||||
library angel_framework.http;
|
||||
|
||||
export 'package:angel_route/angel_route.dart';
|
||||
export 'angel_base.dart';
|
||||
export 'angel_http_exception.dart';
|
||||
export 'base_middleware.dart';
|
||||
|
@ -12,7 +13,6 @@ export 'memory_service.dart';
|
|||
export 'request_context.dart';
|
||||
export 'response_context.dart';
|
||||
export 'routable.dart';
|
||||
export 'route.dart';
|
||||
export 'server.dart';
|
||||
export 'service.dart';
|
||||
|
||||
|
|
|
@ -1,13 +1,18 @@
|
|||
library angel_framework.http.request_context;
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
import 'package:angel_route/src/extensible.dart';
|
||||
import 'package:body_parser/body_parser.dart';
|
||||
import '../../src/extensible.dart';
|
||||
import 'angel_base.dart';
|
||||
import 'route.dart';
|
||||
|
||||
/// A convenience wrapper around an incoming HTTP request.
|
||||
class RequestContext extends Extensible {
|
||||
BodyParseResult _body;
|
||||
ContentType _contentType;
|
||||
String _path;
|
||||
HttpRequest _underlyingRequest;
|
||||
|
||||
/// The [Angel] instance that is responding to this request.
|
||||
AngelBase app;
|
||||
|
||||
|
@ -27,59 +32,58 @@ class RequestContext extends Extensible {
|
|||
String get method => underlyingRequest.method;
|
||||
|
||||
/// All post data submitted to the server.
|
||||
Map body = {};
|
||||
Map get body => _body.body;
|
||||
|
||||
/// The content type of an incoming request.
|
||||
ContentType contentType;
|
||||
ContentType get contentType => _contentType;
|
||||
|
||||
/// Any and all files sent to the server with this request.
|
||||
List<FileUploadInfo> files = [];
|
||||
List<FileUploadInfo> get files => _body.files;
|
||||
|
||||
/// The URL parameters extracted from the request URI.
|
||||
Map params = {};
|
||||
|
||||
/// The requested path.
|
||||
String path;
|
||||
String get path => _path;
|
||||
|
||||
/// The parsed request query string.
|
||||
Map query = {};
|
||||
Map get query => _body.query;
|
||||
|
||||
/// The remote address requesting this resource.
|
||||
InternetAddress remoteAddress;
|
||||
|
||||
/// The route that matched this request.
|
||||
Route route;
|
||||
InternetAddress get remoteAddress =>
|
||||
underlyingRequest.connectionInfo.remoteAddress;
|
||||
|
||||
/// The user's HTTP session.
|
||||
HttpSession session;
|
||||
HttpSession get session => underlyingRequest.session;
|
||||
|
||||
/// The [Uri] instance representing the path this request is responding to.
|
||||
Uri get uri => underlyingRequest.uri;
|
||||
|
||||
/// Is this an **XMLHttpRequest**?
|
||||
bool get xhr => underlyingRequest.headers.value("X-Requested-With")
|
||||
bool get xhr =>
|
||||
underlyingRequest.headers
|
||||
.value("X-Requested-With")
|
||||
?.trim()
|
||||
?.toLowerCase() == 'xmlhttprequest';
|
||||
?.toLowerCase() ==
|
||||
'xmlhttprequest';
|
||||
|
||||
/// The underlying [HttpRequest] instance underneath this context.
|
||||
HttpRequest underlyingRequest;
|
||||
HttpRequest get underlyingRequest => _underlyingRequest;
|
||||
|
||||
/// Magically transforms an [HttpRequest] into a RequestContext.
|
||||
static Future<RequestContext> from(HttpRequest request,
|
||||
Map parameters, AngelBase app, Route sourceRoute) async {
|
||||
RequestContext context = new RequestContext();
|
||||
/// Magically transforms an [HttpRequest] into a [RequestContext].
|
||||
static Future<RequestContext> from(HttpRequest request, AngelBase app) async {
|
||||
RequestContext ctx = new RequestContext();
|
||||
|
||||
context.app = app;
|
||||
context.contentType = request.headers.contentType;
|
||||
context.remoteAddress = request.connectionInfo.remoteAddress;
|
||||
context.params = parameters;
|
||||
context.path = request.uri.toString().replaceAll("?" + request.uri.query, "").replaceAll(new RegExp(r'\/+$'), '');
|
||||
context.route = sourceRoute;
|
||||
context.session = request.session;
|
||||
context.underlyingRequest = request;
|
||||
ctx.app = app;
|
||||
ctx._contentType = request.headers.contentType;
|
||||
ctx._path = request.uri
|
||||
.toString()
|
||||
.replaceAll("?" + request.uri.query, "")
|
||||
.replaceAll(new RegExp(r'/+$'), '');
|
||||
ctx._underlyingRequest = request;
|
||||
|
||||
BodyParseResult bodyParseResult = await parseBody(request);
|
||||
context.query = bodyParseResult.query;
|
||||
context.body = bodyParseResult.body;
|
||||
context.files = bodyParseResult.files;
|
||||
ctx._body = await parseBody(request);
|
||||
|
||||
return context;
|
||||
return ctx;
|
||||
}
|
||||
}
|
|
@ -3,33 +3,35 @@ library angel_framework.http.response_context;
|
|||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'package:angel_route/angel_route.dart';
|
||||
import 'package:json_god/json_god.dart' as god;
|
||||
import 'package:mime/mime.dart';
|
||||
import '../extensible.dart';
|
||||
import 'angel_base.dart';
|
||||
import 'controller.dart';
|
||||
import 'route.dart';
|
||||
|
||||
/// A convenience wrapper around an outgoing HTTP request.
|
||||
class ResponseContext extends Extensible {
|
||||
bool _isOpen = true;
|
||||
|
||||
/// The [Angel] instance that is sending a response.
|
||||
AngelBase app;
|
||||
|
||||
/// Can we still write to this response?
|
||||
bool isOpen = true;
|
||||
bool get isOpen => _isOpen;
|
||||
|
||||
/// A set of UTF-8 encoded bytes that will be written to the response.
|
||||
List<List<int>> responseData = [];
|
||||
final BytesBuilder buffer = new BytesBuilder();
|
||||
|
||||
/// Sets the status code to be sent with this response.
|
||||
status(int code) {
|
||||
void status(int code) {
|
||||
underlyingResponse.statusCode = code;
|
||||
}
|
||||
|
||||
/// The underlying [HttpResponse] under this instance.
|
||||
HttpResponse underlyingResponse;
|
||||
final HttpResponse underlyingResponse;
|
||||
|
||||
ResponseContext(this.underlyingResponse);
|
||||
ResponseContext(this.underlyingResponse, this.app);
|
||||
|
||||
/// Any and all cookies to be sent to the user.
|
||||
List<Cookie> get cookies => underlyingResponse.cookies;
|
||||
|
@ -38,31 +40,37 @@ class ResponseContext extends Extensible {
|
|||
bool willCloseItself = false;
|
||||
|
||||
/// Sends a download as a response.
|
||||
download(File file, {String filename}) {
|
||||
header("Content-Disposition", 'attachment; filename="${filename ?? file.path}"');
|
||||
download(File file, {String filename}) async {
|
||||
header("Content-Disposition",
|
||||
'attachment; filename="${filename ?? file.path}"');
|
||||
header(HttpHeaders.CONTENT_TYPE, lookupMimeType(file.path));
|
||||
header(HttpHeaders.CONTENT_LENGTH, file.lengthSync().toString());
|
||||
responseData.add(file.readAsBytesSync());
|
||||
buffer.add(await file.readAsBytes());
|
||||
end();
|
||||
}
|
||||
|
||||
/// Prevents more data from being written to the response.
|
||||
end() => isOpen = false;
|
||||
void end() {
|
||||
_isOpen = false;
|
||||
}
|
||||
|
||||
/// Sets a response header to the given value, or retrieves its value.
|
||||
header(String key, [String value]) {
|
||||
if (value == null) return underlyingResponse.headers[key];
|
||||
else underlyingResponse.headers.set(key, value);
|
||||
if (value == null)
|
||||
return underlyingResponse.headers[key];
|
||||
else
|
||||
underlyingResponse.headers.set(key, value);
|
||||
}
|
||||
|
||||
/// Serializes JSON to the response.
|
||||
json(value) {
|
||||
void json(value) {
|
||||
write(god.serialize(value));
|
||||
header(HttpHeaders.CONTENT_TYPE, ContentType.JSON.toString());
|
||||
end();
|
||||
}
|
||||
|
||||
/// Returns a JSONP response.
|
||||
jsonp(value, {String callbackName: "callback"}) {
|
||||
void jsonp(value, {String callbackName: "callback"}) {
|
||||
write("$callbackName(${god.serialize(value)})");
|
||||
header(HttpHeaders.CONTENT_TYPE, "application/javascript");
|
||||
end();
|
||||
|
@ -76,7 +84,7 @@ class ResponseContext extends Extensible {
|
|||
}
|
||||
|
||||
/// Redirects to user to the given URL.
|
||||
redirect(String url, {int code: 301}) {
|
||||
void redirect(String url, {int code: 301}) {
|
||||
header(HttpHeaders.LOCATION, url);
|
||||
status(code ?? 301);
|
||||
write('''
|
||||
|
@ -100,22 +108,26 @@ class ResponseContext extends Extensible {
|
|||
}
|
||||
|
||||
/// Redirects to the given named [Route].
|
||||
redirectTo(String name, [Map params, int code]) {
|
||||
void redirectTo(String name, [Map params, int code]) {
|
||||
// Todo: Need to recurse route hierarchy, but also efficiently :)
|
||||
Route matched = app.routes.firstWhere((Route route) => route.name == name);
|
||||
if (matched != null) {
|
||||
return redirect(matched.makeUri(params), code: code);
|
||||
redirect(matched.makeUri(params), code: code);
|
||||
return;
|
||||
}
|
||||
|
||||
throw new ArgumentError.notNull('Route to redirect to ($name)');
|
||||
}
|
||||
|
||||
/// Redirects to the given [Controller] action.
|
||||
redirectToAction(String action, [Map params, int code]) {
|
||||
void redirectToAction(String action, [Map params, int code]) {
|
||||
// UserController@show
|
||||
List<String> split = action.split("@");
|
||||
|
||||
// Todo: AngelResponseException
|
||||
if (split.length < 2)
|
||||
throw new Exception("Controller redirects must take the form of 'Controller@action'. You gave: $action");
|
||||
throw new Exception(
|
||||
"Controller redirects must take the form of 'Controller@action'. You gave: $action");
|
||||
|
||||
Controller controller = app.controller(split[0]);
|
||||
|
||||
|
@ -125,38 +137,30 @@ class ResponseContext extends Extensible {
|
|||
Route matched = controller.routeMappings[split[1]];
|
||||
|
||||
if (matched == null)
|
||||
throw new Exception("Controller '${split[0]}' does not contain any action named '${split[1]}'");
|
||||
throw new Exception(
|
||||
"Controller '${split[0]}' does not contain any action named '${split[1]}'");
|
||||
|
||||
return redirect(matched.makeUri(params), code: code);
|
||||
redirect(matched.makeUri(params), code: code);
|
||||
}
|
||||
|
||||
/// Streams a file to this response as chunked data.
|
||||
///
|
||||
/// Useful for video sites.
|
||||
streamFile(File file,
|
||||
Future streamFile(File file,
|
||||
{int chunkSize, int sleepMs: 0, bool resumable: true}) async {
|
||||
if (!isOpen) return;
|
||||
|
||||
header(HttpHeaders.CONTENT_TYPE, lookupMimeType(file.path));
|
||||
willCloseItself = true;
|
||||
await file.openRead().pipe(underlyingResponse);
|
||||
/*await chunked(file.openRead(), chunkSize: chunkSize,
|
||||
sleepMs: sleepMs,
|
||||
resumable: resumable);*/
|
||||
}
|
||||
|
||||
/// Writes data to the response.
|
||||
write(value) {
|
||||
if (isOpen)
|
||||
responseData.add(UTF8.encode(value.toString()));
|
||||
}
|
||||
|
||||
/// Magically transforms an [HttpResponse] object into a ResponseContext.
|
||||
static Future<ResponseContext> from
|
||||
(HttpResponse response, AngelBase app) async
|
||||
{
|
||||
ResponseContext context = new ResponseContext(response);
|
||||
context.app = app;
|
||||
return context;
|
||||
void write(value, {Encoding encoding: UTF8}) {
|
||||
if (isOpen) {
|
||||
if (value is List<int>)
|
||||
buffer.add(value);
|
||||
else buffer.add(encoding.encode(value.toString()));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,8 +2,7 @@ library angel_framework.http.routable;
|
|||
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
import 'dart:mirrors';
|
||||
import '../extensible.dart';
|
||||
import 'package:angel_route/angel_route.dart';
|
||||
import '../util.dart';
|
||||
import 'angel_base.dart';
|
||||
import 'controller.dart';
|
||||
|
@ -11,11 +10,8 @@ import 'hooked_service.dart';
|
|||
import 'metadata.dart';
|
||||
import 'request_context.dart';
|
||||
import 'response_context.dart';
|
||||
import 'route.dart';
|
||||
import 'service.dart';
|
||||
|
||||
typedef Route RouteAssigner(Pattern path, handler, {List middleware});
|
||||
|
||||
/// A function that intercepts a request and determines whether handling of it should continue.
|
||||
typedef Future<bool> RequestMiddleware(RequestContext req, ResponseContext res);
|
||||
|
||||
|
@ -26,20 +22,26 @@ typedef Future RequestHandler(RequestContext req, ResponseContext res);
|
|||
typedef Future RawRequestHandler(HttpRequest request);
|
||||
|
||||
/// A routable server that can handle dynamic requests.
|
||||
class Routable extends Extensible {
|
||||
/// Additional filters to be run on designated requests.
|
||||
Map <String, RequestMiddleware> requestMiddleware = {};
|
||||
class Routable extends Router {
|
||||
final Map<String, Controller> _controllers = {};
|
||||
final Map<Pattern, Service> _services = {};
|
||||
|
||||
/// Dynamic request paths that this server will respond to.
|
||||
List<Route> routes = [];
|
||||
Routable({bool debug: false}) : super(debug: debug);
|
||||
|
||||
/// Additional filters to be run on designated requests.
|
||||
@override
|
||||
final Map<String, RequestMiddleware> requestMiddleware = {};
|
||||
|
||||
/// A set of [Service] objects that have been mapped into routes.
|
||||
Map <Pattern, Service> services = {};
|
||||
Map<Pattern, Service> get services =>
|
||||
new Map<Pattern, Service>.unmodifiable(_services);
|
||||
|
||||
/// A set of [Controller] objects that have been loaded into the application.
|
||||
Map<String, Controller> controllers = {};
|
||||
Map<String, Controller> get controllers =>
|
||||
new Map<String, Controller>.unmodifiable(_controllers);
|
||||
|
||||
StreamController<Service> _onService = new StreamController<Service>.broadcast();
|
||||
StreamController<Service> _onService =
|
||||
new StreamController<Service>.broadcast();
|
||||
|
||||
/// Fired whenever a service is added to this instance.
|
||||
///
|
||||
|
@ -47,133 +49,80 @@ class Routable extends Extensible {
|
|||
Stream<Service> get onService => _onService.stream;
|
||||
|
||||
/// Assigns a middleware to a name for convenience.
|
||||
registerMiddleware(String name, RequestMiddleware middleware) {
|
||||
this.requestMiddleware[name] = middleware;
|
||||
}
|
||||
@override
|
||||
registerMiddleware(String name, RequestMiddleware middleware) =>
|
||||
super.registerMiddleware(name, middleware);
|
||||
|
||||
/// Retrieves the service assigned to the given path.
|
||||
Service service(Pattern path) => services[path];
|
||||
Service service(Pattern path) => _services[path];
|
||||
|
||||
/// Retrieves the controller with the given name.
|
||||
Controller controller(String name) => controllers[name];
|
||||
|
||||
/// 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.
|
||||
void use(Pattern path, Routable routable,
|
||||
{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;
|
||||
}
|
||||
|
||||
if (_routable is AngelBase) {
|
||||
all(path, (RequestContext req, ResponseContext res) async {
|
||||
req.app = _routable;
|
||||
res.app = _routable;
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
for (Route route in _routable.routes) {
|
||||
Route provisional = new Route('', path);
|
||||
if (route.path == '/') {
|
||||
route.path = '';
|
||||
route.matcher = new RegExp(r'^\/?$');
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
// Let's copy middleware, heeding the optional middleware namespace.
|
||||
String middlewarePrefix = "";
|
||||
if (middlewareNamespace != null)
|
||||
middlewarePrefix = "$middlewareNamespace.";
|
||||
|
||||
for (String middlewareName in _routable.requestMiddleware.keys) {
|
||||
requestMiddleware["$middlewarePrefix$middlewareName"] =
|
||||
_routable.requestMiddleware[middlewareName];
|
||||
}
|
||||
|
||||
// Copy services, too. :)
|
||||
for (Pattern servicePath in _routable.services.keys) {
|
||||
String newServicePath = path.toString().trim().replaceAll(
|
||||
new RegExp(r'(^\/+)|(\/+$)'), '') + '/$servicePath';
|
||||
services[newServicePath] = _routable.services[servicePath];
|
||||
}
|
||||
|
||||
if (routable is Service)
|
||||
_onService.add(routable);
|
||||
}
|
||||
|
||||
/// 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.
|
||||
@override
|
||||
Route addRoute(String method, Pattern path, Object handler,
|
||||
{List middleware}) {
|
||||
List handlers = [];
|
||||
|
||||
final List handlers = [];
|
||||
// Merge @Middleware declaration, if any
|
||||
Middleware middlewareDeclaration = getAnnotation(
|
||||
handler, Middleware);
|
||||
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);
|
||||
routes.add(route);
|
||||
return route;
|
||||
return super.addRoute(method, path, handler,
|
||||
middleware: []..addAll(middleware ?? [])..addAll(handlers));
|
||||
}
|
||||
|
||||
/// Adds a route that responds to any request matching the given path.
|
||||
Route all(Pattern path, Object handler, {List middleware}) {
|
||||
return addRoute('*', path, handler, middleware: middleware);
|
||||
void use(Pattern path, Router router,
|
||||
{bool hooked: true, String namespace: null}) {
|
||||
Router _router = router;
|
||||
Service service;
|
||||
|
||||
// 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 (router is Service) {
|
||||
Hooked hookedDeclaration = getAnnotation(router, Hooked);
|
||||
_router = service = (hookedDeclaration != null || hooked)
|
||||
? new HookedService(router)
|
||||
: router;
|
||||
_services[path
|
||||
.toString()
|
||||
.trim()
|
||||
.replaceAll(new RegExp(r'(^/+)|(/+$)'), '')] = service;
|
||||
}
|
||||
|
||||
/// Adds a route that responds to a GET request.
|
||||
Route get(Pattern path, Object handler, {List middleware}) {
|
||||
return addRoute('GET', path, handler, middleware: middleware);
|
||||
final handlers = [];
|
||||
if (_router is AngelBase) {
|
||||
handlers.add((RequestContext req, ResponseContext res) async {
|
||||
req.app = _router;
|
||||
res.app = _router;
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
/// Adds a route that responds to a POST request.
|
||||
Route post(Pattern path, Object handler, {List middleware}) {
|
||||
return addRoute('POST', path, handler, middleware: middleware);
|
||||
// Let's copy middleware, heeding the optional middleware namespace.
|
||||
String middlewarePrefix = namespace != null ? "$namespace." : "";
|
||||
|
||||
Map copiedMiddleware = new Map.from(router.requestMiddleware);
|
||||
for (String middlewareName in copiedMiddleware.keys) {
|
||||
requestMiddleware["$middlewarePrefix$middlewareName"] =
|
||||
copiedMiddleware[middlewareName];
|
||||
}
|
||||
|
||||
/// Adds a route that responds to a PATCH request.
|
||||
Route patch(Pattern path, Object handler, {List middleware}) {
|
||||
return addRoute('PATCH', path, handler, middleware: middleware);
|
||||
root.child(path, debug: debug, handlers: handlers).addChild(router.root);
|
||||
|
||||
_router.dumpTree(header: 'Mounting on "$path":');
|
||||
|
||||
if (router is Routable) {
|
||||
// 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];
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds a route that responds to a DELETE request.
|
||||
Route delete(Pattern path, Object handler, {List middleware}) {
|
||||
return addRoute('DELETE', path, handler, middleware: middleware);
|
||||
if (service != null) _onService.add(service);
|
||||
}
|
||||
|
||||
Routable() {
|
||||
}
|
||||
|
||||
}
|
|
@ -1,91 +0,0 @@
|
|||
library angel_framework.http.route;
|
||||
|
||||
/// Represents an endpoint open for connection via the Internet.
|
||||
class Route {
|
||||
/// A regular expression used to match URI's to this route.
|
||||
RegExp matcher;
|
||||
/// The HTTP method this route responds to.
|
||||
String method;
|
||||
/// An array of functions, Futures and objects that can respond to this route.
|
||||
List handlers = [];
|
||||
/// The path this route is mounted on.
|
||||
String path;
|
||||
/// (Optional) - A name for this route.
|
||||
String name;
|
||||
|
||||
Route(String method, Pattern path, [List handlers]) {
|
||||
this.method = method;
|
||||
if (path is RegExp) {
|
||||
this.matcher = path;
|
||||
this.path = path.pattern;
|
||||
}
|
||||
else {
|
||||
this.matcher = new RegExp('^' +
|
||||
path.toString()
|
||||
.replaceAll(new RegExp(r'\/\*$'), "*")
|
||||
.replaceAll(new RegExp('\/'), r'\/')
|
||||
.replaceAll(new RegExp(':[a-zA-Z_]+'), '([^\/]+)')
|
||||
.replaceAll(new RegExp('\\*'), '.*')
|
||||
+ r'$');
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
if (handlers != null) {
|
||||
this.handlers.addAll(handlers);
|
||||
}
|
||||
}
|
||||
|
||||
/// Assigns a name to this Route.
|
||||
as(String name) {
|
||||
this.name = name;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// Generates a URI to this route with the given parameters.
|
||||
String makeUri([Map<String, dynamic> params]) {
|
||||
String result = path;
|
||||
if (params != null) {
|
||||
for (String key in (params.keys)) {
|
||||
result = result.replaceAll(new RegExp(":$key" + r"\??"), params[key].toString());
|
||||
}
|
||||
}
|
||||
|
||||
return result.replaceAll("*", "");
|
||||
}
|
||||
|
||||
/// Enables one or more handlers to be called whenever this route is visited.
|
||||
Route middleware(handler) {
|
||||
if (handler is Iterable)
|
||||
handlers.addAll(handler);
|
||||
else handlers.add(handler);
|
||||
return this;
|
||||
}
|
||||
|
||||
/// Extracts route parameters from a given path.
|
||||
Map parseParameters(String requestPath) {
|
||||
Map result = {};
|
||||
|
||||
Iterable<String> values = _parseParameters(requestPath);
|
||||
RegExp rgx = new RegExp(':([a-zA-Z_]+)');
|
||||
Iterable<Match> matches = rgx.allMatches(
|
||||
path.replaceAll(new RegExp('\/'), r'\/'));
|
||||
for (int i = 0; i < matches.length; i++) {
|
||||
Match match = matches.elementAt(i);
|
||||
String paramName = match.group(1);
|
||||
String value = values.elementAt(i);
|
||||
num numValue = num.parse(value, (_) => double.NAN);
|
||||
if (!numValue.isNaN)
|
||||
result[paramName] = numValue;
|
||||
else
|
||||
result[paramName] = value;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
_parseParameters(String requestPath) sync* {
|
||||
Match routeMatch = matcher.firstMatch(requestPath);
|
||||
for (int i = 1; i <= routeMatch.groupCount; i++)
|
||||
yield routeMatch.group(i);
|
||||
}
|
||||
}
|
|
@ -11,10 +11,11 @@ import 'controller.dart';
|
|||
import 'request_context.dart';
|
||||
import 'response_context.dart';
|
||||
import 'routable.dart';
|
||||
import 'route.dart';
|
||||
import 'service.dart';
|
||||
export 'package:container/container.dart';
|
||||
|
||||
final RegExp _straySlashes = new RegExp(r'(^/+)|(/+$)');
|
||||
|
||||
/// A function that binds an [Angel] server to an Internet address and port.
|
||||
typedef Future<HttpServer> ServerGenerator(InternetAddress address, int port);
|
||||
|
||||
|
@ -31,6 +32,7 @@ class Angel extends AngelBase {
|
|||
var _beforeProcessed = new StreamController<HttpRequest>.broadcast();
|
||||
var _fatalErrorStream = new StreamController<Map>.broadcast();
|
||||
var _onController = new StreamController<Controller>.broadcast();
|
||||
final Random _rand = new Random.secure();
|
||||
ServerGenerator _serverGenerator =
|
||||
(address, port) async => await HttpServer.bind(address, port);
|
||||
|
||||
|
@ -74,6 +76,23 @@ class Angel extends AngelBase {
|
|||
/// The native HttpServer running this instancce.
|
||||
HttpServer httpServer;
|
||||
|
||||
/// Handles a server error.
|
||||
_onError(e, [StackTrace stackTrace]) {
|
||||
_fatalErrorStream.add({"error": e, "stack": stackTrace});
|
||||
}
|
||||
|
||||
void _printDebug(x) {
|
||||
if (debug) print(x);
|
||||
}
|
||||
|
||||
String _randomString(int length) {
|
||||
var codeUnits = new List.generate(length, (index) {
|
||||
return _rand.nextInt(33) + 89;
|
||||
});
|
||||
|
||||
return new String.fromCharCodes(codeUnits);
|
||||
}
|
||||
|
||||
/// Starts the server.
|
||||
///
|
||||
/// Returns false on failure; otherwise, returns the HttpServer.
|
||||
|
@ -81,10 +100,7 @@ class Angel extends AngelBase {
|
|||
var server = await _serverGenerator(
|
||||
address ?? InternetAddress.LOOPBACK_IP_V4, port ?? 0);
|
||||
this.httpServer = server;
|
||||
|
||||
server.listen(handleRequest);
|
||||
|
||||
return server;
|
||||
return server..listen(handleRequest);
|
||||
}
|
||||
|
||||
/// Loads some base dependencies into the service container.
|
||||
|
@ -95,75 +111,7 @@ class Angel extends AngelBase {
|
|||
if (runtimeType != Angel) container.singleton(this, as: Angel);
|
||||
}
|
||||
|
||||
Future handleRequest(HttpRequest request) async {
|
||||
_beforeProcessed.add(request);
|
||||
|
||||
String requestedUrl = request.uri
|
||||
.toString()
|
||||
.replaceAll("?" + request.uri.query, "")
|
||||
.replaceAll(new RegExp(r'\/+$'), '');
|
||||
|
||||
if (requestedUrl.isEmpty) requestedUrl = '/';
|
||||
|
||||
RequestContext req = await RequestContext.from(request, {}, this, null);
|
||||
ResponseContext res = await ResponseContext.from(request.response, this);
|
||||
|
||||
bool canContinue = true;
|
||||
|
||||
executeHandler(handler, req) async {
|
||||
if (canContinue) {
|
||||
try {
|
||||
canContinue = await _applyHandler(handler, req, res);
|
||||
} catch (e, stackTrace) {
|
||||
if (e is AngelHttpException) {
|
||||
// Special handling for AngelHttpExceptions :)
|
||||
try {
|
||||
res.status(e.statusCode);
|
||||
String accept = request.headers.value(HttpHeaders.ACCEPT);
|
||||
if (accept == "*/*" ||
|
||||
accept.contains(ContentType.JSON.mimeType) ||
|
||||
accept.contains("application/javascript")) {
|
||||
res.json(e.toMap());
|
||||
} else {
|
||||
await _errorHandler(e, req, res);
|
||||
}
|
||||
_finalizeResponse(request, res);
|
||||
} catch (_) {}
|
||||
}
|
||||
_onError(e, stackTrace);
|
||||
canContinue = false;
|
||||
return false;
|
||||
}
|
||||
} else
|
||||
return false;
|
||||
}
|
||||
|
||||
for (var handler in before) {
|
||||
await executeHandler(handler, req);
|
||||
}
|
||||
|
||||
for (Route route in routes) {
|
||||
if (!canContinue) break;
|
||||
|
||||
if (route.matcher.hasMatch(requestedUrl) &&
|
||||
(request.method == route.method || route.method == '*')) {
|
||||
req.params = route.parseParameters(requestedUrl);
|
||||
req.route = route;
|
||||
|
||||
for (var handler in route.handlers) {
|
||||
await executeHandler(handler, req);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (var handler in after) {
|
||||
await executeHandler(handler, req);
|
||||
}
|
||||
|
||||
_finalizeResponse(request, res);
|
||||
}
|
||||
|
||||
Future<bool> _applyHandler(
|
||||
Future<bool> executeHandler(
|
||||
handler, RequestContext req, ResponseContext res) async {
|
||||
if (handler is RequestMiddleware) {
|
||||
var result = await handler(req, res);
|
||||
|
@ -216,7 +164,7 @@ class Angel extends AngelBase {
|
|||
}
|
||||
|
||||
if (requestMiddleware.containsKey(handler)) {
|
||||
return await _applyHandler(requestMiddleware[handler], req, res);
|
||||
return await executeHandler(requestMiddleware[handler], req, res);
|
||||
}
|
||||
|
||||
res.willCloseItself = true;
|
||||
|
@ -225,11 +173,76 @@ class Angel extends AngelBase {
|
|||
return false;
|
||||
}
|
||||
|
||||
_finalizeResponse(HttpRequest request, ResponseContext res) async {
|
||||
Future handleRequest(HttpRequest request) async {
|
||||
_beforeProcessed.add(request);
|
||||
|
||||
final req = await RequestContext.from(request, this);
|
||||
final res = new ResponseContext(request.response, this);
|
||||
String requestedUrl = request.uri
|
||||
.toString()
|
||||
.replaceAll("?" + request.uri.query, "")
|
||||
.replaceAll(_straySlashes, '');
|
||||
|
||||
if (requestedUrl.isEmpty) requestedUrl = '/';
|
||||
|
||||
final route = resolve(requestedUrl,
|
||||
(route) => route.method == request.method || route.method == '*');
|
||||
print('Resolve ${requestedUrl} -> $route');
|
||||
req.params.addAll(route?.parseParameters(requestedUrl) ?? {});
|
||||
|
||||
final handlerSequence = []..addAll(before);
|
||||
if (route != null) handlerSequence.addAll(route.handlerSequence);
|
||||
handlerSequence.addAll(after);
|
||||
|
||||
_printDebug('Handler sequence on $requestedUrl: $handlerSequence');
|
||||
|
||||
for (final handler in handlerSequence) {
|
||||
try {
|
||||
_printDebug('Executing handler: $handler');
|
||||
final result = await executeHandler(handler, req, res);
|
||||
_printDebug('Result: $result');
|
||||
|
||||
if (!result) {
|
||||
_printDebug('Last executed handler: $handler');
|
||||
break;
|
||||
} else {
|
||||
_printDebug(
|
||||
'Handler completed successfully, did not terminate response: $handler');
|
||||
}
|
||||
} catch (e, st) {
|
||||
_printDebug('Caught error in handler $handler: $e');
|
||||
_printDebug(st);
|
||||
|
||||
if (e is AngelHttpException) {
|
||||
// Special handling for AngelHttpExceptions :)
|
||||
try {
|
||||
res.status(e.statusCode);
|
||||
String accept = request.headers.value(HttpHeaders.ACCEPT);
|
||||
if (accept == "*/*" ||
|
||||
accept.contains(ContentType.JSON.mimeType) ||
|
||||
accept.contains("application/javascript")) {
|
||||
res.json(e.toMap());
|
||||
} else {
|
||||
await _errorHandler(e, req, res);
|
||||
}
|
||||
_finalizeResponse(request, res);
|
||||
} catch (_) {
|
||||
// Todo: This exception needs to be caught as well.
|
||||
}
|
||||
} else {
|
||||
// Todo: Uncaught exceptions need to be... Caught.
|
||||
}
|
||||
|
||||
_onError(e, st);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
_afterProcessed.add(request);
|
||||
|
||||
if (!res.willCloseItself) {
|
||||
res.responseData.forEach((blob) => request.response.add(blob));
|
||||
request.response.add(res.buffer.takeBytes());
|
||||
await request.response.close();
|
||||
}
|
||||
} catch (e) {
|
||||
|
@ -237,15 +250,6 @@ class Angel extends AngelBase {
|
|||
}
|
||||
}
|
||||
|
||||
String _randomString(int length) {
|
||||
var rand = new Random();
|
||||
var codeUnits = new List.generate(length, (index) {
|
||||
return rand.nextInt(33) + 89;
|
||||
});
|
||||
|
||||
return new String.fromCharCodes(codeUnits);
|
||||
}
|
||||
|
||||
// Run a function after injecting from service container
|
||||
Future runContained(Function handler, RequestContext req, ResponseContext res,
|
||||
{Map<String, dynamic> namedParameters,
|
||||
|
@ -302,12 +306,12 @@ class Angel extends AngelBase {
|
|||
|
||||
@override
|
||||
use(Pattern path, Routable routable,
|
||||
{bool hooked: true, String middlewareNamespace: null}) {
|
||||
{bool hooked: true, String namespace: null}) {
|
||||
if (routable is Service) {
|
||||
routable.app = this;
|
||||
}
|
||||
return super.use(path, routable,
|
||||
hooked: hooked, middlewareNamespace: middlewareNamespace);
|
||||
|
||||
return super.use(path, routable, hooked: hooked, namespace: namespace);
|
||||
}
|
||||
|
||||
/// Registers a callback to run upon errors.
|
||||
|
@ -315,15 +319,7 @@ class Angel extends AngelBase {
|
|||
_errorHandler = handler;
|
||||
}
|
||||
|
||||
/// Handles a server error.
|
||||
_onError(e, [StackTrace stackTrace]) {
|
||||
_fatalErrorStream.add({
|
||||
"error": e,
|
||||
"stack": stackTrace
|
||||
});
|
||||
}
|
||||
|
||||
Angel() : super() {
|
||||
Angel({bool debug: false}) : super(debug: debug) {
|
||||
bootstrapContainer();
|
||||
}
|
||||
|
||||
|
@ -332,8 +328,8 @@ class Angel extends AngelBase {
|
|||
/// If no password is provided, a random one will be generated upon running
|
||||
/// the server.
|
||||
Angel.secure(String certificateChainPath, String serverKeyPath,
|
||||
{String password})
|
||||
: super() {
|
||||
{bool debug: false, String password})
|
||||
: super(debug: debug) {
|
||||
bootstrapContainer();
|
||||
_serverGenerator = (InternetAddress address, int port) async {
|
||||
var certificateChain =
|
||||
|
|
|
@ -2,13 +2,11 @@ library angel_framework.http.service;
|
|||
|
||||
import 'dart:async';
|
||||
import 'package:merge_map/merge_map.dart';
|
||||
import '../defs.dart';
|
||||
import '../util.dart';
|
||||
import 'angel_base.dart';
|
||||
import 'angel_http_exception.dart';
|
||||
import 'metadata.dart';
|
||||
import 'routable.dart';
|
||||
import 'route.dart';
|
||||
|
||||
/// Indicates how the service was accessed.
|
||||
///
|
||||
|
@ -72,19 +70,24 @@ class Service extends Routable {
|
|||
|
||||
// Add global middleware if declared on the instance itself
|
||||
Middleware before = getAnnotation(this, Middleware);
|
||||
if (before != null) {
|
||||
routes.add(new Route("*", "*", before.handlers));
|
||||
}
|
||||
final handlers = [];
|
||||
|
||||
if (before != null) handlers.add(before.handlers);
|
||||
|
||||
Middleware indexMiddleware = getAnnotation(this.index, Middleware);
|
||||
get('/', (req, res) async {
|
||||
return await this.index(mergeMap([req.query, restProvider]));
|
||||
}, middleware: (indexMiddleware == null) ? [] : indexMiddleware.handlers);
|
||||
},
|
||||
middleware: []
|
||||
..addAll(handlers)
|
||||
..addAll((indexMiddleware == null) ? [] : indexMiddleware.handlers));
|
||||
|
||||
Middleware createMiddleware = getAnnotation(this.create, Middleware);
|
||||
post('/', (req, res) async => await this.create(req.body, restProvider),
|
||||
middleware:
|
||||
(createMiddleware == null) ? [] : createMiddleware.handlers);
|
||||
middleware: []
|
||||
..addAll(handlers)
|
||||
..addAll(
|
||||
(createMiddleware == null) ? [] : createMiddleware.handlers));
|
||||
|
||||
Middleware readMiddleware = getAnnotation(this.read, Middleware);
|
||||
|
||||
|
@ -92,30 +95,38 @@ class Service extends Routable {
|
|||
'/:id',
|
||||
(req, res) async => await this
|
||||
.read(req.params['id'], mergeMap([req.query, restProvider])),
|
||||
middleware: (readMiddleware == null) ? [] : readMiddleware.handlers);
|
||||
middleware: []
|
||||
..addAll(handlers)
|
||||
..addAll((readMiddleware == null) ? [] : readMiddleware.handlers));
|
||||
|
||||
Middleware modifyMiddleware = getAnnotation(this.modify, Middleware);
|
||||
patch(
|
||||
'/:id',
|
||||
(req, res) async =>
|
||||
await this.modify(req.params['id'], req.body, restProvider),
|
||||
middleware:
|
||||
(modifyMiddleware == null) ? [] : modifyMiddleware.handlers);
|
||||
middleware: []
|
||||
..addAll(handlers)
|
||||
..addAll(
|
||||
(modifyMiddleware == null) ? [] : modifyMiddleware.handlers));
|
||||
|
||||
Middleware updateMiddleware = getAnnotation(this.update, Middleware);
|
||||
post(
|
||||
'/:id',
|
||||
(req, res) async =>
|
||||
await this.update(req.params['id'], req.body, restProvider),
|
||||
middleware:
|
||||
(updateMiddleware == null) ? [] : updateMiddleware.handlers);
|
||||
middleware: []
|
||||
..addAll(handlers)
|
||||
..addAll(
|
||||
(updateMiddleware == null) ? [] : updateMiddleware.handlers));
|
||||
|
||||
Middleware removeMiddleware = getAnnotation(this.remove, Middleware);
|
||||
delete(
|
||||
'/:id',
|
||||
(req, res) async => await this
|
||||
.remove(req.params['id'], mergeMap([req.query, restProvider])),
|
||||
middleware:
|
||||
(removeMiddleware == null) ? [] : removeMiddleware.handlers);
|
||||
middleware: []
|
||||
..addAll(handlers)
|
||||
..addAll(
|
||||
(removeMiddleware == null) ? [] : removeMiddleware.handlers));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,8 @@ description: Core libraries for the Angel framework.
|
|||
author: Tobe O <thosakwe@gmail.com>
|
||||
homepage: https://github.com/angel-dart/angel_framework
|
||||
dependencies:
|
||||
angel_route:
|
||||
path: ../angel_route
|
||||
body_parser: ">=1.0.0-dev <2.0.0"
|
||||
container: ">=0.1.2 <1.0.0"
|
||||
json_god: ">=2.0.0-beta <3.0.0"
|
||||
|
|
|
@ -26,28 +26,39 @@ main() {
|
|||
http.Client client;
|
||||
|
||||
setUp(() async {
|
||||
angel = new Angel();
|
||||
nested = new Angel();
|
||||
todos = new Angel();
|
||||
final debug = false;
|
||||
angel = new Angel(debug: debug);
|
||||
nested = new Angel(debug: debug);
|
||||
todos = new Angel(debug: debug);
|
||||
|
||||
angel..registerMiddleware('interceptor', (req, res) async {
|
||||
angel
|
||||
..registerMiddleware('interceptor', (req, res) async {
|
||||
res.write('Middleware');
|
||||
return false;
|
||||
})..registerMiddleware('intercept_service',
|
||||
})
|
||||
..registerMiddleware('intercept_service',
|
||||
(RequestContext req, res) async {
|
||||
print("Intercepting a service!");
|
||||
return true;
|
||||
});
|
||||
|
||||
todos.get('/action/:action', (req, res) => res.json(req.params));
|
||||
nested.post('/ted/:route', (req, res) => res.json(req.params));
|
||||
|
||||
Route ted;
|
||||
|
||||
ted = nested.post('/ted/:route', (RequestContext req, res) {
|
||||
print('Params: ${req.params}');
|
||||
print('Path: ${ted.path}, matcher: ${ted.matcher.pattern}, uri: ${req.path}');
|
||||
return req.params;
|
||||
});
|
||||
|
||||
angel.use('/nes', nested);
|
||||
angel.get('/meta', testMiddlewareMetadata);
|
||||
angel.get('/intercepted', 'This should not be shown',
|
||||
middleware: ['interceptor']);
|
||||
angel.get('/hello', 'world');
|
||||
angel.get('/name/:first/last/:last', (req, res) => req.params);
|
||||
angel.post('/lambda', (req, res) => req.body);
|
||||
angel.use('/nes', nested);
|
||||
angel.use('/todos/:id', todos);
|
||||
angel
|
||||
.get('/greet/:name',
|
||||
|
@ -60,13 +71,11 @@ main() {
|
|||
print("Query: ${req.query}");
|
||||
return "Logged";
|
||||
});
|
||||
|
||||
angel.use('/query', new QueryService());
|
||||
angel.get('*', 'MJ');
|
||||
|
||||
print("DUMPING ROUTES: ");
|
||||
for (Route route in angel.routes) {
|
||||
print("${route.method} ${route.path} - ${route.handlers}");
|
||||
}
|
||||
angel.dumpTree(header: "DUMPING ROUTES:");
|
||||
|
||||
client = new http.Client();
|
||||
await angel.startServer(InternetAddress.LOOPBACK_IP_V4, 0);
|
||||
|
@ -90,6 +99,7 @@ main() {
|
|||
|
||||
test('Can match url with multiple parameters', () async {
|
||||
var response = await client.get('$url/name/HELLO/last/WORLD');
|
||||
print(response.body);
|
||||
var json = god.deserialize(response.body);
|
||||
expect(json['first'], equals('HELLO'));
|
||||
expect(json['last'], equals('WORLD'));
|
||||
|
@ -104,6 +114,7 @@ main() {
|
|||
test('Can parse parameters from a nested Angel instance', () async {
|
||||
var response = await client.get('$url/todos/1337/action/test');
|
||||
var json = god.deserialize(response.body);
|
||||
print('JSON: $json');
|
||||
expect(json['id'], equals(1337));
|
||||
expect(json['action'], equals('test'));
|
||||
});
|
||||
|
@ -133,10 +144,11 @@ main() {
|
|||
});
|
||||
|
||||
test('Can name routes', () {
|
||||
Route foo = angel.get('/framework/:id', 'Angel').as('frm');
|
||||
Route foo = new Route('/framework/:id', name: 'frm');
|
||||
print('Foo: $foo');
|
||||
String uri = foo.makeUri({'id': 'angel'});
|
||||
print(uri);
|
||||
expect(uri, equals('/framework/angel'));
|
||||
expect(uri, equals('framework/angel'));
|
||||
});
|
||||
|
||||
test('Redirect to named routes', () async {
|
||||
|
|
|
@ -24,11 +24,9 @@ main() {
|
|||
angel.properties['foo'] = () => 'bar';
|
||||
angel.properties['Foo'] = new Foo('bar');
|
||||
|
||||
/**
|
||||
expect(angel.hello, equals('world'));
|
||||
expect(angel.foo(), equals('bar'));
|
||||
expect(angel.Foo.name, equals('bar'));
|
||||
*/
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue