This commit is contained in:
thosakwe 2017-01-20 17:11:20 -05:00
parent eb1e931b89
commit 929e7bbac6
8 changed files with 219 additions and 62 deletions

View file

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

View file

@ -17,11 +17,14 @@ import 'server.dart' show Angel, preInject;
/// Regular request handlers can also skip DI entirely, lowering response time
/// and memory use.
class InjectionRequest {
/// A list of the data required for a DI-enabled method to run.
/// Optional, typed data that can be passed to a DI-enabled method.
Map<String, Type> named = {};
/// A list of the arguments required for a DI-enabled method to run.
final List required = [];
/// A list of the data that can be null in a DI-enabled method.
final List optional = [];
/// A list of the arguments that can be null in a DI-enabled method.
final List<String> optional = [];
}
/// Supports grouping routes with shared functionality.
@ -121,7 +124,7 @@ class Controller {
}
/// Shortcut for calling [preInject], and then [handleContained].
///
///
/// Use this to instantly create a request handler for a DI-enabled method.
RequestHandler createDynamicHandler(handler,
{Iterable<String> optional: const []}) {
@ -173,8 +176,27 @@ RequestHandler handleContained(handler, InjectionRequest injection) {
}
}
Map<Symbol, dynamic> named = {};
injection.required.forEach(inject);
var result = Function.apply(handler, args);
injection.named.forEach((k, v) {
var name = new Symbol(k);
if (req.params.containsKey(k))
named[name] = v;
else if (req.injections.containsKey(k))
named[name] = v;
else if (req.injections.containsKey(v) && v != dynamic)
named[name] = v;
else {
try {
named[name] = req.app.container.make(v);
} catch (e) {
named[name] = null;
}
}
});
var result = Function.apply(handler, args, named);
return result is Future ? await result : result;
};
}

View file

@ -3,6 +3,8 @@ library angel_framework.http;
import 'dart:async';
import 'package:merge_map/merge_map.dart';
import '../util.dart';
import 'request_context.dart';
import 'response_context.dart';
import 'metadata.dart';
import 'service.dart';
@ -39,6 +41,14 @@ class HookedService extends Service {
if (inner.app != null) this.app = inner.app;
}
Map _stripReq(Map params) {
if (params == null)
return params;
else
return params..remove('__requestctx')..remove('__responsectx');
}
/// Adds hooks to this instance.
void addHooks() {
Hooks hooks = getAnnotation(inner, Hooks);
final before = [];
@ -74,6 +84,7 @@ class HookedService extends Service {
applyListeners(inner.removed, afterRemoved, true);
}
/// Adds routes to this instance.
@override
void addRoutes() {
// Set up our routes. We still need to copy middleware from inner service
@ -87,7 +98,11 @@ class HookedService extends Service {
Middleware indexMiddleware = getAnnotation(inner.index, Middleware);
get('/', (req, res) async {
return await this.index(mergeMap([req.query, restProvider]));
return await this.index(mergeMap([
req.query,
restProvider,
{'__requestctx': req, '__responsectx': res}
]));
},
middleware: []
..addAll(handlers)
@ -96,8 +111,13 @@ class HookedService extends Service {
Middleware createMiddleware = getAnnotation(inner.create, Middleware);
post(
'/',
(req, res) async =>
await this.create(req.body, mergeMap([req.query, restProvider])),
(req, res) async => await this.create(
req.body,
mergeMap([
req.query,
restProvider,
{'__requestctx': req, '__responsectx': res}
])),
middleware: []
..addAll(handlers)
..addAll(
@ -107,8 +127,13 @@ class HookedService extends Service {
get(
'/:id',
(req, res) async => await this
.read(req.params['id'], mergeMap([req.query, restProvider])),
(req, res) async => await this.read(
req.params['id'],
mergeMap([
req.query,
restProvider,
{'__requestctx': req, '__responsectx': res}
])),
middleware: []
..addAll(handlers)
..addAll((readMiddleware == null) ? [] : readMiddleware.handlers));
@ -117,7 +142,13 @@ class HookedService extends Service {
patch(
'/:id',
(req, res) async => await this.modify(
req.params['id'], req.body, mergeMap([req.query, restProvider])),
req.params['id'],
req.body,
mergeMap([
req.query,
restProvider,
{'__requestctx': req, '__responsectx': res}
])),
middleware: []
..addAll(handlers)
..addAll(
@ -127,7 +158,13 @@ class HookedService extends Service {
post(
'/:id',
(req, res) async => await this.update(
req.params['id'], req.body, mergeMap([req.query, restProvider])),
req.params['id'],
req.body,
mergeMap([
req.query,
restProvider,
{'__requestctx': req, '__responsectx': res}
])),
middleware: []
..addAll(handlers)
..addAll(
@ -136,8 +173,13 @@ class HookedService extends Service {
Middleware removeMiddleware = getAnnotation(inner.remove, Middleware);
delete(
'/:id',
(req, res) async => await this
.remove(req.params['id'], mergeMap([req.query, restProvider])),
(req, res) async => await this.remove(
req.params['id'],
mergeMap([
req.query,
restProvider,
{'__requestctx': req, '__responsectx': res}
])),
middleware: []
..addAll(handlers)
..addAll(
@ -147,123 +189,180 @@ class HookedService extends Service {
}
@override
Future<List> index([Map params]) async {
Future<List> index([Map _params]) async {
var params = _stripReq(_params);
HookedServiceEvent before = await beforeIndexed._emit(
new HookedServiceEvent._base(inner, HookedServiceEvent.INDEXED,
new HookedServiceEvent._base(_params['__requestctx'],
params['__responsectx'], inner, HookedServiceEvent.INDEXED,
params: params));
if (before._canceled) {
HookedServiceEvent after = await beforeIndexed._emit(
new HookedServiceEvent._base(inner, HookedServiceEvent.INDEXED,
new HookedServiceEvent._base(_params['__requestctx'],
params['__responsectx'], inner, HookedServiceEvent.INDEXED,
params: params, result: before.result));
return after.result;
}
List result = await inner.index(params);
HookedServiceEvent after = await afterIndexed._emit(
new HookedServiceEvent._base(inner, HookedServiceEvent.INDEXED,
new HookedServiceEvent._base(_params['__requestctx'],
params['__responsectx'], inner, HookedServiceEvent.INDEXED,
params: params, result: result));
return after.result;
}
@override
Future read(id, [Map params]) async {
Future read(id, [Map _params]) async {
var params = _stripReq(_params);
HookedServiceEvent before = await beforeRead._emit(
new HookedServiceEvent._base(inner, HookedServiceEvent.READ,
new HookedServiceEvent._base(_params['__requestctx'],
params['__responsectx'], inner, HookedServiceEvent.READ,
id: id, params: params));
if (before._canceled) {
HookedServiceEvent after = await afterRead._emit(
new HookedServiceEvent._base(inner, HookedServiceEvent.READ,
new HookedServiceEvent._base(_params['__requestctx'],
params['__responsectx'], inner, HookedServiceEvent.READ,
id: id, params: params, result: before.result));
return after.result;
}
var result = await inner.read(id, params);
HookedServiceEvent after = await afterRead._emit(
new HookedServiceEvent._base(inner, HookedServiceEvent.READ,
new HookedServiceEvent._base(_params['__requestctx'],
params['__responsectx'], inner, HookedServiceEvent.READ,
id: id, params: params, result: result));
return after.result;
}
@override
Future create(data, [Map params]) async {
Future create(data, [Map _params]) async {
var params = _stripReq(_params);
HookedServiceEvent before = await beforeCreated._emit(
new HookedServiceEvent._base(inner, HookedServiceEvent.CREATED,
new HookedServiceEvent._base(_params['__requestctx'],
params['__responsectx'], inner, HookedServiceEvent.CREATED,
data: data, params: params));
if (before._canceled) {
HookedServiceEvent after = await afterCreated._emit(
new HookedServiceEvent._base(inner, HookedServiceEvent.CREATED,
new HookedServiceEvent._base(_params['__requestctx'],
params['__responsectx'], inner, HookedServiceEvent.CREATED,
data: data, params: params, result: before.result));
return after.result;
}
var result = await inner.create(data, params);
HookedServiceEvent after = await afterCreated._emit(
new HookedServiceEvent._base(inner, HookedServiceEvent.CREATED,
new HookedServiceEvent._base(_params['__requestctx'],
params['__responsectx'], inner, HookedServiceEvent.CREATED,
data: data, params: params, result: result));
return after.result;
}
@override
Future modify(id, data, [Map params]) async {
Future modify(id, data, [Map _params]) async {
var params = _stripReq(_params);
HookedServiceEvent before = await beforeModified._emit(
new HookedServiceEvent._base(inner, HookedServiceEvent.MODIFIED,
new HookedServiceEvent._base(_params['__requestctx'],
params['__responsectx'], inner, HookedServiceEvent.MODIFIED,
id: id, data: data, params: params));
if (before._canceled) {
HookedServiceEvent after = await afterModified._emit(
new HookedServiceEvent._base(inner, HookedServiceEvent.MODIFIED,
new HookedServiceEvent._base(_params['__requestctx'],
params['__responsectx'], inner, HookedServiceEvent.MODIFIED,
id: id, data: data, params: params, result: before.result));
return after.result;
}
var result = await inner.modify(id, data, params);
HookedServiceEvent after = await afterModified._emit(
new HookedServiceEvent._base(inner, HookedServiceEvent.MODIFIED,
new HookedServiceEvent._base(_params['__requestctx'],
params['__responsectx'], inner, HookedServiceEvent.MODIFIED,
id: id, data: data, params: params, result: result));
return after.result;
}
@override
Future update(id, data, [Map params]) async {
Future update(id, data, [Map _params]) async {
var params = _stripReq(_params);
HookedServiceEvent before = await beforeUpdated._emit(
new HookedServiceEvent._base(inner, HookedServiceEvent.UPDATED,
new HookedServiceEvent._base(_params['__requestctx'],
params['__responsectx'], inner, HookedServiceEvent.UPDATED,
id: id, data: data, params: params));
if (before._canceled) {
HookedServiceEvent after = await afterUpdated._emit(
new HookedServiceEvent._base(inner, HookedServiceEvent.UPDATED,
new HookedServiceEvent._base(_params['__requestctx'],
params['__responsectx'], inner, HookedServiceEvent.UPDATED,
id: id, data: data, params: params, result: before.result));
return after.result;
}
var result = await inner.update(id, data, params);
HookedServiceEvent after = await afterUpdated._emit(
new HookedServiceEvent._base(inner, HookedServiceEvent.UPDATED,
new HookedServiceEvent._base(_params['__requestctx'],
params['__responsectx'], inner, HookedServiceEvent.UPDATED,
id: id, data: data, params: params, result: result));
return after.result;
}
@override
Future remove(id, [Map params]) async {
Future remove(id, [Map _params]) async {
var params = _stripReq(_params);
HookedServiceEvent before = await beforeRemoved._emit(
new HookedServiceEvent._base(inner, HookedServiceEvent.REMOVED,
new HookedServiceEvent._base(_params['__requestctx'],
params['__responsectx'], inner, HookedServiceEvent.REMOVED,
id: id, params: params));
if (before._canceled) {
HookedServiceEvent after = await afterRemoved._emit(
new HookedServiceEvent._base(inner, HookedServiceEvent.REMOVED,
new HookedServiceEvent._base(_params['__requestctx'],
params['__responsectx'], inner, HookedServiceEvent.REMOVED,
id: id, params: params, result: before.result));
return after.result;
}
var result = await inner.remove(id, params);
HookedServiceEvent after = await afterRemoved._emit(
new HookedServiceEvent._base(inner, HookedServiceEvent.REMOVED,
new HookedServiceEvent._base(_params['__requestctx'],
params['__responsectx'], inner, HookedServiceEvent.REMOVED,
id: id, params: params, result: result));
return after.result;
}
/// Fires an `after` event. This will not be propagated to clients,
/// but will be broadcasted to WebSockets, etc.
Future<HookedServiceEvent> fire(String eventName, result, [HookedServiceEventListener callback]) async {
HookedServiceEventDispatcher dispatcher;
switch (eventName) {
case HookedServiceEvent.INDEXED:
dispatcher = afterIndexed;
break;
case HookedServiceEvent.READ:
dispatcher = afterRead;
break;
case HookedServiceEvent.CREATED:
dispatcher = afterCreated;
break;
case HookedServiceEvent.MODIFIED:
dispatcher = afterModified;
break;
case HookedServiceEvent.UPDATED:
dispatcher = afterUpdated;
break;
case HookedServiceEvent.REMOVED:
dispatcher = afterRemoved;
break;
default:
throw new ArgumentError("Invalid service event name: '$eventName'");
}
var ev = new HookedServiceEvent._base(null, null, this, eventName);
if (callback != null) await callback(ev);
return await dispatcher._emit(ev);
}
}
/// Fired when a hooked service is invoked.
@ -287,6 +386,8 @@ class HookedServiceEvent {
var data;
Map _params;
var _result;
RequestContext _request;
ResponseContext _response;
String get eventName => _eventName;
@ -294,12 +395,17 @@ class HookedServiceEvent {
Map get params => _params;
RequestContext get request => _request;
ResponseContext get response => _response;
get result => _result;
/// The inner service whose method was hooked.
Service service;
HookedServiceEvent._base(Service this.service, String this._eventName,
HookedServiceEvent._base(this._result, this._response, Service this.service,
String this._eventName,
{id, this.data, Map params, result}) {
_id = id;
_params = params ?? {};
@ -308,7 +414,7 @@ class HookedServiceEvent {
}
/// Triggered on a hooked service event.
typedef Future HookedServiceEventListener(HookedServiceEvent event);
typedef HookedServiceEventListener(HookedServiceEvent event);
/// Can be listened to, but events may be canceled.
class HookedServiceEventDispatcher {

View file

@ -97,6 +97,24 @@ class RequestContext extends Extensible {
return ctx;
}
/// Grabs an object by key or type from [params], [injections], or
/// [app].container. Use this to perform dependency injection
/// within a service hook.
grab(key) {
if (params.containsKey(key))
return params[key];
else if (injections.containsKey(key))
return injections[key];
else if (key is Type) {
try {
return app.container.make(key);
} catch (e) {
return null;
}
} else
return null;
}
void inject(type, value) {
injections[type] = value;
}

View file

@ -116,7 +116,6 @@ class ResponseContext extends Extensible {
}
/// Serializes JSON to the response.
@Deprecated('Please use `serialize` instead.')
void json(value) => serialize(value, contentType: ContentType.JSON);
/// Returns a JSONP response.
@ -232,8 +231,7 @@ class ResponseContext extends Extensible {
void serialize(value, {contentType}) {
var text = serializer(value);
write(text);
headers[HttpHeaders.CONTENT_LENGTH] = text.length.toString();
if (contentType is String)
headers[HttpHeaders.CONTENT_TYPE] = contentType;
else if (contentType is ContentType) this.contentType = contentType;

View file

@ -132,12 +132,11 @@ class Angel extends AngelBase {
/// Loads some base dependencies into the service container.
void bootstrapContainer() {
if (runtimeType != Angel) container.singleton(this, as: Angel);
container.singleton(this, as: AngelBase);
container.singleton(this, as: Routable);
container.singleton(this, as: Router);
container.singleton(this);
if (runtimeType != Angel) container.singleton(this, as: Angel);
}
Future<bool> executeHandler(
@ -189,6 +188,7 @@ class Angel extends AngelBase {
return false;
}
/// Handles a single request.
Future handleRequest(HttpRequest request) async {
try {
_beforeProcessed.add(request);
@ -270,11 +270,12 @@ class Angel extends AngelBase {
}
for (var key in res.headers.keys) {
request.response.headers.set(key, res.headers[key]);
request.response.headers.add(key, res.headers[key]);
}
request.response.headers.chunkedTransferEncoding =
res.chunked ?? true;
request.response.headers
..chunkedTransferEncoding = res.chunked ?? true
..set(HttpHeaders.CONTENT_LENGTH, res.buffer.length);
request.response
..statusCode = res.statusCode
@ -417,17 +418,23 @@ class Angel extends AngelBase {
_errorHandler = handler;
}
Angel({bool debug: false}) : super(debug: debug) {
/// Default constructor. ;)
Angel({bool debug: false}) : super(debug: debug == true) {
bootstrapContainer();
}
/// An instance mounted on a server started by the [serverGenerator].
factory Angel.custom(ServerGenerator serverGenerator, {bool debug: false}) =>
new Angel(debug: debug == true).._serverGenerator = serverGenerator;
/// Creates an HTTPS server.
///
/// Provide paths to a certificate chain and server key (both .pem).
/// If no password is provided, a random one will be generated upon running
/// the server.
factory Angel.secure(String certificateChainPath, String serverKeyPath,
{bool debug: false, String password}) {
final app = new Angel(debug: debug);
final app = new Angel(debug: debug == true);
app._serverGenerator = (InternetAddress address, int port) async {
var certificateChain =
@ -455,16 +462,22 @@ InjectionRequest preInject(Function handler) {
var name = MirrorSystem.getName(parameter.simpleName);
var type = parameter.type.reflectedType;
if (type == RequestContext || type == ResponseContext) {
injection.required.add(type);
} else if (name == 'req') {
injection.required.add(RequestContext);
} else if (name == 'res') {
injection.required.add(ResponseContext);
} else if (type == dynamic) {
injection.required.add(name);
if (!parameter.isNamed) {
if (parameter.isOptional) injection.optional.add(name);
if (type == RequestContext || type == ResponseContext) {
injection.required.add(type);
} else if (name == 'req') {
injection.required.add(RequestContext);
} else if (name == 'res') {
injection.required.add(ResponseContext);
} else if (type == dynamic) {
injection.required.add(name);
} else {
injection.required.add([name, type]);
}
} else {
injection.required.add([name, type]);
injection.named[name] = type;
}
}

View file

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

View file

@ -24,7 +24,7 @@ main() {
app.get("/errands", (Todo singleton) => singleton);
app.get("/errands3",
(Errand singleton, Todo foo, RequestContext req) => singleton.text);
({Errand singleton, Todo foo, RequestContext req}) => singleton.text);
await app.configure(new SingletonController());
await app.configure(new ErrandController());