Working on 1.0.8, including performance tuning
This commit is contained in:
parent
3d12297316
commit
f6695080b8
22 changed files with 995 additions and 619 deletions
|
@ -19,7 +19,7 @@
|
|||
<entry key="angel_route">
|
||||
<value>
|
||||
<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>
|
||||
</value>
|
||||
</entry>
|
||||
|
@ -399,7 +399,7 @@
|
|||
<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/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/async-1.13.3/lib" />
|
||||
<root url="file://$USER_HOME$/AppData/Roaming/Pub/Cache/hosted/pub.dartlang.org/barback-0.15.2+11/lib" />
|
||||
|
|
8
.idea/runConfigurations/performance__hello.xml
Normal file
8
.idea/runConfigurations/performance__hello.xml
Normal 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
17
CHANGELOG.md
17
CHANGELOG.md
|
@ -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
|
||||
Changed `ResponseContext.serialize`. The `contentType` is now set *before* serialization.
|
||||
|
||||
|
|
2
analysis_options.yaml
Normal file
2
analysis_options.yaml
Normal file
|
@ -0,0 +1,2 @@
|
|||
analyzer:
|
||||
strong-mode: true
|
|
@ -143,8 +143,6 @@ HookedServiceEventListener remove(key, [remover(key, obj)]) {
|
|||
return obj.where((k) => !key);
|
||||
else if (obj is Map)
|
||||
return obj..remove(key);
|
||||
else if (obj is Extensible)
|
||||
return obj..properties.remove(key);
|
||||
else {
|
||||
try {
|
||||
reflect(obj).setField(new Symbol(key), null);
|
||||
|
@ -231,8 +229,6 @@ HookedServiceEventListener addCreatedAt(
|
|||
return assign(obj, now);
|
||||
else if (obj is Map)
|
||||
obj[name] = now;
|
||||
else if (obj is Extensible)
|
||||
obj..properties[name] = now;
|
||||
else {
|
||||
try {
|
||||
reflect(obj).setField(new Symbol(name), now);
|
||||
|
@ -282,8 +278,6 @@ HookedServiceEventListener addUpdatedAt(
|
|||
return assign(obj, now);
|
||||
else if (obj is Map)
|
||||
obj[name] = now;
|
||||
else if (obj is Extensible)
|
||||
obj..properties[name] = now;
|
||||
else {
|
||||
try {
|
||||
reflect(obj).setField(new Symbol(name), now);
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
library angel_framework.extensible;
|
||||
|
||||
export 'package:angel_route/src/extensible.dart';
|
6
lib/src/fast_name_from_symbol.dart
Normal file
6
lib/src/fast_name_from_symbol.dart
Normal 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);
|
||||
}
|
|
@ -2,17 +2,19 @@ library angel_framework.http.angel_base;
|
|||
|
||||
import 'dart:async';
|
||||
import 'package:container/container.dart';
|
||||
import '../fast_name_from_symbol.dart';
|
||||
import 'routable.dart';
|
||||
|
||||
/// A function that asynchronously generates a view from the given path and data.
|
||||
typedef Future<String> ViewGenerator(String path, [Map data]);
|
||||
|
||||
/// Base class for Angel servers. Do not bother extending this.
|
||||
@proxy
|
||||
class AngelBase extends Routable {
|
||||
AngelBase({bool debug: false}):super(debug: debug);
|
||||
|
||||
Container _container = new Container();
|
||||
|
||||
final Map properties = {};
|
||||
|
||||
/// When set to true, the request body will not be parsed
|
||||
/// automatically. You can call `req.parse()` manually,
|
||||
/// or use `lazyBody()`.
|
||||
|
@ -29,4 +31,22 @@ class AngelBase extends Routable {
|
|||
///
|
||||
/// Called by [ResponseContext]@`render`.
|
||||
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);
|
||||
}
|
||||
}
|
|
@ -3,6 +3,7 @@ library angel_framework.http.controller;
|
|||
import 'dart:async';
|
||||
import 'dart:mirrors';
|
||||
import 'package:angel_route/angel_route.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
import 'metadata.dart';
|
||||
import 'request_context.dart';
|
||||
import 'response_context.dart';
|
||||
|
@ -18,13 +19,20 @@ import 'server.dart' show Angel, preInject;
|
|||
/// and memory use.
|
||||
class InjectionRequest {
|
||||
/// 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.
|
||||
final List required = [];
|
||||
final List required;
|
||||
|
||||
/// 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.
|
||||
|
@ -47,6 +55,7 @@ class Controller {
|
|||
|
||||
Controller({this.debug: false, this.injectSingleton: true});
|
||||
|
||||
@mustCallSuper
|
||||
Future call(Angel app) async {
|
||||
_app = app;
|
||||
|
||||
|
@ -143,6 +152,7 @@ RequestHandler createDynamicHandler(handler,
|
|||
injection.optional.addAll(optional ?? []);
|
||||
return handleContained(handler, injection);
|
||||
}
|
||||
|
||||
/// Handles a request with a DI-enabled handler.
|
||||
RequestHandler handleContained(handler, InjectionRequest injection) {
|
||||
return (RequestContext req, ResponseContext res) async {
|
||||
|
|
|
@ -108,27 +108,27 @@ class HookedService extends Service {
|
|||
|
||||
applyListeners(inner.index, beforeIndexed);
|
||||
applyListeners(inner.read, beforeRead);
|
||||
applyListeners(inner.created, beforeCreated);
|
||||
applyListeners(inner.create, beforeCreated);
|
||||
applyListeners(inner.modify, beforeModified);
|
||||
applyListeners(inner.updated, beforeUpdated);
|
||||
applyListeners(inner.removed, beforeRemoved);
|
||||
applyListeners(inner.update, beforeUpdated);
|
||||
applyListeners(inner.remove, beforeRemoved);
|
||||
applyListeners(inner.index, afterIndexed, true);
|
||||
applyListeners(inner.read, afterRead, true);
|
||||
applyListeners(inner.created, afterCreated, true);
|
||||
applyListeners(inner.create, afterCreated, true);
|
||||
applyListeners(inner.modify, afterModified, true);
|
||||
applyListeners(inner.updated, afterUpdated, true);
|
||||
applyListeners(inner.removed, afterRemoved, true);
|
||||
applyListeners(inner.update, afterUpdated, true);
|
||||
applyListeners(inner.remove, afterRemoved, true);
|
||||
}
|
||||
|
||||
/// Adds routes to this instance.
|
||||
@override
|
||||
void addRoutes() {
|
||||
// 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
|
||||
Middleware before = getAnnotation(inner, Middleware);
|
||||
final handlers = [
|
||||
List handlers = [
|
||||
(RequestContext req, ResponseContext res) async {
|
||||
req.serviceParams
|
||||
..['__requestctx'] = req
|
||||
|
@ -270,17 +270,17 @@ class HookedService extends Service {
|
|||
Iterable<String> eventNames, HookedServiceEventListener listener) {
|
||||
eventNames.map((name) {
|
||||
switch (name) {
|
||||
case HookedServiceEvent.INDEXED:
|
||||
case HookedServiceEvent.indexed:
|
||||
return beforeIndexed;
|
||||
case HookedServiceEvent.READ:
|
||||
case HookedServiceEvent.read:
|
||||
return beforeRead;
|
||||
case HookedServiceEvent.CREATED:
|
||||
case HookedServiceEvent.created:
|
||||
return beforeCreated;
|
||||
case HookedServiceEvent.MODIFIED:
|
||||
case HookedServiceEvent.modified:
|
||||
return beforeModified;
|
||||
case HookedServiceEvent.UPDATED:
|
||||
case HookedServiceEvent.updated:
|
||||
return beforeUpdated;
|
||||
case HookedServiceEvent.REMOVED:
|
||||
case HookedServiceEvent.removed:
|
||||
return beforeRemoved;
|
||||
default:
|
||||
throw new ArgumentError('Invalid service method: ${name}');
|
||||
|
@ -293,17 +293,17 @@ class HookedService extends Service {
|
|||
void after(Iterable<String> eventNames, HookedServiceEventListener listener) {
|
||||
eventNames.map((name) {
|
||||
switch (name) {
|
||||
case HookedServiceEvent.INDEXED:
|
||||
case HookedServiceEvent.indexed:
|
||||
return afterIndexed;
|
||||
case HookedServiceEvent.READ:
|
||||
case HookedServiceEvent.read:
|
||||
return afterRead;
|
||||
case HookedServiceEvent.CREATED:
|
||||
case HookedServiceEvent.created:
|
||||
return afterCreated;
|
||||
case HookedServiceEvent.MODIFIED:
|
||||
case HookedServiceEvent.modified:
|
||||
return afterModified;
|
||||
case HookedServiceEvent.UPDATED:
|
||||
case HookedServiceEvent.updated:
|
||||
return afterUpdated;
|
||||
case HookedServiceEvent.REMOVED:
|
||||
case HookedServiceEvent.removed:
|
||||
return afterRemoved;
|
||||
default:
|
||||
throw new ArgumentError('Invalid service method: ${name}');
|
||||
|
@ -340,7 +340,7 @@ class HookedService extends Service {
|
|||
Stream<HookedServiceEvent> beforeAllStream() {
|
||||
var ctrl = new StreamController<HookedServiceEvent>();
|
||||
_ctrl.add(ctrl);
|
||||
before(HookedServiceEvent.ALL, ctrl.add);
|
||||
before(HookedServiceEvent.all, ctrl.add);
|
||||
return ctrl.stream;
|
||||
}
|
||||
|
||||
|
@ -352,7 +352,7 @@ class HookedService extends Service {
|
|||
Stream<HookedServiceEvent> afterAllStream() {
|
||||
var ctrl = new StreamController<HookedServiceEvent>();
|
||||
_ctrl.add(ctrl);
|
||||
before(HookedServiceEvent.ALL, ctrl.add);
|
||||
before(HookedServiceEvent.all, ctrl.add);
|
||||
return ctrl.stream;
|
||||
}
|
||||
|
||||
|
@ -392,12 +392,12 @@ class HookedService extends Service {
|
|||
var params = _stripReq(_params);
|
||||
HookedServiceEvent before = await beforeIndexed._emit(
|
||||
new HookedServiceEvent(false, _getRequest(_params),
|
||||
_getResponse(_params), inner, HookedServiceEvent.INDEXED,
|
||||
_getResponse(_params), inner, HookedServiceEvent.indexed,
|
||||
params: params));
|
||||
if (before._canceled) {
|
||||
HookedServiceEvent after = await beforeIndexed._emit(
|
||||
new HookedServiceEvent(true, _getRequest(_params),
|
||||
_getResponse(_params), inner, HookedServiceEvent.INDEXED,
|
||||
_getResponse(_params), inner, HookedServiceEvent.indexed,
|
||||
params: params, result: before.result));
|
||||
return after.result;
|
||||
}
|
||||
|
@ -408,7 +408,7 @@ class HookedService extends Service {
|
|||
_getRequest(_params),
|
||||
_getResponse(_params),
|
||||
inner,
|
||||
HookedServiceEvent.INDEXED,
|
||||
HookedServiceEvent.indexed,
|
||||
params: params,
|
||||
result: result));
|
||||
return after.result;
|
||||
|
@ -422,7 +422,7 @@ class HookedService extends Service {
|
|||
_getRequest(_params),
|
||||
_getResponse(_params),
|
||||
inner,
|
||||
HookedServiceEvent.READ,
|
||||
HookedServiceEvent.indexed,
|
||||
id: id,
|
||||
params: params));
|
||||
|
||||
|
@ -432,7 +432,7 @@ class HookedService extends Service {
|
|||
_getRequest(_params),
|
||||
_getResponse(_params),
|
||||
inner,
|
||||
HookedServiceEvent.READ,
|
||||
HookedServiceEvent.read,
|
||||
id: id,
|
||||
params: params,
|
||||
result: before.result));
|
||||
|
@ -445,7 +445,7 @@ class HookedService extends Service {
|
|||
_getRequest(_params),
|
||||
_getResponse(_params),
|
||||
inner,
|
||||
HookedServiceEvent.READ,
|
||||
HookedServiceEvent.read,
|
||||
id: id,
|
||||
params: params,
|
||||
result: result));
|
||||
|
@ -457,13 +457,13 @@ class HookedService extends Service {
|
|||
var params = _stripReq(_params);
|
||||
HookedServiceEvent before = await beforeCreated._emit(
|
||||
new HookedServiceEvent(false, _getRequest(_params),
|
||||
_getResponse(_params), inner, HookedServiceEvent.CREATED,
|
||||
_getResponse(_params), inner, HookedServiceEvent.created,
|
||||
data: data, params: params));
|
||||
|
||||
if (before._canceled) {
|
||||
HookedServiceEvent after = await afterCreated._emit(
|
||||
new HookedServiceEvent(true, _getRequest(_params),
|
||||
_getResponse(_params), inner, HookedServiceEvent.CREATED,
|
||||
_getResponse(_params), inner, HookedServiceEvent.created,
|
||||
data: data, params: params, result: before.result));
|
||||
return after.result;
|
||||
}
|
||||
|
@ -474,7 +474,7 @@ class HookedService extends Service {
|
|||
_getRequest(_params),
|
||||
_getResponse(_params),
|
||||
inner,
|
||||
HookedServiceEvent.CREATED,
|
||||
HookedServiceEvent.created,
|
||||
data: data,
|
||||
params: params,
|
||||
result: result));
|
||||
|
@ -486,13 +486,13 @@ class HookedService extends Service {
|
|||
var params = _stripReq(_params);
|
||||
HookedServiceEvent before = await beforeModified._emit(
|
||||
new HookedServiceEvent(false, _getRequest(_params),
|
||||
_getResponse(_params), inner, HookedServiceEvent.MODIFIED,
|
||||
_getResponse(_params), inner, HookedServiceEvent.modified,
|
||||
id: id, data: data, params: params));
|
||||
|
||||
if (before._canceled) {
|
||||
HookedServiceEvent after = await afterModified._emit(
|
||||
new HookedServiceEvent(true, _getRequest(_params),
|
||||
_getResponse(_params), inner, HookedServiceEvent.MODIFIED,
|
||||
_getResponse(_params), inner, HookedServiceEvent.modified,
|
||||
id: id, data: data, params: params, result: before.result));
|
||||
return after.result;
|
||||
}
|
||||
|
@ -503,7 +503,7 @@ class HookedService extends Service {
|
|||
_getRequest(_params),
|
||||
_getResponse(_params),
|
||||
inner,
|
||||
HookedServiceEvent.MODIFIED,
|
||||
HookedServiceEvent.modified,
|
||||
id: id,
|
||||
data: data,
|
||||
params: params,
|
||||
|
@ -516,13 +516,13 @@ class HookedService extends Service {
|
|||
var params = _stripReq(_params);
|
||||
HookedServiceEvent before = await beforeUpdated._emit(
|
||||
new HookedServiceEvent(false, _getRequest(_params),
|
||||
_getResponse(_params), inner, HookedServiceEvent.UPDATED,
|
||||
_getResponse(_params), inner, HookedServiceEvent.updated,
|
||||
id: id, data: data, params: params));
|
||||
|
||||
if (before._canceled) {
|
||||
HookedServiceEvent after = await afterUpdated._emit(
|
||||
new HookedServiceEvent(true, _getRequest(_params),
|
||||
_getResponse(_params), inner, HookedServiceEvent.UPDATED,
|
||||
_getResponse(_params), inner, HookedServiceEvent.updated,
|
||||
id: id, data: data, params: params, result: before.result));
|
||||
return after.result;
|
||||
}
|
||||
|
@ -533,7 +533,7 @@ class HookedService extends Service {
|
|||
_getRequest(_params),
|
||||
_getResponse(_params),
|
||||
inner,
|
||||
HookedServiceEvent.UPDATED,
|
||||
HookedServiceEvent.updated,
|
||||
id: id,
|
||||
data: data,
|
||||
params: params,
|
||||
|
@ -546,13 +546,13 @@ class HookedService extends Service {
|
|||
var params = _stripReq(_params);
|
||||
HookedServiceEvent before = await beforeRemoved._emit(
|
||||
new HookedServiceEvent(false, _getRequest(_params),
|
||||
_getResponse(_params), inner, HookedServiceEvent.REMOVED,
|
||||
_getResponse(_params), inner, HookedServiceEvent.removed,
|
||||
id: id, params: params));
|
||||
|
||||
if (before._canceled) {
|
||||
HookedServiceEvent after = await afterRemoved._emit(
|
||||
new HookedServiceEvent(true, _getRequest(_params),
|
||||
_getResponse(_params), inner, HookedServiceEvent.REMOVED,
|
||||
_getResponse(_params), inner, HookedServiceEvent.removed,
|
||||
id: id, params: params, result: before.result));
|
||||
return after.result;
|
||||
}
|
||||
|
@ -563,7 +563,7 @@ class HookedService extends Service {
|
|||
_getRequest(_params),
|
||||
_getResponse(_params),
|
||||
inner,
|
||||
HookedServiceEvent.REMOVED,
|
||||
HookedServiceEvent.removed,
|
||||
id: id,
|
||||
params: params,
|
||||
result: result));
|
||||
|
@ -577,22 +577,22 @@ class HookedService extends Service {
|
|||
HookedServiceEventDispatcher dispatcher;
|
||||
|
||||
switch (eventName) {
|
||||
case HookedServiceEvent.INDEXED:
|
||||
case HookedServiceEvent.indexed:
|
||||
dispatcher = afterIndexed;
|
||||
break;
|
||||
case HookedServiceEvent.READ:
|
||||
case HookedServiceEvent.read:
|
||||
dispatcher = afterRead;
|
||||
break;
|
||||
case HookedServiceEvent.CREATED:
|
||||
case HookedServiceEvent.created:
|
||||
dispatcher = afterCreated;
|
||||
break;
|
||||
case HookedServiceEvent.MODIFIED:
|
||||
case HookedServiceEvent.modified:
|
||||
dispatcher = afterModified;
|
||||
break;
|
||||
case HookedServiceEvent.UPDATED:
|
||||
case HookedServiceEvent.updated:
|
||||
dispatcher = afterUpdated;
|
||||
break;
|
||||
case HookedServiceEvent.REMOVED:
|
||||
case HookedServiceEvent.removed:
|
||||
dispatcher = afterRemoved;
|
||||
break;
|
||||
default:
|
||||
|
@ -614,20 +614,44 @@ class HookedService extends Service {
|
|||
|
||||
/// Fired when a hooked service is invoked.
|
||||
class HookedServiceEvent {
|
||||
static const String INDEXED = "indexed";
|
||||
static const String READ = "read";
|
||||
static const String CREATED = "created";
|
||||
static const String MODIFIED = "modified";
|
||||
static const String UPDATED = "updated";
|
||||
static const String REMOVED = "removed";
|
||||
static const List<String> ALL = const [
|
||||
INDEXED,
|
||||
READ,
|
||||
CREATED,
|
||||
MODIFIED,
|
||||
UPDATED,
|
||||
REMOVED
|
||||
static const String indexed = 'indexed';
|
||||
static const String read = 'read';
|
||||
static const String created = 'created';
|
||||
static const String modified = 'modified';
|
||||
static const String updated = 'updated';
|
||||
static const String removed = 'removed';
|
||||
|
||||
static const List<String> all = const [
|
||||
indexed, read, 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.
|
||||
void cancel([result]) {
|
||||
|
|
|
@ -2,18 +2,23 @@ library angel_framework.http.request_context;
|
|||
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
import 'package:angel_route/src/extensible.dart';
|
||||
import 'package:body_parser/body_parser.dart';
|
||||
import 'package:charcode/charcode.dart';
|
||||
import '../fast_name_from_symbol.dart';
|
||||
import 'server.dart' show Angel;
|
||||
|
||||
/// A convenience wrapper around an incoming HTTP request.
|
||||
class RequestContext extends Extensible {
|
||||
@proxy
|
||||
class RequestContext {
|
||||
String _acceptHeaderCache;
|
||||
bool _acceptsAllCache;
|
||||
BodyParseResult _body;
|
||||
ContentType _contentType;
|
||||
HttpRequest _io;
|
||||
String _override, _path;
|
||||
Map _provisionalQuery;
|
||||
|
||||
final Map properties = {};
|
||||
|
||||
/// Additional params to be passed to services.
|
||||
final Map serviceParams = {};
|
||||
|
@ -106,7 +111,7 @@ class RequestContext extends Extensible {
|
|||
/// **If you are writing a plug-in, consider using [lazyQuery] instead.**
|
||||
Map get query {
|
||||
if (_body == null)
|
||||
return uri.queryParameters;
|
||||
return _provisionalQuery ??= new Map.from(uri.queryParameters);
|
||||
else
|
||||
return _body.query;
|
||||
}
|
||||
|
@ -145,16 +150,36 @@ class RequestContext extends Extensible {
|
|||
ctx.app = app;
|
||||
ctx._contentType = request.headers.contentType;
|
||||
ctx._override = override;
|
||||
ctx._path = request.uri
|
||||
.toString()
|
||||
.replaceAll("?" + request.uri.query, "")
|
||||
.replaceAll(new RegExp(r'/+$'), '');
|
||||
|
||||
// Faster way to get path
|
||||
List<int> _path = [];
|
||||
|
||||
// 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;
|
||||
|
||||
if (app.lazyParseBodies != true)
|
||||
if (app.lazyParseBodies != true) {
|
||||
ctx._body = (await parseBody(request,
|
||||
storeOriginalBuffer: app.storeOriginalBuffer == true)) ??
|
||||
storeOriginalBuffer: app.storeOriginalBuffer == true)) ??
|
||||
{};
|
||||
}
|
||||
|
||||
return ctx;
|
||||
}
|
||||
|
@ -246,7 +271,26 @@ class RequestContext extends Extensible {
|
|||
if (_body != null)
|
||||
return _body;
|
||||
else
|
||||
_provisionalQuery = null;
|
||||
return _body = await parseBody(io,
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,10 +3,10 @@ library angel_framework.http.response_context;
|
|||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'dart:typed_data';
|
||||
import 'package:angel_route/angel_route.dart';
|
||||
import 'package:json_god/json_god.dart' as god;
|
||||
import 'package:mime/mime.dart';
|
||||
import '../extensible.dart';
|
||||
import 'server.dart' show Angel;
|
||||
import 'controller.dart';
|
||||
|
||||
|
@ -19,9 +19,10 @@ final RegExp _straySlashes = new RegExp(r'(^/+)|(/+$)');
|
|||
typedef String ResponseSerializer(data);
|
||||
|
||||
/// A convenience wrapper around an outgoing HTTP request.
|
||||
class ResponseContext extends Extensible implements StringSink {
|
||||
class ResponseContext implements StringSink {
|
||||
final _LockableBytesBuilder _buffer = new _LockableBytesBuilder();
|
||||
final Map<String, String> _headers = {HttpHeaders.SERVER: 'angel'};
|
||||
final Map properties = {};
|
||||
bool _isOpen = true, _isClosed = false;
|
||||
int _statusCode = 200;
|
||||
|
||||
|
@ -366,7 +367,7 @@ abstract class _LockableBytesBuilder extends BytesBuilder {
|
|||
|
||||
class _LockableBytesBuilderImpl implements _LockableBytesBuilder {
|
||||
bool _closed = false;
|
||||
final List<int> _data = [];
|
||||
Uint8List _data = new Uint8List(0);
|
||||
|
||||
StateError _deny() =>
|
||||
new StateError('Cannot modified a closed response\'s buffer.');
|
||||
|
@ -385,8 +386,19 @@ class _LockableBytesBuilderImpl implements _LockableBytesBuilder {
|
|||
void add(List<int> bytes) {
|
||||
if (_closed)
|
||||
throw _deny();
|
||||
else {
|
||||
_data.addAll(bytes);
|
||||
else if (bytes.isNotEmpty) {
|
||||
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)
|
||||
throw _deny();
|
||||
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)
|
||||
throw _deny();
|
||||
else {
|
||||
_data.clear();
|
||||
for (int i = 0; i < _data.length; i++) _data[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -422,7 +442,7 @@ class _LockableBytesBuilderImpl implements _LockableBytesBuilder {
|
|||
if (_closed)
|
||||
return toBytes();
|
||||
else {
|
||||
var r = new List<int>.from(_data);
|
||||
var r = new Uint8List.fromList(_data);
|
||||
clear();
|
||||
return r;
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ library angel_framework.http.routable;
|
|||
|
||||
import 'dart:async';
|
||||
import 'package:angel_route/angel_route.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
import '../util.dart';
|
||||
import 'angel_base.dart';
|
||||
import 'controller.dart';
|
||||
|
@ -36,6 +37,7 @@ RequestMiddleware waterfall(List handlers) {
|
|||
class Routable extends Router {
|
||||
final Map<Pattern, Controller> _controllers = {};
|
||||
final Map<Pattern, Service> _services = {};
|
||||
final Map properties = {};
|
||||
|
||||
Routable({bool debug: false}) : super(debug: debug);
|
||||
|
||||
|
@ -59,7 +61,7 @@ class Routable extends Router {
|
|||
|
||||
/// Assigns a middleware to a name for convenience.
|
||||
@override
|
||||
registerMiddleware(String name, RequestMiddleware middleware) =>
|
||||
registerMiddleware(String name, @checked RequestHandler middleware) =>
|
||||
super.registerMiddleware(name, middleware);
|
||||
|
||||
/// Retrieves the service assigned to the given path.
|
||||
|
@ -129,17 +131,19 @@ class Routable extends Router {
|
|||
}
|
||||
|
||||
// Also copy properties...
|
||||
Map copiedProperties = new Map.from(router.properties);
|
||||
for (String propertyName in copiedProperties.keys) {
|
||||
properties.putIfAbsent("$middlewarePrefix$propertyName",
|
||||
() => copiedMiddleware[propertyName]);
|
||||
if (router is Routable) {
|
||||
Map copiedProperties = new Map.from(router.properties);
|
||||
for (String propertyName in copiedProperties.keys) {
|
||||
properties.putIfAbsent("$middlewarePrefix$propertyName",
|
||||
() => copiedMiddleware[propertyName]);
|
||||
}
|
||||
}
|
||||
|
||||
// _router.dumpTree(header: 'Mounting on "$path":');
|
||||
// root.child(path, debug: debug, handlers: handlers).addChild(router.root);
|
||||
mount(path, _router);
|
||||
|
||||
if (router is Routable) {
|
||||
if (_router is Routable) {
|
||||
// Copy services, too. :)
|
||||
for (Pattern servicePath in _router._services.keys) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,10 +3,13 @@ library angel_framework.http.server;
|
|||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
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';
|
||||
import 'package:flatten/flatten.dart';
|
||||
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_http_exception.dart';
|
||||
import 'controller.dart';
|
||||
|
@ -31,18 +34,18 @@ typedef Future AngelConfigurer(Angel app);
|
|||
|
||||
/// A powerful real-time/REST/MVC server class.
|
||||
class Angel extends AngelBase {
|
||||
StreamController<HttpRequest> _afterProcessed =
|
||||
new StreamController<HttpRequest>.broadcast();
|
||||
StreamController<HttpRequest> _beforeProcessed =
|
||||
new StreamController<HttpRequest>.broadcast();
|
||||
StreamController<AngelFatalError> _fatalErrorStream =
|
||||
new StreamController<AngelFatalError>.broadcast();
|
||||
StreamController<Controller> _onController =
|
||||
new StreamController<Controller>.broadcast();
|
||||
SafeCtrl<HttpRequest> _afterProcessed = new SafeCtrl<HttpRequest>.broadcast();
|
||||
SafeCtrl<HttpRequest> _beforeProcessed =
|
||||
new SafeCtrl<HttpRequest>.broadcast();
|
||||
SafeCtrl<AngelFatalError> _fatalErrorStream =
|
||||
new SafeCtrl<AngelFatalError>.broadcast();
|
||||
SafeCtrl<Controller> _onController = new SafeCtrl<Controller>.broadcast();
|
||||
|
||||
final List<Angel> _children = [];
|
||||
Router _flattened;
|
||||
bool _isProduction;
|
||||
Angel _parent;
|
||||
final Map<String, List> _handlerCache = {};
|
||||
ServerGenerator _serverGenerator = HttpServer.bind;
|
||||
|
||||
/// A global Map of manual injections. You usually will not want to touch this.
|
||||
|
@ -152,7 +155,7 @@ class Angel extends AngelBase {
|
|||
}
|
||||
|
||||
@override
|
||||
Route addRoute(String method, String path, Object handler,
|
||||
Route addRoute(String method, Pattern path, Object handler,
|
||||
{List middleware: const []}) {
|
||||
if (_flattened != null) {
|
||||
print(
|
||||
|
@ -308,9 +311,9 @@ class Angel extends AngelBase {
|
|||
});
|
||||
}
|
||||
|
||||
Future<ResponseContext> createResponseContext(HttpResponse response) async =>
|
||||
new ResponseContext(response, this)
|
||||
..serializer = (_serializer ?? god.serialize);
|
||||
Future<ResponseContext> createResponseContext(HttpResponse response) =>
|
||||
new Future<ResponseContext>.value(new ResponseContext(response, this)
|
||||
..serializer = (_serializer ?? god.serialize));
|
||||
|
||||
/// Attempts to find a middleware by the given name within this application.
|
||||
findMiddleware(key) {
|
||||
|
@ -361,42 +364,50 @@ class Angel extends AngelBase {
|
|||
try {
|
||||
var req = await createRequestContext(request);
|
||||
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 = '/';
|
||||
|
||||
Router r =
|
||||
isProduction ? (_flattened ?? (_flattened = flatten(this))) : this;
|
||||
var resolved =
|
||||
r.resolveAll(requestedUrl, requestedUrl, method: req.method);
|
||||
var pipeline = _handlerCache.putIfAbsent(requestedUrl, () {
|
||||
Router r =
|
||||
isProduction ? (_flattened ?? (_flattened = flatten(this))) : this;
|
||||
var resolved =
|
||||
r.resolveAll(requestedUrl, requestedUrl, method: req.method);
|
||||
|
||||
for (var result in resolved) req.params.addAll(result.allParams);
|
||||
for (var result in resolved) req.params.addAll(result.allParams);
|
||||
|
||||
if (resolved.isNotEmpty) {
|
||||
var route = resolved.first.route;
|
||||
req.inject(Match, route.match(requestedUrl));
|
||||
}
|
||||
if (resolved.isNotEmpty) {
|
||||
var route = resolved.first.route;
|
||||
req.inject(Match, route.match(requestedUrl));
|
||||
}
|
||||
|
||||
var m = new MiddlewarePipeline(resolved);
|
||||
req.inject(MiddlewarePipeline, m);
|
||||
var m = new MiddlewarePipeline(resolved);
|
||||
req.inject(MiddlewarePipeline, m);
|
||||
|
||||
var pipeline = []..addAll(before)..addAll(m.handlers)..addAll(after);
|
||||
|
||||
// _printDebug('Handler sequence on $requestedUrl: $pipeline');
|
||||
return new List.from(before)..addAll(m.handlers)..addAll(after);
|
||||
});
|
||||
|
||||
for (var handler in pipeline) {
|
||||
try {
|
||||
// _printDebug('Executing handler: $handler');
|
||||
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');
|
||||
}
|
||||
if (!await executeHandler(handler, req, res)) break;
|
||||
} on AngelHttpException catch (e, st) {
|
||||
e.stackTrace ??= st;
|
||||
return await handleAngelHttpException(e, st, req, res, request);
|
||||
|
@ -448,7 +459,8 @@ class Angel extends AngelBase {
|
|||
if (_flattened == null) _flattened = flatten(this);
|
||||
|
||||
_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.
|
||||
Future sendResponse(
|
||||
HttpRequest request, RequestContext req, ResponseContext res,
|
||||
{bool ignoreFinalizers: false}) async {
|
||||
{bool ignoreFinalizers: false}) {
|
||||
_afterProcessed.add(request);
|
||||
|
||||
if (!res.willCloseItself) {
|
||||
if (ignoreFinalizers != true) {
|
||||
for (var finalizer in responseFinalizers) {
|
||||
await finalizer(req, res);
|
||||
}
|
||||
}
|
||||
if (res.willCloseItself) {
|
||||
return new Future.value();
|
||||
} else {
|
||||
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();
|
||||
|
||||
|
@ -505,8 +518,9 @@ class Angel extends AngelBase {
|
|||
request.response
|
||||
..statusCode = res.statusCode
|
||||
..cookies.addAll(res.cookies)
|
||||
..add(res.buffer.takeBytes());
|
||||
await request.response.close();
|
||||
..add(res.buffer.toBytes());
|
||||
|
||||
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
|
||||
/// a [RegExp].
|
||||
@override
|
||||
use(Pattern path, Routable routable,
|
||||
use(Pattern path, @checked Routable routable,
|
||||
{bool hooked: true, String namespace: null}) {
|
||||
var head = path.toString().replaceAll(_straySlashes, '');
|
||||
|
||||
|
@ -581,6 +595,22 @@ class Angel extends AngelBase {
|
|||
var tail = k.toString().replaceAll(_straySlashes, '');
|
||||
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) {
|
||||
|
@ -597,17 +627,18 @@ class Angel extends AngelBase {
|
|||
}
|
||||
|
||||
/// Default constructor. ;)
|
||||
Angel({bool debug: false}) : super(debug: debug == true) {
|
||||
Angel({@deprecated bool debug: false}) : super() {
|
||||
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;
|
||||
factory Angel.custom(ServerGenerator serverGenerator,
|
||||
{@deprecated bool debug: false}) =>
|
||||
new Angel().._serverGenerator = serverGenerator;
|
||||
|
||||
factory Angel.fromSecurityContext(SecurityContext context,
|
||||
{bool debug: false}) {
|
||||
var app = new Angel(debug: debug == true);
|
||||
{@deprecated bool debug: false}) {
|
||||
var app = new Angel();
|
||||
|
||||
app._serverGenerator = (InternetAddress address, int port) async {
|
||||
return await HttpServer.bindSecure(address, port, context);
|
||||
|
|
|
@ -20,14 +20,34 @@ class Providers {
|
|||
|
||||
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";
|
||||
|
||||
/// Use [viaWebSocket] instead.
|
||||
@deprecated
|
||||
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.
|
||||
static final Providers REST = const Providers(VIA_REST);
|
||||
static const Providers rest = const Providers(viaRest);
|
||||
|
||||
/// 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
|
||||
bool operator ==(other) => other is Providers && other.via == via;
|
||||
|
@ -38,13 +58,17 @@ class Providers {
|
|||
/// Heavily inspired by FeathersJS. <3
|
||||
class Service extends Routable {
|
||||
/// 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'$sort',
|
||||
'page',
|
||||
'token'
|
||||
];
|
||||
|
||||
/// Use [specialQueryKeys] instead.
|
||||
@deprecated
|
||||
static const List<String> SPECIAL_QUERY_KEYS = specialQueryKeys;
|
||||
|
||||
/// The [Angel] app powering this service.
|
||||
AngelBase app;
|
||||
|
||||
|
@ -91,7 +115,7 @@ class Service extends Routable {
|
|||
|
||||
/// Generates RESTful routes pointing to this class's methods.
|
||||
void addRoutes() {
|
||||
Map restProvider = {'provider': Providers.REST};
|
||||
Map restProvider = {'provider': Providers.rest};
|
||||
|
||||
// Add global middleware if declared on the instance itself
|
||||
Middleware before = getAnnotation(this, Middleware);
|
||||
|
@ -107,8 +131,9 @@ class Service extends Routable {
|
|||
req.serviceParams
|
||||
]));
|
||||
},
|
||||
middleware: []..addAll(handlers)..addAll(
|
||||
(indexMiddleware == null) ? [] : indexMiddleware.handlers));
|
||||
middleware: []
|
||||
..addAll(handlers)
|
||||
..addAll((indexMiddleware == null) ? [] : indexMiddleware.handlers));
|
||||
|
||||
Middleware createMiddleware = getAnnotation(this.create, Middleware);
|
||||
post('/', (req, ResponseContext res) async {
|
||||
|
@ -122,29 +147,30 @@ class Service extends Routable {
|
|||
res.statusCode = 201;
|
||||
return r;
|
||||
},
|
||||
middleware: []..addAll(handlers)..addAll(
|
||||
(createMiddleware == null) ? [] : createMiddleware.handlers));
|
||||
middleware: []
|
||||
..addAll(handlers)
|
||||
..addAll(
|
||||
(createMiddleware == null) ? [] : createMiddleware.handlers));
|
||||
|
||||
Middleware readMiddleware = getAnnotation(this.read, Middleware);
|
||||
|
||||
get(
|
||||
'/:id',
|
||||
(req, res) async =>
|
||||
await this.read(
|
||||
(req, res) async => await this.read(
|
||||
toId(req.params['id']),
|
||||
mergeMap([
|
||||
{'query': req.query},
|
||||
restProvider,
|
||||
req.serviceParams
|
||||
])),
|
||||
middleware: []..addAll(handlers)..addAll(
|
||||
(readMiddleware == null) ? [] : readMiddleware.handlers));
|
||||
middleware: []
|
||||
..addAll(handlers)
|
||||
..addAll((readMiddleware == null) ? [] : readMiddleware.handlers));
|
||||
|
||||
Middleware modifyMiddleware = getAnnotation(this.modify, Middleware);
|
||||
patch(
|
||||
'/:id',
|
||||
(req, res) async =>
|
||||
await this.modify(
|
||||
(req, res) async => await this.modify(
|
||||
toId(req.params['id']),
|
||||
await req.lazyBody(),
|
||||
mergeMap([
|
||||
|
@ -152,14 +178,15 @@ class Service extends Routable {
|
|||
restProvider,
|
||||
req.serviceParams
|
||||
])),
|
||||
middleware: []..addAll(handlers)..addAll(
|
||||
(modifyMiddleware == null) ? [] : modifyMiddleware.handlers));
|
||||
middleware: []
|
||||
..addAll(handlers)
|
||||
..addAll(
|
||||
(modifyMiddleware == null) ? [] : modifyMiddleware.handlers));
|
||||
|
||||
Middleware updateMiddleware = getAnnotation(this.update, Middleware);
|
||||
post(
|
||||
'/:id',
|
||||
(req, res) async =>
|
||||
await this.update(
|
||||
(req, res) async => await this.update(
|
||||
toId(req.params['id']),
|
||||
await req.lazyBody(),
|
||||
mergeMap([
|
||||
|
@ -167,12 +194,13 @@ class Service extends Routable {
|
|||
restProvider,
|
||||
req.serviceParams
|
||||
])),
|
||||
middleware: []..addAll(handlers)..addAll(
|
||||
(updateMiddleware == null) ? [] : updateMiddleware.handlers));
|
||||
middleware: []
|
||||
..addAll(handlers)
|
||||
..addAll(
|
||||
(updateMiddleware == null) ? [] : updateMiddleware.handlers));
|
||||
put(
|
||||
'/:id',
|
||||
(req, res) async =>
|
||||
await this.update(
|
||||
(req, res) async => await this.update(
|
||||
toId(req.params['id']),
|
||||
await req.lazyBody(),
|
||||
mergeMap([
|
||||
|
@ -180,34 +208,38 @@ class Service extends Routable {
|
|||
restProvider,
|
||||
req.serviceParams
|
||||
])),
|
||||
middleware: []..addAll(handlers)..addAll(
|
||||
(updateMiddleware == null) ? [] : updateMiddleware.handlers));
|
||||
middleware: []
|
||||
..addAll(handlers)
|
||||
..addAll(
|
||||
(updateMiddleware == null) ? [] : updateMiddleware.handlers));
|
||||
|
||||
Middleware removeMiddleware = getAnnotation(this.remove, Middleware);
|
||||
delete(
|
||||
'/',
|
||||
(req, res) async =>
|
||||
await this.remove(
|
||||
(req, res) async => await this.remove(
|
||||
null,
|
||||
mergeMap([
|
||||
{'query': req.query},
|
||||
restProvider,
|
||||
req.serviceParams
|
||||
])),
|
||||
middleware: []..addAll(handlers)..addAll(
|
||||
(removeMiddleware == null) ? [] : removeMiddleware.handlers));
|
||||
middleware: []
|
||||
..addAll(handlers)
|
||||
..addAll(
|
||||
(removeMiddleware == null) ? [] : removeMiddleware.handlers));
|
||||
delete(
|
||||
'/:id',
|
||||
(req, res) async =>
|
||||
await this.remove(
|
||||
(req, res) async => await this.remove(
|
||||
toId(req.params['id']),
|
||||
mergeMap([
|
||||
{'query': req.query},
|
||||
restProvider,
|
||||
req.serviceParams
|
||||
])),
|
||||
middleware: []..addAll(handlers)..addAll(
|
||||
(removeMiddleware == null) ? [] : removeMiddleware.handlers));
|
||||
middleware: []
|
||||
..addAll(handlers)
|
||||
..addAll(
|
||||
(removeMiddleware == null) ? [] : removeMiddleware.handlers));
|
||||
|
||||
// REST compliance
|
||||
put('/', () => throw new AngelHttpException.notFound());
|
||||
|
|
123
lib/src/safe_stream_controller.dart
Normal file
123
lib/src/safe_stream_controller.dart
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
27
performance/hello/main.dart
Normal file
27
performance/hello/main.dart
Normal 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}');
|
||||
});
|
||||
}
|
|
@ -7,14 +7,15 @@ environment:
|
|||
sdk: ">=1.19.0"
|
||||
dependencies:
|
||||
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
|
||||
container: ^0.1.2
|
||||
flatten: ^1.0.0
|
||||
json_god: ^2.0.0-beta
|
||||
merge_map: ^1.0.0
|
||||
random_string: ^0.0.1
|
||||
meta: ^1.0.0
|
||||
mime: ^0.9.3
|
||||
random_string: ^0.0.1
|
||||
dev_dependencies:
|
||||
mock_request: ^1.0.0
|
||||
http: ^0.11.3
|
||||
|
|
|
@ -2,58 +2,35 @@ import 'dart:convert';
|
|||
import 'dart:io';
|
||||
import 'package:angel_framework/angel_framework.dart';
|
||||
import 'package:matcher/matcher.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
main() {
|
||||
test('named constructors', () {
|
||||
expect(new AngelHttpException.badRequest(),
|
||||
isException(HttpStatus.BAD_REQUEST, '400 Bad Request'));
|
||||
expect(new AngelHttpException.BadRequest(),
|
||||
isException(HttpStatus.BAD_REQUEST, '400 Bad Request'));
|
||||
expect(new AngelHttpException.notAuthenticated(),
|
||||
isException(HttpStatus.UNAUTHORIZED, '401 Not Authenticated'));
|
||||
expect(new AngelHttpException.NotAuthenticated(),
|
||||
isException(HttpStatus.UNAUTHORIZED, '401 Not Authenticated'));
|
||||
expect(new AngelHttpException.paymentRequired(),
|
||||
isException(HttpStatus.PAYMENT_REQUIRED, '402 Payment Required'));
|
||||
expect(new AngelHttpException.PaymentRequired(),
|
||||
isException(HttpStatus.PAYMENT_REQUIRED, '402 Payment Required'));
|
||||
expect(new AngelHttpException.forbidden(),
|
||||
isException(HttpStatus.FORBIDDEN, '403 Forbidden'));
|
||||
expect(new AngelHttpException.Forbidden(),
|
||||
isException(HttpStatus.FORBIDDEN, '403 Forbidden'));
|
||||
expect(new AngelHttpException.notFound(),
|
||||
isException(HttpStatus.NOT_FOUND, '404 Not Found'));
|
||||
expect(new AngelHttpException.NotFound(),
|
||||
isException(HttpStatus.NOT_FOUND, '404 Not Found'));
|
||||
expect(new AngelHttpException.methodNotAllowed(),
|
||||
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(),
|
||||
isException(HttpStatus.NOT_ACCEPTABLE, '406 Not Acceptable'));
|
||||
expect(new AngelHttpException.NotAcceptable(),
|
||||
isException(HttpStatus.NOT_ACCEPTABLE, '406 Not Acceptable'));
|
||||
expect(new AngelHttpException.methodTimeout(),
|
||||
isException(HttpStatus.REQUEST_TIMEOUT, '408 Timeout'));
|
||||
expect(new AngelHttpException.MethodTimeout(),
|
||||
isException(HttpStatus.REQUEST_TIMEOUT, '408 Timeout'));
|
||||
expect(new AngelHttpException.conflict(),
|
||||
isException(HttpStatus.CONFLICT, '409 Conflict'));
|
||||
expect(new AngelHttpException.Conflict(),
|
||||
isException(HttpStatus.CONFLICT, '409 Conflict'));
|
||||
expect(new AngelHttpException.notProcessable(),
|
||||
isException(422, '422 Not Processable'));
|
||||
expect(new AngelHttpException.NotProcessable(),
|
||||
isException(422, '422 Not Processable'));
|
||||
expect(new AngelHttpException.notImplemented(),
|
||||
isException(HttpStatus.NOT_IMPLEMENTED, '501 Not Implemented'));
|
||||
expect(new AngelHttpException.NotImplemented(),
|
||||
isException(HttpStatus.NOT_IMPLEMENTED, '501 Not Implemented'));
|
||||
expect(new AngelHttpException.unavailable(),
|
||||
isException(HttpStatus.SERVICE_UNAVAILABLE, '503 Unavailable'));
|
||||
expect(new AngelHttpException.Unavailable(),
|
||||
isException(HttpStatus.SERVICE_UNAVAILABLE, '503 Unavailable'));
|
||||
});
|
||||
|
||||
test('fromMap', () {
|
||||
|
@ -91,7 +68,7 @@ class _IsException extends Matcher {
|
|||
description.add('has status code $statusCode and message "$message"');
|
||||
|
||||
@override
|
||||
bool matches(AngelHttpException item, Map matchState) {
|
||||
bool matches(@checked AngelHttpException item, Map matchState) {
|
||||
return item.statusCode == statusCode && item.message == message;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,9 +13,7 @@ testMiddlewareMetadata(RequestContext req, ResponseContext res) async {
|
|||
class QueryService extends Service {
|
||||
@override
|
||||
@Middleware(const ['interceptor'])
|
||||
read(id, [Map params]) {
|
||||
return params;
|
||||
}
|
||||
read(id, [Map params]) async => params;
|
||||
}
|
||||
|
||||
main() {
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import 'dart:convert';
|
||||
import 'package:angel_framework/src/defs.dart';
|
||||
import 'package:angel_framework/angel_framework.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
|
@ -44,13 +45,8 @@ main() {
|
|||
var response = await client.get("$url/todos/");
|
||||
print(response.body);
|
||||
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);
|
||||
expect(god.deserialize(response.body).length, equals(3));
|
||||
expect(JSON.decode(response.body).length, 0);
|
||||
});
|
||||
|
||||
test('can create data', () async {
|
||||
|
@ -100,15 +96,12 @@ main() {
|
|||
|
||||
test('can delete data', () async {
|
||||
String postData = god.serialize({'text': 'Hello, world!'});
|
||||
await client.post("$url/todos", headers: headers, body: postData);
|
||||
var response = await client.delete("$url/todos/0");
|
||||
var created = await client.post("$url/todos", headers: headers, body: postData).then((r) => JSON.decode(r.body));
|
||||
var response = await client.delete("$url/todos/${created['id']}");
|
||||
expect(response.statusCode, 200);
|
||||
var json = god.deserialize(response.body);
|
||||
print(json);
|
||||
expect(json['text'], equals('Hello, world!'));
|
||||
response = await client.get("$url/todos");
|
||||
print(response.body);
|
||||
expect(god.deserialize(response.body).length, equals(0));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue