New router... Again
This commit is contained in:
parent
39d70e74a6
commit
774edb4be8
9 changed files with 78 additions and 63 deletions
|
@ -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.
|
|
@ -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.
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -128,7 +128,5 @@ class Service extends Routable {
|
|||
..addAll(handlers)
|
||||
..addAll(
|
||||
(removeMiddleware == null) ? [] : removeMiddleware.handlers));
|
||||
|
||||
normalize();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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!\""));
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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}";
|
||||
|
|
|
@ -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'));
|
||||
});
|
||||
|
||||
|
|
Loading…
Reference in a new issue