Working on 1.0.8, including performance tuning

This commit is contained in:
thosakwe 2017-08-03 12:40:21 -04:00
parent 3d12297316
commit f6695080b8
22 changed files with 995 additions and 619 deletions

View file

@ -19,7 +19,7 @@
<entry key="angel_route"> <entry key="angel_route">
<value> <value>
<list> <list>
<option value="$USER_HOME$/AppData/Roaming/Pub/Cache/hosted/pub.dartlang.org/angel_route-1.0.3/lib" /> <option value="$USER_HOME$/AppData/Roaming/Pub/Cache/hosted/pub.dartlang.org/angel_route-1.0.5/lib" />
</list> </list>
</value> </value>
</entry> </entry>
@ -399,7 +399,7 @@
<CLASSES> <CLASSES>
<root url="file://$USER_HOME$/AppData/Roaming/Pub/Cache/hosted/pub.dartlang.org/analyzer-0.30.0+2/lib" /> <root url="file://$USER_HOME$/AppData/Roaming/Pub/Cache/hosted/pub.dartlang.org/analyzer-0.30.0+2/lib" />
<root url="file://$USER_HOME$/AppData/Roaming/Pub/Cache/hosted/pub.dartlang.org/angel_model-1.0.0/lib" /> <root url="file://$USER_HOME$/AppData/Roaming/Pub/Cache/hosted/pub.dartlang.org/angel_model-1.0.0/lib" />
<root url="file://$USER_HOME$/AppData/Roaming/Pub/Cache/hosted/pub.dartlang.org/angel_route-1.0.3/lib" /> <root url="file://$USER_HOME$/AppData/Roaming/Pub/Cache/hosted/pub.dartlang.org/angel_route-1.0.5/lib" />
<root url="file://$USER_HOME$/AppData/Roaming/Pub/Cache/hosted/pub.dartlang.org/args-0.13.7/lib" /> <root url="file://$USER_HOME$/AppData/Roaming/Pub/Cache/hosted/pub.dartlang.org/args-0.13.7/lib" />
<root url="file://$USER_HOME$/AppData/Roaming/Pub/Cache/hosted/pub.dartlang.org/async-1.13.3/lib" /> <root url="file://$USER_HOME$/AppData/Roaming/Pub/Cache/hosted/pub.dartlang.org/async-1.13.3/lib" />
<root url="file://$USER_HOME$/AppData/Roaming/Pub/Cache/hosted/pub.dartlang.org/barback-0.15.2+11/lib" /> <root url="file://$USER_HOME$/AppData/Roaming/Pub/Cache/hosted/pub.dartlang.org/barback-0.15.2+11/lib" />

View file

@ -0,0 +1,8 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="performance::hello" type="DartCommandLineRunConfigurationType" factoryName="Dart Command Line Application" singleton="true">
<option name="checkedMode" value="false" />
<option name="filePath" value="$PROJECT_DIR$/performance/hello/main.dart" />
<option name="workingDirectory" value="$PROJECT_DIR$" />
<method />
</configuration>
</component>

File diff suppressed because it is too large Load diff

View file

@ -1,3 +1,20 @@
# 1.0.8
* Changed `req.query` to use a modifiable Map if the body has not parsed. Resolves
[#157](https://github.com/angel-dart/framework/issues/157).
* Changed all constants to `camelCase`, and deprecated their `CONSTANT_CASE` counterparts. Resolves
[#155](https://github.com/angel-dart/framework/issues/155).
* Resolved [#156](https://github.com/angel-dart/framework/issues/156) by adding a `graphql` provider.
* Added an `analysis-options.yaml` enabling strong mode. Preparing for Dart 2.0.
* Added a dependency on `package:meta`, resolving [#154](https://github.com/angel-dart/framework/issues/154),
and added corresponding annotations to make extending Angel easier.
* Resolved [#158](https://github.com/angel-dart/framework/issues/158) by using proper `StreamController`
patterns, to prevent memory leaks.
* Route handler sequences are now cached in a Map, so repeat requests will be resolved faster.
* A message is no longer printed in production mode.
* Removed the inheritance on `Extensible` in many classes, and removed it from `angel_route`.
Now, only `Angel` and `RequestContext` have `@proxy` annotations.
* Deprecated passing `debug` to Angel.
# 1.0.7+2 # 1.0.7+2
Changed `ResponseContext.serialize`. The `contentType` is now set *before* serialization. Changed `ResponseContext.serialize`. The `contentType` is now set *before* serialization.

2
analysis_options.yaml Normal file
View file

@ -0,0 +1,2 @@
analyzer:
strong-mode: true

View file

@ -143,8 +143,6 @@ HookedServiceEventListener remove(key, [remover(key, obj)]) {
return obj.where((k) => !key); return obj.where((k) => !key);
else if (obj is Map) else if (obj is Map)
return obj..remove(key); return obj..remove(key);
else if (obj is Extensible)
return obj..properties.remove(key);
else { else {
try { try {
reflect(obj).setField(new Symbol(key), null); reflect(obj).setField(new Symbol(key), null);
@ -231,8 +229,6 @@ HookedServiceEventListener addCreatedAt(
return assign(obj, now); return assign(obj, now);
else if (obj is Map) else if (obj is Map)
obj[name] = now; obj[name] = now;
else if (obj is Extensible)
obj..properties[name] = now;
else { else {
try { try {
reflect(obj).setField(new Symbol(name), now); reflect(obj).setField(new Symbol(name), now);
@ -282,8 +278,6 @@ HookedServiceEventListener addUpdatedAt(
return assign(obj, now); return assign(obj, now);
else if (obj is Map) else if (obj is Map)
obj[name] = now; obj[name] = now;
else if (obj is Extensible)
obj..properties[name] = now;
else { else {
try { try {
reflect(obj).setField(new Symbol(name), now); reflect(obj).setField(new Symbol(name), now);

View file

@ -1,3 +0,0 @@
library angel_framework.extensible;
export 'package:angel_route/src/extensible.dart';

View file

@ -0,0 +1,6 @@
String fastNameFromSymbol(Symbol s) {
String str = s.toString();
int open = str.indexOf('"');
int close = str.lastIndexOf('"');
return str.substring(open + 1, close);
}

View file

@ -2,17 +2,19 @@ library angel_framework.http.angel_base;
import 'dart:async'; import 'dart:async';
import 'package:container/container.dart'; import 'package:container/container.dart';
import '../fast_name_from_symbol.dart';
import 'routable.dart'; import 'routable.dart';
/// A function that asynchronously generates a view from the given path and data. /// A function that asynchronously generates a view from the given path and data.
typedef Future<String> ViewGenerator(String path, [Map data]); typedef Future<String> ViewGenerator(String path, [Map data]);
/// Base class for Angel servers. Do not bother extending this. /// Base class for Angel servers. Do not bother extending this.
@proxy
class AngelBase extends Routable { class AngelBase extends Routable {
AngelBase({bool debug: false}):super(debug: debug);
Container _container = new Container(); Container _container = new Container();
final Map properties = {};
/// When set to true, the request body will not be parsed /// When set to true, the request body will not be parsed
/// automatically. You can call `req.parse()` manually, /// automatically. You can call `req.parse()` manually,
/// or use `lazyBody()`. /// or use `lazyBody()`.
@ -29,4 +31,22 @@ class AngelBase extends Routable {
/// ///
/// Called by [ResponseContext]@`render`. /// Called by [ResponseContext]@`render`.
ViewGenerator viewGenerator = (String view, [Map data]) async => "No view engine has been configured yet."; ViewGenerator viewGenerator = (String view, [Map data]) async => "No view engine has been configured yet.";
operator [](key) => properties[key];
operator []=(key, value) => properties[key] = value;
noSuchMethod(Invocation invocation) {
if (invocation.memberName != null) {
String name = fastNameFromSymbol(invocation.memberName);
if (invocation.isMethod) {
return Function.apply(properties[name], invocation.positionalArguments,
invocation.namedArguments);
} else if (invocation.isGetter) {
return properties[name];
}
}
return super.noSuchMethod(invocation);
}
} }

View file

@ -3,6 +3,7 @@ library angel_framework.http.controller;
import 'dart:async'; import 'dart:async';
import 'dart:mirrors'; import 'dart:mirrors';
import 'package:angel_route/angel_route.dart'; import 'package:angel_route/angel_route.dart';
import 'package:meta/meta.dart';
import 'metadata.dart'; import 'metadata.dart';
import 'request_context.dart'; import 'request_context.dart';
import 'response_context.dart'; import 'response_context.dart';
@ -18,13 +19,20 @@ import 'server.dart' show Angel, preInject;
/// and memory use. /// and memory use.
class InjectionRequest { class InjectionRequest {
/// Optional, typed data that can be passed to a DI-enabled method. /// Optional, typed data that can be passed to a DI-enabled method.
Map<String, Type> named = {}; final Map<String, Type> named;
/// A list of the arguments required for a DI-enabled method to run. /// A list of the arguments required for a DI-enabled method to run.
final List required = []; final List required;
/// A list of the arguments that can be null in a DI-enabled method. /// A list of the arguments that can be null in a DI-enabled method.
final List<String> optional = []; final List<String> optional;
const InjectionRequest.constant({this.named, this.required, this.optional});
InjectionRequest()
: named = {},
required = [],
optional = [];
} }
/// Supports grouping routes with shared functionality. /// Supports grouping routes with shared functionality.
@ -47,6 +55,7 @@ class Controller {
Controller({this.debug: false, this.injectSingleton: true}); Controller({this.debug: false, this.injectSingleton: true});
@mustCallSuper
Future call(Angel app) async { Future call(Angel app) async {
_app = app; _app = app;
@ -143,6 +152,7 @@ RequestHandler createDynamicHandler(handler,
injection.optional.addAll(optional ?? []); injection.optional.addAll(optional ?? []);
return handleContained(handler, injection); return handleContained(handler, injection);
} }
/// Handles a request with a DI-enabled handler. /// Handles a request with a DI-enabled handler.
RequestHandler handleContained(handler, InjectionRequest injection) { RequestHandler handleContained(handler, InjectionRequest injection) {
return (RequestContext req, ResponseContext res) async { return (RequestContext req, ResponseContext res) async {

View file

@ -108,27 +108,27 @@ class HookedService extends Service {
applyListeners(inner.index, beforeIndexed); applyListeners(inner.index, beforeIndexed);
applyListeners(inner.read, beforeRead); applyListeners(inner.read, beforeRead);
applyListeners(inner.created, beforeCreated); applyListeners(inner.create, beforeCreated);
applyListeners(inner.modify, beforeModified); applyListeners(inner.modify, beforeModified);
applyListeners(inner.updated, beforeUpdated); applyListeners(inner.update, beforeUpdated);
applyListeners(inner.removed, beforeRemoved); applyListeners(inner.remove, beforeRemoved);
applyListeners(inner.index, afterIndexed, true); applyListeners(inner.index, afterIndexed, true);
applyListeners(inner.read, afterRead, true); applyListeners(inner.read, afterRead, true);
applyListeners(inner.created, afterCreated, true); applyListeners(inner.create, afterCreated, true);
applyListeners(inner.modify, afterModified, true); applyListeners(inner.modify, afterModified, true);
applyListeners(inner.updated, afterUpdated, true); applyListeners(inner.update, afterUpdated, true);
applyListeners(inner.removed, afterRemoved, true); applyListeners(inner.remove, afterRemoved, true);
} }
/// Adds routes to this instance. /// Adds routes to this instance.
@override @override
void addRoutes() { void addRoutes() {
// Set up our routes. We still need to copy middleware from inner service // Set up our routes. We still need to copy middleware from inner service
Map restProvider = {'provider': Providers.REST}; Map restProvider = {'provider': Providers.rest};
// Add global middleware if declared on the instance itself // Add global middleware if declared on the instance itself
Middleware before = getAnnotation(inner, Middleware); Middleware before = getAnnotation(inner, Middleware);
final handlers = [ List handlers = [
(RequestContext req, ResponseContext res) async { (RequestContext req, ResponseContext res) async {
req.serviceParams req.serviceParams
..['__requestctx'] = req ..['__requestctx'] = req
@ -270,17 +270,17 @@ class HookedService extends Service {
Iterable<String> eventNames, HookedServiceEventListener listener) { Iterable<String> eventNames, HookedServiceEventListener listener) {
eventNames.map((name) { eventNames.map((name) {
switch (name) { switch (name) {
case HookedServiceEvent.INDEXED: case HookedServiceEvent.indexed:
return beforeIndexed; return beforeIndexed;
case HookedServiceEvent.READ: case HookedServiceEvent.read:
return beforeRead; return beforeRead;
case HookedServiceEvent.CREATED: case HookedServiceEvent.created:
return beforeCreated; return beforeCreated;
case HookedServiceEvent.MODIFIED: case HookedServiceEvent.modified:
return beforeModified; return beforeModified;
case HookedServiceEvent.UPDATED: case HookedServiceEvent.updated:
return beforeUpdated; return beforeUpdated;
case HookedServiceEvent.REMOVED: case HookedServiceEvent.removed:
return beforeRemoved; return beforeRemoved;
default: default:
throw new ArgumentError('Invalid service method: ${name}'); throw new ArgumentError('Invalid service method: ${name}');
@ -293,17 +293,17 @@ class HookedService extends Service {
void after(Iterable<String> eventNames, HookedServiceEventListener listener) { void after(Iterable<String> eventNames, HookedServiceEventListener listener) {
eventNames.map((name) { eventNames.map((name) {
switch (name) { switch (name) {
case HookedServiceEvent.INDEXED: case HookedServiceEvent.indexed:
return afterIndexed; return afterIndexed;
case HookedServiceEvent.READ: case HookedServiceEvent.read:
return afterRead; return afterRead;
case HookedServiceEvent.CREATED: case HookedServiceEvent.created:
return afterCreated; return afterCreated;
case HookedServiceEvent.MODIFIED: case HookedServiceEvent.modified:
return afterModified; return afterModified;
case HookedServiceEvent.UPDATED: case HookedServiceEvent.updated:
return afterUpdated; return afterUpdated;
case HookedServiceEvent.REMOVED: case HookedServiceEvent.removed:
return afterRemoved; return afterRemoved;
default: default:
throw new ArgumentError('Invalid service method: ${name}'); throw new ArgumentError('Invalid service method: ${name}');
@ -340,7 +340,7 @@ class HookedService extends Service {
Stream<HookedServiceEvent> beforeAllStream() { Stream<HookedServiceEvent> beforeAllStream() {
var ctrl = new StreamController<HookedServiceEvent>(); var ctrl = new StreamController<HookedServiceEvent>();
_ctrl.add(ctrl); _ctrl.add(ctrl);
before(HookedServiceEvent.ALL, ctrl.add); before(HookedServiceEvent.all, ctrl.add);
return ctrl.stream; return ctrl.stream;
} }
@ -352,7 +352,7 @@ class HookedService extends Service {
Stream<HookedServiceEvent> afterAllStream() { Stream<HookedServiceEvent> afterAllStream() {
var ctrl = new StreamController<HookedServiceEvent>(); var ctrl = new StreamController<HookedServiceEvent>();
_ctrl.add(ctrl); _ctrl.add(ctrl);
before(HookedServiceEvent.ALL, ctrl.add); before(HookedServiceEvent.all, ctrl.add);
return ctrl.stream; return ctrl.stream;
} }
@ -392,12 +392,12 @@ class HookedService extends Service {
var params = _stripReq(_params); var params = _stripReq(_params);
HookedServiceEvent before = await beforeIndexed._emit( HookedServiceEvent before = await beforeIndexed._emit(
new HookedServiceEvent(false, _getRequest(_params), new HookedServiceEvent(false, _getRequest(_params),
_getResponse(_params), inner, HookedServiceEvent.INDEXED, _getResponse(_params), inner, HookedServiceEvent.indexed,
params: params)); params: params));
if (before._canceled) { if (before._canceled) {
HookedServiceEvent after = await beforeIndexed._emit( HookedServiceEvent after = await beforeIndexed._emit(
new HookedServiceEvent(true, _getRequest(_params), new HookedServiceEvent(true, _getRequest(_params),
_getResponse(_params), inner, HookedServiceEvent.INDEXED, _getResponse(_params), inner, HookedServiceEvent.indexed,
params: params, result: before.result)); params: params, result: before.result));
return after.result; return after.result;
} }
@ -408,7 +408,7 @@ class HookedService extends Service {
_getRequest(_params), _getRequest(_params),
_getResponse(_params), _getResponse(_params),
inner, inner,
HookedServiceEvent.INDEXED, HookedServiceEvent.indexed,
params: params, params: params,
result: result)); result: result));
return after.result; return after.result;
@ -422,7 +422,7 @@ class HookedService extends Service {
_getRequest(_params), _getRequest(_params),
_getResponse(_params), _getResponse(_params),
inner, inner,
HookedServiceEvent.READ, HookedServiceEvent.indexed,
id: id, id: id,
params: params)); params: params));
@ -432,7 +432,7 @@ class HookedService extends Service {
_getRequest(_params), _getRequest(_params),
_getResponse(_params), _getResponse(_params),
inner, inner,
HookedServiceEvent.READ, HookedServiceEvent.read,
id: id, id: id,
params: params, params: params,
result: before.result)); result: before.result));
@ -445,7 +445,7 @@ class HookedService extends Service {
_getRequest(_params), _getRequest(_params),
_getResponse(_params), _getResponse(_params),
inner, inner,
HookedServiceEvent.READ, HookedServiceEvent.read,
id: id, id: id,
params: params, params: params,
result: result)); result: result));
@ -457,13 +457,13 @@ class HookedService extends Service {
var params = _stripReq(_params); var params = _stripReq(_params);
HookedServiceEvent before = await beforeCreated._emit( HookedServiceEvent before = await beforeCreated._emit(
new HookedServiceEvent(false, _getRequest(_params), new HookedServiceEvent(false, _getRequest(_params),
_getResponse(_params), inner, HookedServiceEvent.CREATED, _getResponse(_params), inner, HookedServiceEvent.created,
data: data, params: params)); data: data, params: params));
if (before._canceled) { if (before._canceled) {
HookedServiceEvent after = await afterCreated._emit( HookedServiceEvent after = await afterCreated._emit(
new HookedServiceEvent(true, _getRequest(_params), new HookedServiceEvent(true, _getRequest(_params),
_getResponse(_params), inner, HookedServiceEvent.CREATED, _getResponse(_params), inner, HookedServiceEvent.created,
data: data, params: params, result: before.result)); data: data, params: params, result: before.result));
return after.result; return after.result;
} }
@ -474,7 +474,7 @@ class HookedService extends Service {
_getRequest(_params), _getRequest(_params),
_getResponse(_params), _getResponse(_params),
inner, inner,
HookedServiceEvent.CREATED, HookedServiceEvent.created,
data: data, data: data,
params: params, params: params,
result: result)); result: result));
@ -486,13 +486,13 @@ class HookedService extends Service {
var params = _stripReq(_params); var params = _stripReq(_params);
HookedServiceEvent before = await beforeModified._emit( HookedServiceEvent before = await beforeModified._emit(
new HookedServiceEvent(false, _getRequest(_params), new HookedServiceEvent(false, _getRequest(_params),
_getResponse(_params), inner, HookedServiceEvent.MODIFIED, _getResponse(_params), inner, HookedServiceEvent.modified,
id: id, data: data, params: params)); id: id, data: data, params: params));
if (before._canceled) { if (before._canceled) {
HookedServiceEvent after = await afterModified._emit( HookedServiceEvent after = await afterModified._emit(
new HookedServiceEvent(true, _getRequest(_params), new HookedServiceEvent(true, _getRequest(_params),
_getResponse(_params), inner, HookedServiceEvent.MODIFIED, _getResponse(_params), inner, HookedServiceEvent.modified,
id: id, data: data, params: params, result: before.result)); id: id, data: data, params: params, result: before.result));
return after.result; return after.result;
} }
@ -503,7 +503,7 @@ class HookedService extends Service {
_getRequest(_params), _getRequest(_params),
_getResponse(_params), _getResponse(_params),
inner, inner,
HookedServiceEvent.MODIFIED, HookedServiceEvent.modified,
id: id, id: id,
data: data, data: data,
params: params, params: params,
@ -516,13 +516,13 @@ class HookedService extends Service {
var params = _stripReq(_params); var params = _stripReq(_params);
HookedServiceEvent before = await beforeUpdated._emit( HookedServiceEvent before = await beforeUpdated._emit(
new HookedServiceEvent(false, _getRequest(_params), new HookedServiceEvent(false, _getRequest(_params),
_getResponse(_params), inner, HookedServiceEvent.UPDATED, _getResponse(_params), inner, HookedServiceEvent.updated,
id: id, data: data, params: params)); id: id, data: data, params: params));
if (before._canceled) { if (before._canceled) {
HookedServiceEvent after = await afterUpdated._emit( HookedServiceEvent after = await afterUpdated._emit(
new HookedServiceEvent(true, _getRequest(_params), new HookedServiceEvent(true, _getRequest(_params),
_getResponse(_params), inner, HookedServiceEvent.UPDATED, _getResponse(_params), inner, HookedServiceEvent.updated,
id: id, data: data, params: params, result: before.result)); id: id, data: data, params: params, result: before.result));
return after.result; return after.result;
} }
@ -533,7 +533,7 @@ class HookedService extends Service {
_getRequest(_params), _getRequest(_params),
_getResponse(_params), _getResponse(_params),
inner, inner,
HookedServiceEvent.UPDATED, HookedServiceEvent.updated,
id: id, id: id,
data: data, data: data,
params: params, params: params,
@ -546,13 +546,13 @@ class HookedService extends Service {
var params = _stripReq(_params); var params = _stripReq(_params);
HookedServiceEvent before = await beforeRemoved._emit( HookedServiceEvent before = await beforeRemoved._emit(
new HookedServiceEvent(false, _getRequest(_params), new HookedServiceEvent(false, _getRequest(_params),
_getResponse(_params), inner, HookedServiceEvent.REMOVED, _getResponse(_params), inner, HookedServiceEvent.removed,
id: id, params: params)); id: id, params: params));
if (before._canceled) { if (before._canceled) {
HookedServiceEvent after = await afterRemoved._emit( HookedServiceEvent after = await afterRemoved._emit(
new HookedServiceEvent(true, _getRequest(_params), new HookedServiceEvent(true, _getRequest(_params),
_getResponse(_params), inner, HookedServiceEvent.REMOVED, _getResponse(_params), inner, HookedServiceEvent.removed,
id: id, params: params, result: before.result)); id: id, params: params, result: before.result));
return after.result; return after.result;
} }
@ -563,7 +563,7 @@ class HookedService extends Service {
_getRequest(_params), _getRequest(_params),
_getResponse(_params), _getResponse(_params),
inner, inner,
HookedServiceEvent.REMOVED, HookedServiceEvent.removed,
id: id, id: id,
params: params, params: params,
result: result)); result: result));
@ -577,22 +577,22 @@ class HookedService extends Service {
HookedServiceEventDispatcher dispatcher; HookedServiceEventDispatcher dispatcher;
switch (eventName) { switch (eventName) {
case HookedServiceEvent.INDEXED: case HookedServiceEvent.indexed:
dispatcher = afterIndexed; dispatcher = afterIndexed;
break; break;
case HookedServiceEvent.READ: case HookedServiceEvent.read:
dispatcher = afterRead; dispatcher = afterRead;
break; break;
case HookedServiceEvent.CREATED: case HookedServiceEvent.created:
dispatcher = afterCreated; dispatcher = afterCreated;
break; break;
case HookedServiceEvent.MODIFIED: case HookedServiceEvent.modified:
dispatcher = afterModified; dispatcher = afterModified;
break; break;
case HookedServiceEvent.UPDATED: case HookedServiceEvent.updated:
dispatcher = afterUpdated; dispatcher = afterUpdated;
break; break;
case HookedServiceEvent.REMOVED: case HookedServiceEvent.removed:
dispatcher = afterRemoved; dispatcher = afterRemoved;
break; break;
default: default:
@ -614,21 +614,45 @@ class HookedService extends Service {
/// Fired when a hooked service is invoked. /// Fired when a hooked service is invoked.
class HookedServiceEvent { class HookedServiceEvent {
static const String INDEXED = "indexed"; static const String indexed = 'indexed';
static const String READ = "read"; static const String read = 'read';
static const String CREATED = "created"; static const String created = 'created';
static const String MODIFIED = "modified"; static const String modified = 'modified';
static const String UPDATED = "updated"; static const String updated = 'updated';
static const String REMOVED = "removed"; static const String removed = 'removed';
static const List<String> ALL = const [
INDEXED, static const List<String> all = const [
READ, indexed, read, created, modified, updated, removed
CREATED,
MODIFIED,
UPDATED,
REMOVED
]; ];
/// Use [indexed] instead.
@deprecated
static const String INDEXED = indexed;
/// Use [read] instead.
@deprecated
static const String READ = read;
/// Use [created] instead.
@deprecated
static const String CREATED = created;
/// Use [modified] instead.
@deprecated
static const String MODIFIED = modified;
/// Use [updated] instead.
@deprecated
static const String UPDATED = updated;
/// Use [removed] instead.
@deprecated
static const String REMOVED = removed;
/// Use [all] instead.
@deprecated
static const List<String> ALL = all;
/// Use this to end processing of an event. /// Use this to end processing of an event.
void cancel([result]) { void cancel([result]) {
_canceled = true; _canceled = true;

View file

@ -2,18 +2,23 @@ library angel_framework.http.request_context;
import 'dart:async'; import 'dart:async';
import 'dart:io'; import 'dart:io';
import 'package:angel_route/src/extensible.dart';
import 'package:body_parser/body_parser.dart'; import 'package:body_parser/body_parser.dart';
import 'package:charcode/charcode.dart';
import '../fast_name_from_symbol.dart';
import 'server.dart' show Angel; import 'server.dart' show Angel;
/// A convenience wrapper around an incoming HTTP request. /// A convenience wrapper around an incoming HTTP request.
class RequestContext extends Extensible { @proxy
class RequestContext {
String _acceptHeaderCache; String _acceptHeaderCache;
bool _acceptsAllCache; bool _acceptsAllCache;
BodyParseResult _body; BodyParseResult _body;
ContentType _contentType; ContentType _contentType;
HttpRequest _io; HttpRequest _io;
String _override, _path; String _override, _path;
Map _provisionalQuery;
final Map properties = {};
/// Additional params to be passed to services. /// Additional params to be passed to services.
final Map serviceParams = {}; final Map serviceParams = {};
@ -106,7 +111,7 @@ class RequestContext extends Extensible {
/// **If you are writing a plug-in, consider using [lazyQuery] instead.** /// **If you are writing a plug-in, consider using [lazyQuery] instead.**
Map get query { Map get query {
if (_body == null) if (_body == null)
return uri.queryParameters; return _provisionalQuery ??= new Map.from(uri.queryParameters);
else else
return _body.query; return _body.query;
} }
@ -145,16 +150,36 @@ class RequestContext extends Extensible {
ctx.app = app; ctx.app = app;
ctx._contentType = request.headers.contentType; ctx._contentType = request.headers.contentType;
ctx._override = override; ctx._override = override;
ctx._path = request.uri
.toString() // Faster way to get path
.replaceAll("?" + request.uri.query, "") List<int> _path = [];
.replaceAll(new RegExp(r'/+$'), '');
// Go up until we reach a ?
for (int ch in request.uri.toString().codeUnits) {
if (ch != $question)
_path.add(ch);
else break;
}
// Remove trailing slashes
int lastSlash = -1;
for (int i = _path.length - 1; i >= 0; i--) {
if (_path[i] == $slash)
lastSlash = i;
else break;
}
if (lastSlash > -1)
ctx._path = new String.fromCharCodes(_path.take(lastSlash));
else ctx._path = new String.fromCharCodes(_path);
ctx._io = request; ctx._io = request;
if (app.lazyParseBodies != true) if (app.lazyParseBodies != true) {
ctx._body = (await parseBody(request, ctx._body = (await parseBody(request,
storeOriginalBuffer: app.storeOriginalBuffer == true)) ?? storeOriginalBuffer: app.storeOriginalBuffer == true)) ??
{}; {};
}
return ctx; return ctx;
} }
@ -246,7 +271,26 @@ class RequestContext extends Extensible {
if (_body != null) if (_body != null)
return _body; return _body;
else else
_provisionalQuery = null;
return _body = await parseBody(io, return _body = await parseBody(io,
storeOriginalBuffer: app.storeOriginalBuffer == true); storeOriginalBuffer: app.storeOriginalBuffer == true);
} }
operator [](key) => properties[key];
operator []=(key, value) => properties[key] = value;
noSuchMethod(Invocation invocation) {
if (invocation.memberName != null) {
String name = fastNameFromSymbol(invocation.memberName);
if (invocation.isMethod) {
return Function.apply(properties[name], invocation.positionalArguments,
invocation.namedArguments);
} else if (invocation.isGetter) {
return properties[name];
}
}
return super.noSuchMethod(invocation);
}
} }

View file

@ -3,10 +3,10 @@ library angel_framework.http.response_context;
import 'dart:async'; import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'dart:typed_data';
import 'package:angel_route/angel_route.dart'; import 'package:angel_route/angel_route.dart';
import 'package:json_god/json_god.dart' as god; import 'package:json_god/json_god.dart' as god;
import 'package:mime/mime.dart'; import 'package:mime/mime.dart';
import '../extensible.dart';
import 'server.dart' show Angel; import 'server.dart' show Angel;
import 'controller.dart'; import 'controller.dart';
@ -19,9 +19,10 @@ final RegExp _straySlashes = new RegExp(r'(^/+)|(/+$)');
typedef String ResponseSerializer(data); typedef String ResponseSerializer(data);
/// A convenience wrapper around an outgoing HTTP request. /// A convenience wrapper around an outgoing HTTP request.
class ResponseContext extends Extensible implements StringSink { class ResponseContext implements StringSink {
final _LockableBytesBuilder _buffer = new _LockableBytesBuilder(); final _LockableBytesBuilder _buffer = new _LockableBytesBuilder();
final Map<String, String> _headers = {HttpHeaders.SERVER: 'angel'}; final Map<String, String> _headers = {HttpHeaders.SERVER: 'angel'};
final Map properties = {};
bool _isOpen = true, _isClosed = false; bool _isOpen = true, _isClosed = false;
int _statusCode = 200; int _statusCode = 200;
@ -366,7 +367,7 @@ abstract class _LockableBytesBuilder extends BytesBuilder {
class _LockableBytesBuilderImpl implements _LockableBytesBuilder { class _LockableBytesBuilderImpl implements _LockableBytesBuilder {
bool _closed = false; bool _closed = false;
final List<int> _data = []; Uint8List _data = new Uint8List(0);
StateError _deny() => StateError _deny() =>
new StateError('Cannot modified a closed response\'s buffer.'); new StateError('Cannot modified a closed response\'s buffer.');
@ -385,8 +386,19 @@ class _LockableBytesBuilderImpl implements _LockableBytesBuilder {
void add(List<int> bytes) { void add(List<int> bytes) {
if (_closed) if (_closed)
throw _deny(); throw _deny();
else { else if (bytes.isNotEmpty) {
_data.addAll(bytes); int len = _data.length + bytes.length;
var d = new Uint8List(len);
for (int i = 0; i < _data.length; i++) {
d[i] = _data[i];
}
for (int i = 0; i < bytes.length; i++) {
d[i + _data.length] = bytes[i];
}
_data = d;
} }
} }
@ -395,7 +407,15 @@ class _LockableBytesBuilderImpl implements _LockableBytesBuilder {
if (_closed) if (_closed)
throw _deny(); throw _deny();
else { else {
_data.add(byte); int len = _data.length + 1;
var d = new Uint8List(len);
for (int i = 0; i < _data.length; i++) {
d[i] = _data[i];
}
d[_data.length] = byte;
_data = d;
} }
} }
@ -404,7 +424,7 @@ class _LockableBytesBuilderImpl implements _LockableBytesBuilder {
if (_closed) if (_closed)
throw _deny(); throw _deny();
else { else {
_data.clear(); for (int i = 0; i < _data.length; i++) _data[i] = 0;
} }
} }
@ -422,7 +442,7 @@ class _LockableBytesBuilderImpl implements _LockableBytesBuilder {
if (_closed) if (_closed)
return toBytes(); return toBytes();
else { else {
var r = new List<int>.from(_data); var r = new Uint8List.fromList(_data);
clear(); clear();
return r; return r;
} }

View file

@ -2,6 +2,7 @@ library angel_framework.http.routable;
import 'dart:async'; import 'dart:async';
import 'package:angel_route/angel_route.dart'; import 'package:angel_route/angel_route.dart';
import 'package:meta/meta.dart';
import '../util.dart'; import '../util.dart';
import 'angel_base.dart'; import 'angel_base.dart';
import 'controller.dart'; import 'controller.dart';
@ -36,6 +37,7 @@ RequestMiddleware waterfall(List handlers) {
class Routable extends Router { class Routable extends Router {
final Map<Pattern, Controller> _controllers = {}; final Map<Pattern, Controller> _controllers = {};
final Map<Pattern, Service> _services = {}; final Map<Pattern, Service> _services = {};
final Map properties = {};
Routable({bool debug: false}) : super(debug: debug); Routable({bool debug: false}) : super(debug: debug);
@ -59,7 +61,7 @@ class Routable extends Router {
/// Assigns a middleware to a name for convenience. /// Assigns a middleware to a name for convenience.
@override @override
registerMiddleware(String name, RequestMiddleware middleware) => registerMiddleware(String name, @checked RequestHandler middleware) =>
super.registerMiddleware(name, middleware); super.registerMiddleware(name, middleware);
/// Retrieves the service assigned to the given path. /// Retrieves the service assigned to the given path.
@ -129,17 +131,19 @@ class Routable extends Router {
} }
// Also copy properties... // Also copy properties...
if (router is Routable) {
Map copiedProperties = new Map.from(router.properties); Map copiedProperties = new Map.from(router.properties);
for (String propertyName in copiedProperties.keys) { for (String propertyName in copiedProperties.keys) {
properties.putIfAbsent("$middlewarePrefix$propertyName", properties.putIfAbsent("$middlewarePrefix$propertyName",
() => copiedMiddleware[propertyName]); () => copiedMiddleware[propertyName]);
} }
}
// _router.dumpTree(header: 'Mounting on "$path":'); // _router.dumpTree(header: 'Mounting on "$path":');
// root.child(path, debug: debug, handlers: handlers).addChild(router.root); // root.child(path, debug: debug, handlers: handlers).addChild(router.root);
mount(path, _router); mount(path, _router);
if (router is Routable) { if (_router is Routable) {
// Copy services, too. :) // Copy services, too. :)
for (Pattern servicePath in _router._services.keys) { for (Pattern servicePath in _router._services.keys) {
String newServicePath = String newServicePath =
@ -149,6 +153,8 @@ class Routable extends Router {
} }
} }
if (service != null) _onService.add(service); if (service != null) {
if (_onService.hasListener) _onService.add(service);
}
} }
} }

View file

@ -3,10 +3,13 @@ library angel_framework.http.server;
import 'dart:async'; import 'dart:async';
import 'dart:io'; import 'dart:io';
import 'dart:mirrors'; import 'dart:mirrors';
import 'package:angel_route/angel_route.dart'; import 'package:angel_route/angel_route.dart' hide Extensible;
import 'package:charcode/charcode.dart';
export 'package:container/container.dart'; export 'package:container/container.dart';
import 'package:flatten/flatten.dart'; import 'package:flatten/flatten.dart';
import 'package:json_god/json_god.dart' as god; import 'package:json_god/json_god.dart' as god;
import 'package:meta/meta.dart';
import '../safe_stream_controller.dart';
import 'angel_base.dart'; import 'angel_base.dart';
import 'angel_http_exception.dart'; import 'angel_http_exception.dart';
import 'controller.dart'; import 'controller.dart';
@ -31,18 +34,18 @@ typedef Future AngelConfigurer(Angel app);
/// A powerful real-time/REST/MVC server class. /// A powerful real-time/REST/MVC server class.
class Angel extends AngelBase { class Angel extends AngelBase {
StreamController<HttpRequest> _afterProcessed = SafeCtrl<HttpRequest> _afterProcessed = new SafeCtrl<HttpRequest>.broadcast();
new StreamController<HttpRequest>.broadcast(); SafeCtrl<HttpRequest> _beforeProcessed =
StreamController<HttpRequest> _beforeProcessed = new SafeCtrl<HttpRequest>.broadcast();
new StreamController<HttpRequest>.broadcast(); SafeCtrl<AngelFatalError> _fatalErrorStream =
StreamController<AngelFatalError> _fatalErrorStream = new SafeCtrl<AngelFatalError>.broadcast();
new StreamController<AngelFatalError>.broadcast(); SafeCtrl<Controller> _onController = new SafeCtrl<Controller>.broadcast();
StreamController<Controller> _onController =
new StreamController<Controller>.broadcast();
final List<Angel> _children = []; final List<Angel> _children = [];
Router _flattened; Router _flattened;
bool _isProduction; bool _isProduction;
Angel _parent; Angel _parent;
final Map<String, List> _handlerCache = {};
ServerGenerator _serverGenerator = HttpServer.bind; ServerGenerator _serverGenerator = HttpServer.bind;
/// A global Map of manual injections. You usually will not want to touch this. /// A global Map of manual injections. You usually will not want to touch this.
@ -152,7 +155,7 @@ class Angel extends AngelBase {
} }
@override @override
Route addRoute(String method, String path, Object handler, Route addRoute(String method, Pattern path, Object handler,
{List middleware: const []}) { {List middleware: const []}) {
if (_flattened != null) { if (_flattened != null) {
print( print(
@ -308,9 +311,9 @@ class Angel extends AngelBase {
}); });
} }
Future<ResponseContext> createResponseContext(HttpResponse response) async => Future<ResponseContext> createResponseContext(HttpResponse response) =>
new ResponseContext(response, this) new Future<ResponseContext>.value(new ResponseContext(response, this)
..serializer = (_serializer ?? god.serialize); ..serializer = (_serializer ?? god.serialize));
/// Attempts to find a middleware by the given name within this application. /// Attempts to find a middleware by the given name within this application.
findMiddleware(key) { findMiddleware(key) {
@ -361,10 +364,29 @@ class Angel extends AngelBase {
try { try {
var req = await createRequestContext(request); var req = await createRequestContext(request);
var res = await createResponseContext(request.response); var res = await createResponseContext(request.response);
String requestedUrl = request.uri.path.replaceAll(_straySlashes, ''); String requestedUrl;
// Faster way to get path
List<int> _path = request.uri.path.codeUnits;
// Remove trailing slashes
int lastSlash = -1;
for (int i = _path.length - 1; i >= 0; i--) {
if (_path[i] == $slash)
lastSlash = i;
else
break;
}
if (lastSlash > -1)
requestedUrl = new String.fromCharCodes(_path.take(lastSlash));
else
requestedUrl = new String.fromCharCodes(_path);
if (requestedUrl.isEmpty) requestedUrl = '/'; if (requestedUrl.isEmpty) requestedUrl = '/';
var pipeline = _handlerCache.putIfAbsent(requestedUrl, () {
Router r = Router r =
isProduction ? (_flattened ?? (_flattened = flatten(this))) : this; isProduction ? (_flattened ?? (_flattened = flatten(this))) : this;
var resolved = var resolved =
@ -380,23 +402,12 @@ class Angel extends AngelBase {
var m = new MiddlewarePipeline(resolved); var m = new MiddlewarePipeline(resolved);
req.inject(MiddlewarePipeline, m); req.inject(MiddlewarePipeline, m);
var pipeline = []..addAll(before)..addAll(m.handlers)..addAll(after); return new List.from(before)..addAll(m.handlers)..addAll(after);
});
// _printDebug('Handler sequence on $requestedUrl: $pipeline');
for (var handler in pipeline) { for (var handler in pipeline) {
try { try {
// _printDebug('Executing handler: $handler'); if (!await executeHandler(handler, req, res)) break;
var result = await executeHandler(handler, req, res);
// _printDebug('Result: $result');
if (!result) {
// _printDebug('Last executed handler: $handler');
break;
} else {
// _printDebug(
// 'Handler completed successfully, did not terminate response: $handler');
}
} on AngelHttpException catch (e, st) { } on AngelHttpException catch (e, st) {
e.stackTrace ??= st; e.stackTrace ??= st;
return await handleAngelHttpException(e, st, req, res, request); return await handleAngelHttpException(e, st, req, res, request);
@ -448,7 +459,8 @@ class Angel extends AngelBase {
if (_flattened == null) _flattened = flatten(this); if (_flattened == null) _flattened = flatten(this);
_walk(_flattened); _walk(_flattened);
print('Angel is running in production mode.');
//if (silent != true) print('Angel is running in production mode.');
} }
} }
@ -482,15 +494,16 @@ class Angel extends AngelBase {
/// Sends a response. /// Sends a response.
Future sendResponse( Future sendResponse(
HttpRequest request, RequestContext req, ResponseContext res, HttpRequest request, RequestContext req, ResponseContext res,
{bool ignoreFinalizers: false}) async { {bool ignoreFinalizers: false}) {
_afterProcessed.add(request); _afterProcessed.add(request);
if (!res.willCloseItself) { if (res.willCloseItself) {
if (ignoreFinalizers != true) { return new Future.value();
for (var finalizer in responseFinalizers) { } else {
await finalizer(req, res); Future finalizers = ignoreFinalizers == true
} ? new Future.value()
} : responseFinalizers.fold<Future>(
new Future.value(), (out, f) => out.then((_) => f(req, res)));
if (res.isOpen) res.end(); if (res.isOpen) res.end();
@ -505,8 +518,9 @@ class Angel extends AngelBase {
request.response request.response
..statusCode = res.statusCode ..statusCode = res.statusCode
..cookies.addAll(res.cookies) ..cookies.addAll(res.cookies)
..add(res.buffer.takeBytes()); ..add(res.buffer.toBytes());
await request.response.close();
return finalizers.then((_) => request.response.close());
} }
} }
@ -541,7 +555,7 @@ class Angel extends AngelBase {
/// NOTE: The above will not be properly copied if [path] is /// NOTE: The above will not be properly copied if [path] is
/// a [RegExp]. /// a [RegExp].
@override @override
use(Pattern path, Routable routable, use(Pattern path, @checked Routable routable,
{bool hooked: true, String namespace: null}) { {bool hooked: true, String namespace: null}) {
var head = path.toString().replaceAll(_straySlashes, ''); var head = path.toString().replaceAll(_straySlashes, '');
@ -581,6 +595,22 @@ class Angel extends AngelBase {
var tail = k.toString().replaceAll(_straySlashes, ''); var tail = k.toString().replaceAll(_straySlashes, '');
services['$head/$tail'.replaceAll(_straySlashes, '')] = v; services['$head/$tail'.replaceAll(_straySlashes, '')] = v;
}); });
_beforeProcessed.whenInitialized(() {
routable.beforeProcessed.listen(_beforeProcessed.add);
});
_afterProcessed.whenInitialized(() {
routable.afterProcessed.listen(_afterProcessed.add);
});
_fatalErrorStream.whenInitialized(() {
routable.fatalErrorStream.listen(_fatalErrorStream.add);
});
_onController.whenInitialized(() {
routable.onController.listen(_onController.add);
});
} }
if (routable is Service) { if (routable is Service) {
@ -597,17 +627,18 @@ class Angel extends AngelBase {
} }
/// Default constructor. ;) /// Default constructor. ;)
Angel({bool debug: false}) : super(debug: debug == true) { Angel({@deprecated bool debug: false}) : super() {
bootstrapContainer(); bootstrapContainer();
} }
/// An instance mounted on a server started by the [serverGenerator]. /// An instance mounted on a server started by the [serverGenerator].
factory Angel.custom(ServerGenerator serverGenerator, {bool debug: false}) => factory Angel.custom(ServerGenerator serverGenerator,
new Angel(debug: debug == true).._serverGenerator = serverGenerator; {@deprecated bool debug: false}) =>
new Angel().._serverGenerator = serverGenerator;
factory Angel.fromSecurityContext(SecurityContext context, factory Angel.fromSecurityContext(SecurityContext context,
{bool debug: false}) { {@deprecated bool debug: false}) {
var app = new Angel(debug: debug == true); var app = new Angel();
app._serverGenerator = (InternetAddress address, int port) async { app._serverGenerator = (InternetAddress address, int port) async {
return await HttpServer.bindSecure(address, port, context); return await HttpServer.bindSecure(address, port, context);

View file

@ -20,14 +20,34 @@ class Providers {
const Providers(String this.via); const Providers(String this.via);
static const String viaRest = "rest";
static const String viaWebsocket = "websocket";
static const String viaGraphQL = "graphql";
/// Use [viaRest] instead.
@deprecated
static const String VIA_REST = "rest"; static const String VIA_REST = "rest";
/// Use [viaWebSocket] instead.
@deprecated
static const String VIA_WEBSOCKET = "websocket"; static const String VIA_WEBSOCKET = "websocket";
/// Use [rest] instead.
@deprecated
static const Providers REST = const Providers(viaRest);
/// Use [websocket] instead.
@deprecated
static const Providers WEBSOCKET = const Providers(viaWebsocket);
/// Represents a request via REST. /// Represents a request via REST.
static final Providers REST = const Providers(VIA_REST); static const Providers rest = const Providers(viaRest);
/// Represents a request over WebSockets. /// Represents a request over WebSockets.
static final Providers WEBSOCKET = const Providers(VIA_WEBSOCKET); static const Providers websocket = const Providers(viaWebsocket);
/// Represents a request parsed from GraphQL.
static const Providers graphql = const Providers(viaGraphQL);
@override @override
bool operator ==(other) => other is Providers && other.via == via; bool operator ==(other) => other is Providers && other.via == via;
@ -38,13 +58,17 @@ class Providers {
/// Heavily inspired by FeathersJS. <3 /// Heavily inspired by FeathersJS. <3
class Service extends Routable { class Service extends Routable {
/// A [List] of keys that services should ignore, should they see them in the query. /// A [List] of keys that services should ignore, should they see them in the query.
static const List<String> SPECIAL_QUERY_KEYS = const [ static const List<String> specialQueryKeys = const [
r'$limit', r'$limit',
r'$sort', r'$sort',
'page', 'page',
'token' 'token'
]; ];
/// Use [specialQueryKeys] instead.
@deprecated
static const List<String> SPECIAL_QUERY_KEYS = specialQueryKeys;
/// The [Angel] app powering this service. /// The [Angel] app powering this service.
AngelBase app; AngelBase app;
@ -91,7 +115,7 @@ class Service extends Routable {
/// Generates RESTful routes pointing to this class's methods. /// Generates RESTful routes pointing to this class's methods.
void addRoutes() { void addRoutes() {
Map restProvider = {'provider': Providers.REST}; Map restProvider = {'provider': Providers.rest};
// Add global middleware if declared on the instance itself // Add global middleware if declared on the instance itself
Middleware before = getAnnotation(this, Middleware); Middleware before = getAnnotation(this, Middleware);
@ -107,8 +131,9 @@ class Service extends Routable {
req.serviceParams req.serviceParams
])); ]));
}, },
middleware: []..addAll(handlers)..addAll( middleware: []
(indexMiddleware == null) ? [] : indexMiddleware.handlers)); ..addAll(handlers)
..addAll((indexMiddleware == null) ? [] : indexMiddleware.handlers));
Middleware createMiddleware = getAnnotation(this.create, Middleware); Middleware createMiddleware = getAnnotation(this.create, Middleware);
post('/', (req, ResponseContext res) async { post('/', (req, ResponseContext res) async {
@ -122,29 +147,30 @@ class Service extends Routable {
res.statusCode = 201; res.statusCode = 201;
return r; return r;
}, },
middleware: []..addAll(handlers)..addAll( middleware: []
..addAll(handlers)
..addAll(
(createMiddleware == null) ? [] : createMiddleware.handlers)); (createMiddleware == null) ? [] : createMiddleware.handlers));
Middleware readMiddleware = getAnnotation(this.read, Middleware); Middleware readMiddleware = getAnnotation(this.read, Middleware);
get( get(
'/:id', '/:id',
(req, res) async => (req, res) async => await this.read(
await this.read(
toId(req.params['id']), toId(req.params['id']),
mergeMap([ mergeMap([
{'query': req.query}, {'query': req.query},
restProvider, restProvider,
req.serviceParams req.serviceParams
])), ])),
middleware: []..addAll(handlers)..addAll( middleware: []
(readMiddleware == null) ? [] : readMiddleware.handlers)); ..addAll(handlers)
..addAll((readMiddleware == null) ? [] : readMiddleware.handlers));
Middleware modifyMiddleware = getAnnotation(this.modify, Middleware); Middleware modifyMiddleware = getAnnotation(this.modify, Middleware);
patch( patch(
'/:id', '/:id',
(req, res) async => (req, res) async => await this.modify(
await this.modify(
toId(req.params['id']), toId(req.params['id']),
await req.lazyBody(), await req.lazyBody(),
mergeMap([ mergeMap([
@ -152,14 +178,15 @@ class Service extends Routable {
restProvider, restProvider,
req.serviceParams req.serviceParams
])), ])),
middleware: []..addAll(handlers)..addAll( middleware: []
..addAll(handlers)
..addAll(
(modifyMiddleware == null) ? [] : modifyMiddleware.handlers)); (modifyMiddleware == null) ? [] : modifyMiddleware.handlers));
Middleware updateMiddleware = getAnnotation(this.update, Middleware); Middleware updateMiddleware = getAnnotation(this.update, Middleware);
post( post(
'/:id', '/:id',
(req, res) async => (req, res) async => await this.update(
await this.update(
toId(req.params['id']), toId(req.params['id']),
await req.lazyBody(), await req.lazyBody(),
mergeMap([ mergeMap([
@ -167,12 +194,13 @@ class Service extends Routable {
restProvider, restProvider,
req.serviceParams req.serviceParams
])), ])),
middleware: []..addAll(handlers)..addAll( middleware: []
..addAll(handlers)
..addAll(
(updateMiddleware == null) ? [] : updateMiddleware.handlers)); (updateMiddleware == null) ? [] : updateMiddleware.handlers));
put( put(
'/:id', '/:id',
(req, res) async => (req, res) async => await this.update(
await this.update(
toId(req.params['id']), toId(req.params['id']),
await req.lazyBody(), await req.lazyBody(),
mergeMap([ mergeMap([
@ -180,33 +208,37 @@ class Service extends Routable {
restProvider, restProvider,
req.serviceParams req.serviceParams
])), ])),
middleware: []..addAll(handlers)..addAll( middleware: []
..addAll(handlers)
..addAll(
(updateMiddleware == null) ? [] : updateMiddleware.handlers)); (updateMiddleware == null) ? [] : updateMiddleware.handlers));
Middleware removeMiddleware = getAnnotation(this.remove, Middleware); Middleware removeMiddleware = getAnnotation(this.remove, Middleware);
delete( delete(
'/', '/',
(req, res) async => (req, res) async => await this.remove(
await this.remove(
null, null,
mergeMap([ mergeMap([
{'query': req.query}, {'query': req.query},
restProvider, restProvider,
req.serviceParams req.serviceParams
])), ])),
middleware: []..addAll(handlers)..addAll( middleware: []
..addAll(handlers)
..addAll(
(removeMiddleware == null) ? [] : removeMiddleware.handlers)); (removeMiddleware == null) ? [] : removeMiddleware.handlers));
delete( delete(
'/:id', '/:id',
(req, res) async => (req, res) async => await this.remove(
await this.remove(
toId(req.params['id']), toId(req.params['id']),
mergeMap([ mergeMap([
{'query': req.query}, {'query': req.query},
restProvider, restProvider,
req.serviceParams req.serviceParams
])), ])),
middleware: []..addAll(handlers)..addAll( middleware: []
..addAll(handlers)
..addAll(
(removeMiddleware == null) ? [] : removeMiddleware.handlers)); (removeMiddleware == null) ? [] : removeMiddleware.handlers));
// REST compliance // REST compliance

View file

@ -0,0 +1,123 @@
import 'dart:async';
typedef void _InitCallback();
/// A [StreamController] boilerplate that prevents memory leaks.
abstract class SafeCtrl<T> {
factory SafeCtrl() => new _SingleSafeCtrl();
factory SafeCtrl.broadcast() => new _BroadcastSafeCtrl();
Stream<T> get stream;
void add(T event);
void addError(error, [StackTrace stackTrace]);
Future close();
void whenInitialized(void callback());
}
class _SingleSafeCtrl<T> implements SafeCtrl<T> {
StreamController<T> _stream;
bool _hasListener = false, _initialized = false;
_InitCallback _initializer;
_SingleSafeCtrl() {
_stream = new StreamController<T>(onListen: () {
_hasListener = true;
if (!_initialized && _initializer != null) {
_initializer();
_initialized = true;
}
}, onPause: () {
_hasListener = false;
}, onResume: () {
_hasListener = true;
}, onCancel: () {
_hasListener = false;
});
}
@override
Stream<T> get stream => _stream.stream;
@override
void add(T event) {
if (_hasListener) _stream.add(event);
}
@override
void addError(error, [StackTrace stackTrace]) {
if (_hasListener) _stream.addError(error, stackTrace);
}
@override
Future close() {
return _stream.close();
}
@override
void whenInitialized(void callback()) {
if (!_initialized) {
if (!_hasListener)
_initializer = callback;
else {
_initializer();
_initialized = true;
}
}
}
}
class _BroadcastSafeCtrl<T> implements SafeCtrl<T> {
StreamController<T> _stream;
int _listeners = 0;
bool _initialized = false;
_InitCallback _initializer;
_BroadcastSafeCtrl() {
_stream = new StreamController<T>.broadcast(onListen: () {
_listeners++;
if (!_initialized && _initializer != null) {
_initializer();
_initialized = true;
}
}, onCancel: () {
_listeners--;
});
}
@override
Stream<T> get stream => _stream.stream;
@override
void add(T event) {
if (_listeners > 0) _stream.add(event);
}
@override
void addError(error, [StackTrace stackTrace]) {
if (_listeners > 0) _stream.addError(error, stackTrace);
}
@override
Future close() {
return _stream.close();
}
@override
void whenInitialized(void callback()) {
if (!_initialized) {
if (_listeners <= 0)
_initializer = callback;
else {
_initializer();
_initialized = true;
}
}
}
}

View file

@ -0,0 +1,27 @@
/// A basic server that prints "Hello, world!"
library performance.hello;
import 'dart:io';
import 'dart:isolate';
import 'package:angel_framework/angel_framework.dart';
main() {
for (int i = 0; i < Platform.numberOfProcessors - 1; i++)
Isolate.spawn(start, i + 1);
start(0);
}
void start(int id) {
var app = new Angel.custom(startShared)
..lazyParseBodies = true
..get('/', (req, res) => res.write('Hello, world!'))
..optimizeForProduction(force: true)
..fatalErrorStream.listen((e) {
print('Oops: ${e.error}');
print(e.stack);
});
app.startServer(InternetAddress.LOOPBACK_IP_V4, 3000).then((server) {
print(
'Instance #$id listening at http://${server.address.address}:${server.port}');
});
}

View file

@ -7,14 +7,15 @@ environment:
sdk: ">=1.19.0" sdk: ">=1.19.0"
dependencies: dependencies:
angel_model: ^1.0.0 angel_model: ^1.0.0
angel_route: ^1.0.0-dev angel_route: ">=1.0.5 <2.0.0"
body_parser: ^1.0.0-dev body_parser: ^1.0.0-dev
container: ^0.1.2 container: ^0.1.2
flatten: ^1.0.0 flatten: ^1.0.0
json_god: ^2.0.0-beta json_god: ^2.0.0-beta
merge_map: ^1.0.0 merge_map: ^1.0.0
random_string: ^0.0.1 meta: ^1.0.0
mime: ^0.9.3 mime: ^0.9.3
random_string: ^0.0.1
dev_dependencies: dev_dependencies:
mock_request: ^1.0.0 mock_request: ^1.0.0
http: ^0.11.3 http: ^0.11.3

View file

@ -2,58 +2,35 @@ import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'package:angel_framework/angel_framework.dart'; import 'package:angel_framework/angel_framework.dart';
import 'package:matcher/matcher.dart'; import 'package:matcher/matcher.dart';
import 'package:meta/meta.dart';
import 'package:test/test.dart'; import 'package:test/test.dart';
main() { main() {
test('named constructors', () { test('named constructors', () {
expect(new AngelHttpException.badRequest(), expect(new AngelHttpException.badRequest(),
isException(HttpStatus.BAD_REQUEST, '400 Bad Request')); isException(HttpStatus.BAD_REQUEST, '400 Bad Request'));
expect(new AngelHttpException.BadRequest(),
isException(HttpStatus.BAD_REQUEST, '400 Bad Request'));
expect(new AngelHttpException.notAuthenticated(), expect(new AngelHttpException.notAuthenticated(),
isException(HttpStatus.UNAUTHORIZED, '401 Not Authenticated')); isException(HttpStatus.UNAUTHORIZED, '401 Not Authenticated'));
expect(new AngelHttpException.NotAuthenticated(),
isException(HttpStatus.UNAUTHORIZED, '401 Not Authenticated'));
expect(new AngelHttpException.paymentRequired(), expect(new AngelHttpException.paymentRequired(),
isException(HttpStatus.PAYMENT_REQUIRED, '402 Payment Required')); isException(HttpStatus.PAYMENT_REQUIRED, '402 Payment Required'));
expect(new AngelHttpException.PaymentRequired(),
isException(HttpStatus.PAYMENT_REQUIRED, '402 Payment Required'));
expect(new AngelHttpException.forbidden(), expect(new AngelHttpException.forbidden(),
isException(HttpStatus.FORBIDDEN, '403 Forbidden')); isException(HttpStatus.FORBIDDEN, '403 Forbidden'));
expect(new AngelHttpException.Forbidden(),
isException(HttpStatus.FORBIDDEN, '403 Forbidden'));
expect(new AngelHttpException.notFound(), expect(new AngelHttpException.notFound(),
isException(HttpStatus.NOT_FOUND, '404 Not Found')); isException(HttpStatus.NOT_FOUND, '404 Not Found'));
expect(new AngelHttpException.NotFound(),
isException(HttpStatus.NOT_FOUND, '404 Not Found'));
expect(new AngelHttpException.methodNotAllowed(), expect(new AngelHttpException.methodNotAllowed(),
isException(HttpStatus.METHOD_NOT_ALLOWED, '405 Method Not Allowed')); isException(HttpStatus.METHOD_NOT_ALLOWED, '405 Method Not Allowed'));
expect(new AngelHttpException.MethodNotAllowed(),
isException(HttpStatus.METHOD_NOT_ALLOWED, '405 Method Not Allowed'));
expect(new AngelHttpException.notAcceptable(), expect(new AngelHttpException.notAcceptable(),
isException(HttpStatus.NOT_ACCEPTABLE, '406 Not Acceptable')); isException(HttpStatus.NOT_ACCEPTABLE, '406 Not Acceptable'));
expect(new AngelHttpException.NotAcceptable(),
isException(HttpStatus.NOT_ACCEPTABLE, '406 Not Acceptable'));
expect(new AngelHttpException.methodTimeout(), expect(new AngelHttpException.methodTimeout(),
isException(HttpStatus.REQUEST_TIMEOUT, '408 Timeout')); isException(HttpStatus.REQUEST_TIMEOUT, '408 Timeout'));
expect(new AngelHttpException.MethodTimeout(),
isException(HttpStatus.REQUEST_TIMEOUT, '408 Timeout'));
expect(new AngelHttpException.conflict(), expect(new AngelHttpException.conflict(),
isException(HttpStatus.CONFLICT, '409 Conflict')); isException(HttpStatus.CONFLICT, '409 Conflict'));
expect(new AngelHttpException.Conflict(),
isException(HttpStatus.CONFLICT, '409 Conflict'));
expect(new AngelHttpException.notProcessable(), expect(new AngelHttpException.notProcessable(),
isException(422, '422 Not Processable')); isException(422, '422 Not Processable'));
expect(new AngelHttpException.NotProcessable(),
isException(422, '422 Not Processable'));
expect(new AngelHttpException.notImplemented(), expect(new AngelHttpException.notImplemented(),
isException(HttpStatus.NOT_IMPLEMENTED, '501 Not Implemented')); isException(HttpStatus.NOT_IMPLEMENTED, '501 Not Implemented'));
expect(new AngelHttpException.NotImplemented(),
isException(HttpStatus.NOT_IMPLEMENTED, '501 Not Implemented'));
expect(new AngelHttpException.unavailable(), expect(new AngelHttpException.unavailable(),
isException(HttpStatus.SERVICE_UNAVAILABLE, '503 Unavailable')); isException(HttpStatus.SERVICE_UNAVAILABLE, '503 Unavailable'));
expect(new AngelHttpException.Unavailable(),
isException(HttpStatus.SERVICE_UNAVAILABLE, '503 Unavailable'));
}); });
test('fromMap', () { test('fromMap', () {
@ -91,7 +68,7 @@ class _IsException extends Matcher {
description.add('has status code $statusCode and message "$message"'); description.add('has status code $statusCode and message "$message"');
@override @override
bool matches(AngelHttpException item, Map matchState) { bool matches(@checked AngelHttpException item, Map matchState) {
return item.statusCode == statusCode && item.message == message; return item.statusCode == statusCode && item.message == message;
} }
} }

View file

@ -13,9 +13,7 @@ testMiddlewareMetadata(RequestContext req, ResponseContext res) async {
class QueryService extends Service { class QueryService extends Service {
@override @override
@Middleware(const ['interceptor']) @Middleware(const ['interceptor'])
read(id, [Map params]) { read(id, [Map params]) async => params;
return params;
}
} }
main() { main() {

View file

@ -1,3 +1,4 @@
import 'dart:convert';
import 'package:angel_framework/src/defs.dart'; import 'package:angel_framework/src/defs.dart';
import 'package:angel_framework/angel_framework.dart'; import 'package:angel_framework/angel_framework.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
@ -44,13 +45,8 @@ main() {
var response = await client.get("$url/todos/"); var response = await client.get("$url/todos/");
print(response.body); print(response.body);
expect(response.body, equals('[]')); expect(response.body, equals('[]'));
for (int i = 0; i < 3; i++) {
String postData = god.serialize({'text': 'Hello, world!'});
await client.post("$url/todos", headers: headers, body: postData);
}
response = await client.get("$url/todos");
print(response.body); print(response.body);
expect(god.deserialize(response.body).length, equals(3)); expect(JSON.decode(response.body).length, 0);
}); });
test('can create data', () async { test('can create data', () async {
@ -100,15 +96,12 @@ main() {
test('can delete data', () async { test('can delete data', () async {
String postData = god.serialize({'text': 'Hello, world!'}); String postData = god.serialize({'text': 'Hello, world!'});
await client.post("$url/todos", headers: headers, body: postData); var created = await client.post("$url/todos", headers: headers, body: postData).then((r) => JSON.decode(r.body));
var response = await client.delete("$url/todos/0"); var response = await client.delete("$url/todos/${created['id']}");
expect(response.statusCode, 200); expect(response.statusCode, 200);
var json = god.deserialize(response.body); var json = god.deserialize(response.body);
print(json); print(json);
expect(json['text'], equals('Hello, world!')); expect(json['text'], equals('Hello, world!'));
response = await client.get("$url/todos");
print(response.body);
expect(god.deserialize(response.body).length, equals(0));
}); });
}); });
} }