Finally switched to new router
This commit is contained in:
parent
eb24b0c43e
commit
3355b0ab39
13 changed files with 175 additions and 132 deletions
1
.travis.yml
Normal file
1
.travis.yml
Normal file
|
@ -0,0 +1 @@
|
|||
language: dart
|
|
@ -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.
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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. :)
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 {
|
||||
|
@ -128,5 +128,7 @@ class Service extends Routable {
|
|||
..addAll(handlers)
|
||||
..addAll(
|
||||
(removeMiddleware == null) ? [] : removeMiddleware.handlers));
|
||||
|
||||
normalize();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -11,22 +11,22 @@ 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 {
|
||||
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 =>
|
||||
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");
|
||||
|
|
|
@ -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}";
|
||||
});
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
@ -9,22 +10,29 @@ main() {
|
|||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json'
|
||||
};
|
||||
|
||||
Angel app;
|
||||
HttpServer server;
|
||||
String url;
|
||||
http.Client client;
|
||||
HookedService Todos;
|
||||
|
||||
setUp(() async {
|
||||
app = new Angel();
|
||||
app = new Angel(debug: true);
|
||||
client = new http.Client();
|
||||
app.use('/todos', new MemoryService<Todo>());
|
||||
Todos = app.service("todos");
|
||||
|
||||
await app.startServer(null, 0);
|
||||
app
|
||||
..normalize()
|
||||
..dumpTree(showMatchers: true);
|
||||
|
||||
server = await app.startServer();
|
||||
url = "http://${app.httpServer.address.host}:${app.httpServer.port}";
|
||||
});
|
||||
|
||||
tearDown(() async {
|
||||
await server.close(force: true);
|
||||
app = null;
|
||||
url = null;
|
||||
client.close();
|
||||
|
@ -49,25 +57,30 @@ main() {
|
|||
});
|
||||
|
||||
test("cancel before", () async {
|
||||
Todos.beforeCreated..listen((HookedServiceEvent event) {
|
||||
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);
|
||||
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 {
|
||||
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"});
|
||||
});
|
||||
|
||||
|
|
|
@ -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"));
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue