New router... Again

This commit is contained in:
thosakwe 2016-11-27 19:49:27 -05:00
parent 39d70e74a6
commit 774edb4be8
9 changed files with 78 additions and 63 deletions

View file

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

View file

@ -10,6 +10,8 @@ import '../extensible.dart';
import 'angel_base.dart';
import 'controller.dart';
final RegExp _straySlashes = new RegExp(r'(^/+)|(/+$)');
/// A convenience wrapper around an outgoing HTTP request.
class ResponseContext extends Extensible {
bool _isOpen = true;
@ -90,8 +92,15 @@ class ResponseContext extends Extensible {
}
/// Redirects to user to the given URL.
void redirect(String url, {int code: 301}) {
header(HttpHeaders.LOCATION, url);
///
/// [url] can be a `String`, or a `List`.
/// If it is a `List`, a URI will be constructed
/// based on the provided params.
///
/// See [Router]#navigate for more. :)
void redirect(url, {bool absolute: true, int code: 301}) {
header(HttpHeaders.LOCATION,
url is String ? url : app.navigate(url, absolute: absolute));
status(code ?? 301);
write('''
<!DOCTYPE html>
@ -115,18 +124,19 @@ class ResponseContext extends Extensible {
/// Redirects to the given named [Route].
void redirectTo(String name, [Map params, int code]) {
_findRoute(Route route) {
for (Route child in route.children) {
final resolved = _findRoute(child);
Route _findRoute(Router r) {
for (Route route in r.routes) {
if (route is SymlinkRoute) {
final m = _findRoute(route.router);
if (resolved != null) return resolved;
if (m != null) return m;
} else if (route.name == name) return route;
}
return route.children
.firstWhere((r) => r.name == name, orElse: () => null);
return null;
}
Route matched = _findRoute(app.root);
Route matched = _findRoute(app);
if (matched != null) {
redirect(matched.makeUri(params), code: code);
@ -146,7 +156,8 @@ class ResponseContext extends Extensible {
throw new Exception(
"Controller redirects must take the form of 'Controller@action'. You gave: $action");
Controller controller = app.controller(split[0]);
Controller controller =
app.controller(split[0].replaceAll(_straySlashes, ''));
if (controller == null)
throw new Exception("Could not find a controller named '${split[0]}'");
@ -157,7 +168,11 @@ class ResponseContext extends Extensible {
throw new Exception(
"Controller '${split[0]}' does not contain any action named '${split[1]}'");
redirect(matched.makeUri(params), code: code);
final head =
controller.exposeDecl.path.toString().replaceAll(_straySlashes, '');
final tail = matched.makeUri(params).replaceAll(_straySlashes, '');
redirect('$head/$tail'.replaceAll(_straySlashes, ''), code: code);
}
/// Streams a file to this response as chunked data.

View file

@ -11,6 +11,7 @@ import 'metadata.dart';
import 'request_context.dart';
import 'response_context.dart';
import 'service.dart';
final RegExp _straySlashes = new RegExp(r'(^/+)|(/+$)');
/// A function that intercepts a request and determines whether handling of it should continue.
typedef Future<bool> RequestMiddleware(RequestContext req, ResponseContext res);
@ -23,7 +24,7 @@ typedef Future RawRequestHandler(HttpRequest request);
/// A routable server that can handle dynamic requests.
class Routable extends Router {
final Map<String, Controller> _controllers = {};
final Map<Pattern, Controller> _controllers = {};
final Map<Pattern, Service> _services = {};
Routable({bool debug: false}) : super(debug: debug);
@ -36,7 +37,7 @@ class Routable extends Router {
Map<Pattern, Service> get services => _services;
/// A set of [Controller] objects that have been loaded into the application.
Map<String, Controller> get controllers => _controllers;
Map<Pattern, Controller> get controllers => _controllers;
StreamController<Service> _onService =
new StreamController<Service>.broadcast();

View file

@ -4,6 +4,7 @@ import 'dart:async';
import 'dart:io';
import 'dart:math' show Random;
import 'dart:mirrors';
import 'package:angel_route/angel_route.dart';
import 'package:json_god/json_god.dart' as god;
import 'package:shelf/shelf.dart' as shelf;
import 'angel_base.dart';
@ -185,23 +186,18 @@ class Angel extends AngelBase {
if (requestedUrl.isEmpty) requestedUrl = '/';
final resolved = resolveAll(requestedUrl, method: request.method);
final resolved =
resolveAll(requestedUrl, requestedUrl, method: request.method);
for (final result in resolved) req.params.addAll(result.allParams);
if (resolved.isNotEmpty) {
final route = resolved.first;
req.params.addAll(route?.parseParameters(requestedUrl) ?? {});
final route = resolved.first.route;
req.inject(Match, route.match(requestedUrl));
}
final pipeline = []..addAll(before);
if (resolved.isNotEmpty) {
for (final route in resolved) {
pipeline.addAll(route.handlerSequence);
}
}
pipeline.addAll(after);
final m = new MiddlewarePipeline(resolved);
final pipeline = []..addAll(before)..addAll(m.handlers)..addAll(after);
_printDebug('Handler sequence on $requestedUrl: $pipeline');
@ -304,7 +300,8 @@ class Angel extends AngelBase {
Future configure(AngelConfigurer configurer) async {
await configurer(this);
if (configurer is Controller) _onController.add(configurer);
if (configurer is Controller)
_onController.add(controllers[configurer.exposeDecl.path] = configurer);
}
/// Fallback when an error is thrown while handling a request.
@ -320,6 +317,15 @@ class Angel extends AngelBase {
@override
use(Pattern path, Routable routable,
{bool hooked: true, String namespace: null}) {
if (routable is Angel) {
final head = path.toString().replaceAll(_straySlashes, '');
routable.controllers.forEach((k, v) {
final tail = k.toString().replaceAll(_straySlashes, '');
controllers['$head/$tail'.replaceAll(_straySlashes, '')] = v;
});
}
if (routable is Service) {
routable.app = this;
}

View file

@ -128,7 +128,5 @@ class Service extends Routable {
..addAll(handlers)
..addAll(
(removeMiddleware == null) ? [] : removeMiddleware.handlers));
normalize();
}
}

View file

@ -1,5 +1,5 @@
name: angel_framework
version: 1.0.0-dev.25
version: 1.0.0-dev.26
description: Core libraries for the Angel framework.
author: Tobe O <thosakwe@gmail.com>
homepage: https://github.com/angel-dart/angel_framework

View file

@ -12,10 +12,10 @@ class TodoController extends Controller {
@Expose("/:id", middleware: const ["bar"])
Future<Todo> fetchTodo(
int id, RequestContext req, ResponseContext res) async {
String id, RequestContext req, ResponseContext res) async {
expect(req, isNotNull);
expect(res, isNotNull);
return todos[id];
return todos[int.parse(id)];
}
@Expose("/namedRoute/:foo", as: "foo")
@ -32,7 +32,7 @@ main() {
String url;
setUp(() async {
app = new Angel();
app = new Angel(debug: true);
app.registerMiddleware("foo", (req, res) async => res.write("Hello, "));
app.registerMiddleware("bar", (req, res) async => res.write("world!"));
app.get(
@ -57,7 +57,7 @@ main() {
test("middleware", () async {
var rgx = new RegExp("^Hello, world!");
var response = await client.get("$url/todos/0");
print(response.body);
print('Response: ${response.body}');
expect(rgx.firstMatch(response.body).start, equals(0));
@ -70,8 +70,7 @@ main() {
test("named actions", () async {
var response = await client.get("$url/redirect");
print(response.body);
print('Response: ${response.body}');
expect(response.body, equals("Hello, \"world!\""));
});
}

View file

@ -23,9 +23,7 @@ main() {
app.use('/todos', new MemoryService<Todo>());
Todos = app.service("todos");
app
..normalize()
..dumpTree(showMatchers: true);
app.dumpTree(showMatchers: true);
server = await app.startServer();
url = "http://${app.httpServer.address.host}:${app.httpServer.port}";

View file

@ -19,7 +19,7 @@ class QueryService extends Service {
}
main() {
Angel angel;
Angel app;
Angel nested;
Angel todos;
String url;
@ -27,11 +27,11 @@ main() {
setUp(() async {
final debug = true;
angel = new Angel(debug: debug);
app = new Angel(debug: debug);
nested = new Angel(debug: debug);
todos = new Angel(debug: debug);
angel
app
..registerMiddleware('interceptor', (req, res) async {
res.write('Middleware');
return false;
@ -53,41 +53,39 @@ main() {
return req.params;
});
angel.use('/nes', nested);
angel.get('/meta', testMiddlewareMetadata);
angel.get('/intercepted', 'This should not be shown',
app.use('/nes', nested);
app.get('/meta', testMiddlewareMetadata);
app.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('/todos/:id', todos);
angel
app.get('/hello', 'world');
app.get('/name/:first/last/:last', (req, res) => req.params);
app.post('/lambda', (req, res) => req.body);
app.use('/todos/:id', todos);
app
.get('/greet/:name',
(RequestContext req, res) async => "Hello ${req.params['name']}")
.as('Named routes');
angel.get('/named', (req, ResponseContext res) async {
app.get('/named', (req, ResponseContext res) async {
res.redirectTo('Named routes', {'name': 'tests'});
});
angel.get('/log', (RequestContext req, res) async {
app.get('/log', (RequestContext req, res) async {
print("Query: ${req.query}");
return "Logged";
});
angel.use('/query', new QueryService());
angel.get('*', 'MJ');
app.use('/query', new QueryService());
app.get('*', 'MJ');
angel
..normalize()
..dumpTree(header: "DUMPING ROUTES:", showMatchers: true);
app.dumpTree(header: "DUMPING ROUTES:", showMatchers: true);
client = new http.Client();
await angel.startServer(InternetAddress.LOOPBACK_IP_V4, 0);
url = "http://${angel.httpServer.address.host}:${angel.httpServer.port}";
await app.startServer(InternetAddress.LOOPBACK_IP_V4, 0);
url = "http://${app.httpServer.address.host}:${app.httpServer.port}";
});
tearDown(() async {
await angel.httpServer.close(force: true);
angel = null;
await app.httpServer.close(force: true);
app = null;
nested = null;
todos = null;
client.close();
@ -102,7 +100,7 @@ main() {
test('Can match url with multiple parameters', () async {
var response = await client.get('$url/name/HELLO/last/WORLD');
print(response.body);
print('Response: ${response.body}');
var json = god.deserialize(response.body);
expect(json, new isInstanceOf<Map<String, String>>());
expect(json['first'], equals('HELLO'));
@ -119,7 +117,7 @@ main() {
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['id'], equals('1337'));
expect(json['action'], equals('test'));
});