Finally switched to new router

This commit is contained in:
thosakwe 2016-11-23 04:10:47 -05:00
parent eb24b0c43e
commit 3355b0ab39
13 changed files with 175 additions and 132 deletions

1
.travis.yml Normal file
View file

@ -0,0 +1 @@
language: dart

View file

@ -1,5 +1,6 @@
# angel_framework
![version 1.0.0-dev](https://img.shields.io/badge/version-1.0.0--dev-red.svg)
![version 1.0.0-dev](https://img.shields.io/badge/version-1.0.0--dev.23-red.svg)
![build status](https://travis-ci.org/angel-dart/framework.svg)
Core libraries for the Angel Framework.

View file

@ -18,19 +18,6 @@ class 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);
@ -47,11 +34,18 @@ class Controller {
"All controllers must carry an @Expose() declaration.");
}
final handlers = []..addAll(exposeDecl.middleware)..addAll(middleware);
app.use(exposeDecl.path, generateRoutable(classMirror));
TypeMirror typeMirror = reflectType(this.runtimeType);
String name = exposeDecl.as;
InstanceMirror instanceMirror = reflect(this);
classMirror.instanceMembers
.forEach((Symbol key, MethodMirror methodMirror) {
if (name == null || name.isEmpty)
name = MirrorSystem.getName(typeMirror.simpleName);
app.controllers[name] = this;
}
_callback(InstanceMirror instanceMirror, Routable routable, List handlers) {
return (Symbol key, MethodMirror methodMirror) {
if (methodMirror.isRegularMethod &&
key != #toString &&
key != #noSuchMethod &&
@ -102,11 +96,13 @@ class Controller {
return await instanceMirror.invoke(key, args).reflectee;
};
final middleware = []
..addAll(handlers)
..addAll(exposeMirror.reflectee.middleware);
final route = routable.addRoute(exposeMirror.reflectee.method,
exposeMirror.reflectee.path, handler,
middleware: []
..addAll(handlers)
..addAll(exposeMirror.reflectee.middleware));
middleware: middleware);
String name = exposeMirror.reflectee.as;
@ -115,7 +111,16 @@ class Controller {
routeMappings[name] = route;
}
}
});
};
}
Routable generateRoutable(ClassMirror classMirror) {
final routable = new Routable(debug: true);
final handlers = []..addAll(exposeDecl.middleware)..addAll(middleware);
InstanceMirror instanceMirror = reflect(this);
final callback = _callback(instanceMirror, routable, handlers);
classMirror.instanceMembers.forEach(callback);
return routable;
}

View file

@ -37,7 +37,10 @@ class HookedService extends Service {
HookedService(Service this.inner) {
// Clone app instance
if (inner.app != null) this.app = inner.app;
}
@override
void addRoutes() {
// Set up our routes. We still need to copy middleware from inner service
Map restProvider = {'provider': Providers.REST};
@ -45,7 +48,7 @@ class HookedService extends Service {
Middleware before = getAnnotation(inner, Middleware);
final handlers = [];
if (before != null) handlers.add(before.handlers);
if (before != null) handlers.addAll(before.handlers);
Middleware indexMiddleware = getAnnotation(inner.index, Middleware);
get('/', (req, res) async {
@ -67,7 +70,7 @@ class HookedService extends Service {
get(
'/:id',
(req, res) async => await this
.read(req.params['id'], mergeMap([req.query, restProvider])),
.read(req.params['id'], mergeMap([req.query, restProvider])),
middleware: []
..addAll(handlers)
..addAll((readMiddleware == null) ? [] : readMiddleware.handlers));
@ -76,7 +79,7 @@ class HookedService extends Service {
patch(
'/:id',
(req, res) async =>
await this.modify(req.params['id'], req.body, restProvider),
await this.modify(req.params['id'], req.body, restProvider),
middleware: []
..addAll(handlers)
..addAll(
@ -86,7 +89,7 @@ class HookedService extends Service {
post(
'/:id',
(req, res) async =>
await this.update(req.params['id'], req.body, restProvider),
await this.update(req.params['id'], req.body, restProvider),
middleware: []
..addAll(handlers)
..addAll(
@ -96,7 +99,7 @@ class HookedService extends Service {
delete(
'/:id',
(req, res) async => await this
.remove(req.params['id'], mergeMap([req.query, restProvider])),
.remove(req.params['id'], mergeMap([req.query, restProvider])),
middleware: []
..addAll(handlers)
..addAll(

View file

@ -109,8 +109,19 @@ class ResponseContext extends Extensible {
/// Redirects to the given named [Route].
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);
_findRoute(Route route) {
for (Route child in route.children) {
final resolved = _findRoute(child);
if (resolved != null) return resolved;
}
return route.children
.firstWhere((r) => r.name == name, orElse: () => null);
}
Route matched = _findRoute(app.root);
if (matched != null) {
redirect(matched.makeUri(params), code: code);
return;
@ -160,7 +171,8 @@ class ResponseContext extends Extensible {
if (isOpen) {
if (value is List<int>)
buffer.add(value);
else buffer.add(encoding.encode(value.toString()));
else
buffer.add(encoding.encode(value.toString()));
}
}
}

View file

@ -33,12 +33,10 @@ class Routable extends Router {
final Map<String, RequestMiddleware> requestMiddleware = {};
/// A set of [Service] objects that have been mapped into routes.
Map<Pattern, Service> get services =>
new Map<Pattern, Service>.unmodifiable(_services);
Map<Pattern, Service> get services => _services;
/// A set of [Controller] objects that have been loaded into the application.
Map<String, Controller> get controllers =>
new Map<String, Controller>.unmodifiable(_controllers);
Map<String, Controller> get controllers => _controllers;
StreamController<Service> _onService =
new StreamController<Service>.broadcast();
@ -61,7 +59,7 @@ class Routable extends Router {
@override
Route addRoute(String method, Pattern path, Object handler,
{List middleware}) {
{List middleware: const []}) {
final List handlers = [];
// Merge @Middleware declaration, if any
Middleware middlewareDeclaration = getAnnotation(handler, Middleware);
@ -69,8 +67,11 @@ class Routable extends Router {
handlers.addAll(middlewareDeclaration.handlers);
}
return super.addRoute(method, path, handler,
middleware: []..addAll(middleware ?? [])..addAll(handlers));
final List handlerSequence = [];
handlerSequence.addAll(middleware ?? []);
handlerSequence.addAll(handlers);
return super.addRoute(method, path, handler, middleware: handlerSequence);
}
void use(Pattern path, Router router,
@ -89,9 +90,11 @@ class Routable extends Router {
.toString()
.trim()
.replaceAll(new RegExp(r'(^/+)|(/+$)'), '')] = service;
service.addRoutes();
}
final handlers = [];
if (_router is AngelBase) {
handlers.add((RequestContext req, ResponseContext res) async {
req.app = _router;
@ -109,9 +112,9 @@ class Routable extends Router {
copiedMiddleware[middlewareName];
}
root.child(path, debug: debug, handlers: handlers).addChild(router.root);
_router.dumpTree(header: 'Mounting on "$path":');
// _router.dumpTree(header: 'Mounting on "$path":');
// root.child(path, debug: debug, handlers: handlers).addChild(router.root);
mount(path, _router);
if (router is Routable) {
// Copy services, too. :)

View file

@ -98,8 +98,8 @@ class Angel extends AngelBase {
/// Returns false on failure; otherwise, returns the HttpServer.
Future<HttpServer> startServer([InternetAddress address, int port]) async {
final host = address ?? InternetAddress.LOOPBACK_IP_V4;
final server = await _serverGenerator(host, port ?? 0);
return this.httpServer = server..listen(handleRequest);
this.httpServer = await _serverGenerator(host, port ?? 0);
return httpServer..listen(handleRequest);
}
/// Loads some base dependencies into the service container.
@ -178,14 +178,13 @@ class Angel extends AngelBase {
final req = await RequestContext.from(request, this);
final res = new ResponseContext(request.response, this);
String requestedUrl = request.uri
.toString()
.replaceAll("?" + request.uri.query, "")
.path
.replaceAll(_straySlashes, '');
if (requestedUrl.isEmpty) requestedUrl = '/';
final route = resolve(requestedUrl, method: request.method);
print('Resolve ${requestedUrl} -> $route');
_printDebug('Resolved ${requestedUrl} -> $route');
req.params.addAll(route?.parseParameters(requestedUrl) ?? {});
final handlerSequence = []..addAll(before);
@ -331,8 +330,8 @@ class Angel extends AngelBase {
bootstrapContainer();
_serverGenerator = (InternetAddress address, int port) async {
var certificateChain =
Platform.script.resolve('server_chain.pem').toFilePath();
var serverKey = Platform.script.resolve('server_key.pem').toFilePath();
Platform.script.resolve(certificateChainPath).toFilePath();
var serverKey = Platform.script.resolve(serverKeyPath).toFilePath();
var serverContext = new SecurityContext();
serverContext.useCertificateChain(certificateChain);
serverContext.usePrivateKey(serverKey,

View file

@ -65,14 +65,14 @@ class Service extends Routable {
throw new AngelHttpException.MethodNotAllowed();
}
Service() : super() {
void addRoutes() {
Map restProvider = {'provider': Providers.REST};
// Add global middleware if declared on the instance itself
Middleware before = getAnnotation(this, Middleware);
final handlers = [];
if (before != null) handlers.add(before.handlers);
if (before != null) handlers.addAll(before.handlers);
Middleware indexMiddleware = getAnnotation(this.index, Middleware);
get('/', (req, res) async {
@ -94,7 +94,7 @@ class Service extends Routable {
get(
'/:id',
(req, res) async => await this
.read(req.params['id'], mergeMap([req.query, restProvider])),
.read(req.params['id'], mergeMap([req.query, restProvider])),
middleware: []
..addAll(handlers)
..addAll((readMiddleware == null) ? [] : readMiddleware.handlers));
@ -103,7 +103,7 @@ class Service extends Routable {
patch(
'/:id',
(req, res) async =>
await this.modify(req.params['id'], req.body, restProvider),
await this.modify(req.params['id'], req.body, restProvider),
middleware: []
..addAll(handlers)
..addAll(
@ -113,7 +113,7 @@ class Service extends Routable {
post(
'/:id',
(req, res) async =>
await this.update(req.params['id'], req.body, restProvider),
await this.update(req.params['id'], req.body, restProvider),
middleware: []
..addAll(handlers)
..addAll(
@ -123,10 +123,12 @@ class Service extends Routable {
delete(
'/:id',
(req, res) async => await this
.remove(req.params['id'], mergeMap([req.query, restProvider])),
.remove(req.params['id'], mergeMap([req.query, restProvider])),
middleware: []
..addAll(handlers)
..addAll(
(removeMiddleware == null) ? [] : removeMiddleware.handlers));
normalize();
}
}

View file

@ -1,13 +1,12 @@
name: angel_framework
version: 1.0.0-dev.22
version: 1.0.0-dev.23
description: Core libraries for the Angel framework.
author: Tobe O <thosakwe@gmail.com>
homepage: https://github.com/angel-dart/angel_framework
environment:
sdk: ">=1.18.0"
dependencies:
angel_route:
path: ../angel_route
angel_route: ^1.0.0-dev
body_parser: ^1.0.0-dev
container: ^0.1.2
json_god: ^2.0.0-beta

View file

@ -10,23 +10,23 @@ import 'common.dart';
class TodoController extends Controller {
List<Todo> todos = [new Todo(text: "Hello", over: "world")];
@Expose("/:id", middleware: const["bar"])
Future<Todo> fetchTodo(int id, RequestContext req,
ResponseContext res) async {
@Expose("/:id", middleware: const ["bar"])
Future<Todo> fetchTodo(
int id, RequestContext req, ResponseContext res) async {
expect(req, isNotNull);
expect(res, isNotNull);
return todos[id];
}
@Expose("/namedRoute/:foo", as: "foo")
Future<String> someRandomRoute(RequestContext req,
ResponseContext res) async {
Future<String> someRandomRoute(
RequestContext req, ResponseContext res) async {
return "${req.params['foo']}!";
}
}
main() {
Angel app = new Angel();
Angel app;
HttpServer server;
InternetAddress host = InternetAddress.LOOPBACK_IP_V4;
int port = 3000;
@ -34,25 +34,25 @@ main() {
String url = "http://${host.address}:$port";
setUp(() async {
app = new Angel();
app.registerMiddleware("foo", (req, res) async => res.write("Hello, "));
app.registerMiddleware("bar", (req, res) async => res.write("world!"));
app.get("/redirect", (req, ResponseContext res) async =>
res.redirectToAction("TodoController@foo", {"foo": "world"}));
app.get(
"/redirect",
(req, ResponseContext res) async =>
res.redirectToAction("TodoController@foo", {"foo": "world"}));
await app.configure(new TodoController());
print(app.controllers);
print("\nDUMPING ROUTES:");
app.routes.forEach((Route route) {
print("\t${route.method} ${route.path} -> ${route.handlers}");
});
print("\n");
app.dumpTree();
server = await app.startServer(host, port);
client = new http.Client();
});
tearDown(() async {
await (server ?? app.httpServer).close(force: true);
await server.close(force: true);
app = null;
client.close();
client = null;
});
@ -62,7 +62,7 @@ main() {
var response = await client.get("$url/todos/0");
print(response.body);
expect(response.body.indexOf("Hello, "), equals(0));
expect(rgx.firstMatch(response.body).start, equals(0));
Map todo = JSON.decode(response.body.replaceAll(rgx, ""));
print("Todo: $todo");

View file

@ -15,7 +15,7 @@ main() {
String url;
setUp(() async {
app = new Angel();
app = new Angel(debug: true);
client = new http.Client();
// Inject some todos
@ -27,6 +27,7 @@ main() {
await app.configure(new ErrandController());
server = await app.startServer();
print('server: $server, httpServer: ${app.httpServer}');
url = "http://${server.address.host}:${server.port}";
});

View file

@ -1,3 +1,4 @@
import 'dart:io';
import 'package:angel_framework/angel_framework.dart';
import 'package:http/http.dart' as http;
import 'package:json_god/json_god.dart' as god;
@ -6,74 +7,86 @@ import 'common.dart';
main() {
Map headers = {
'Accept': 'application/json',
'Content-Type': 'application/json'
};
Angel app;
String url;
http.Client client;
HookedService Todos;
'Accept': 'application/json',
'Content-Type': 'application/json'
};
setUp(() async {
app = new Angel();
client = new http.Client();
app.use('/todos', new MemoryService<Todo>());
Todos = app.service("todos");
Angel app;
HttpServer server;
String url;
http.Client client;
HookedService Todos;
await app.startServer(null, 0);
url = "http://${app.httpServer.address.host}:${app.httpServer.port}";
});
setUp(() async {
app = new Angel(debug: true);
client = new http.Client();
app.use('/todos', new MemoryService<Todo>());
Todos = app.service("todos");
tearDown(() async {
app = null;
url = null;
client.close();
client = null;
Todos = null;
});
app
..normalize()
..dumpTree(showMatchers: true);
test("listen before and after", () async {
int count = 0;
server = await app.startServer();
url = "http://${app.httpServer.address.host}:${app.httpServer.port}";
});
Todos
..beforeIndexed.listen((_) {
count++;
})
..afterIndexed.listen((_) {
count++;
});
tearDown(() async {
await server.close(force: true);
app = null;
url = null;
client.close();
client = null;
Todos = null;
});
var response = await client.get("$url/todos");
print(response.body);
expect(count, equals(2));
});
test("listen before and after", () async {
int count = 0;
test("cancel before", () async {
Todos.beforeCreated..listen((HookedServiceEvent event) {
Todos
..beforeIndexed.listen((_) {
count++;
})
..afterIndexed.listen((_) {
count++;
});
var response = await client.get("$url/todos");
print(response.body);
expect(count, equals(2));
});
test("cancel before", () async {
Todos.beforeCreated
..listen((HookedServiceEvent event) {
event.cancel({"hello": "hooked world"});
})..listen((HookedServiceEvent event) {
})
..listen((HookedServiceEvent event) {
event.cancel({"this_hook": "should never run"});
});
var response = await client.post(
"$url/todos", body: god.serialize({"arbitrary": "data"}),
headers: headers);
print(response.body);
Map result = god.deserialize(response.body);
expect(result["hello"], equals("hooked world"));
});
var response = await client.post("$url/todos",
body: god.serialize({"arbitrary": "data"}), headers: headers);
print(response.body);
Map result = god.deserialize(response.body);
expect(result["hello"], equals("hooked world"));
});
test("cancel after", () async {
Todos.afterIndexed..listen((HookedServiceEvent event) async {
test("cancel after", () async {
Todos.afterIndexed
..listen((HookedServiceEvent event) async {
// Hooks can be Futures ;)
event.cancel([{"angel": "framework"}]);
})..listen((HookedServiceEvent event) {
event.cancel([
{"angel": "framework"}
]);
})
..listen((HookedServiceEvent event) {
event.cancel({"this_hook": "should never run either"});
});
var response = await client.get("$url/todos");
print(response.body);
List result = god.deserialize(response.body);
expect(result[0]["angel"], equals("framework"));
});
}
var response = await client.get("$url/todos");
print(response.body);
List result = god.deserialize(response.body);
expect(result[0]["angel"], equals("framework"));
});
}

View file

@ -26,7 +26,7 @@ main() {
http.Client client;
setUp(() async {
final debug = false;
final debug = true;
angel = new Angel(debug: debug);
nested = new Angel(debug: debug);
todos = new Angel(debug: debug);
@ -38,7 +38,7 @@ main() {
})
..registerMiddleware('intercept_service',
(RequestContext req, res) async {
print("Intercepting a service!");
res.write("Service with ");
return true;
});
@ -48,7 +48,8 @@ main() {
ted = nested.post('/ted/:route', (RequestContext req, res) {
print('Params: ${req.params}');
print('Path: ${ted.path}, matcher: ${ted.matcher.pattern}, uri: ${req.path}');
print(
'Path: ${ted.path}, matcher: ${ted.matcher.pattern}, uri: ${req.path}');
return req.params;
});
@ -75,7 +76,9 @@ main() {
angel.use('/query', new QueryService());
angel.get('*', 'MJ');
angel.dumpTree(header: "DUMPING ROUTES:");
angel
..normalize()
..dumpTree(header: "DUMPING ROUTES:", showMatchers: true);
client = new http.Client();
await angel.startServer(InternetAddress.LOOPBACK_IP_V4, 0);
@ -101,6 +104,7 @@ main() {
var response = await client.get('$url/name/HELLO/last/WORLD');
print(response.body);
var json = god.deserialize(response.body);
expect(json, new isInstanceOf<Map<String, String>>());
expect(json['first'], equals('HELLO'));
expect(json['last'], equals('WORLD'));
});
@ -165,6 +169,6 @@ main() {
response = await client.get("$url/query/foo?bar=baz");
print(response.body);
expect(response.body, equals("Middleware"));
expect(response.body, equals("Service with Middleware"));
});
}