1.1.4
This commit is contained in:
parent
ef5a28b956
commit
954e8141dd
38 changed files with 1315 additions and 1388 deletions
|
@ -519,6 +519,7 @@
|
||||||
<root url="file://$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/yaml-2.1.13/lib" />
|
<root url="file://$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/yaml-2.1.13/lib" />
|
||||||
</CLASSES>
|
</CLASSES>
|
||||||
<JAVADOC />
|
<JAVADOC />
|
||||||
|
<LIBRARY_FILE />
|
||||||
<SOURCES />
|
<SOURCES />
|
||||||
</library>
|
</library>
|
||||||
</component>
|
</component>
|
|
@ -21,6 +21,7 @@
|
||||||
<root url="file:///usr/local/opt/dart/libexec/lib/web_sql" />
|
<root url="file:///usr/local/opt/dart/libexec/lib/web_sql" />
|
||||||
</CLASSES>
|
</CLASSES>
|
||||||
<JAVADOC />
|
<JAVADOC />
|
||||||
|
<LIBRARY_FILE />
|
||||||
<SOURCES />
|
<SOURCES />
|
||||||
</library>
|
</library>
|
||||||
</component>
|
</component>
|
|
@ -0,0 +1,8 @@
|
||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="Match routes, even with query params in routing_test.dart" type="DartTestRunConfigurationType" factoryName="Dart Test" singleton="true" nameIsGenerated="true">
|
||||||
|
<option name="filePath" value="$PROJECT_DIR$/test/routing_test.dart" />
|
||||||
|
<option name="scope" value="GROUP_OR_TEST_BY_NAME" />
|
||||||
|
<option name="testName" value="Match routes, even with query params" />
|
||||||
|
<method />
|
||||||
|
</configuration>
|
||||||
|
</component>
|
|
@ -0,0 +1,9 @@
|
||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="can fetch data in services_test.dart" type="DartTestRunConfigurationType" factoryName="Dart Test" singleton="true" nameIsGenerated="true">
|
||||||
|
<option name="filePath" value="$PROJECT_DIR$/test/services_test.dart" />
|
||||||
|
<option name="scope" value="GROUP_OR_TEST_BY_NAME" />
|
||||||
|
<option name="testName" value="can fetch data" />
|
||||||
|
<option name="testRunnerOptions" value="-j 4" />
|
||||||
|
<method />
|
||||||
|
</configuration>
|
||||||
|
</component>
|
1265
.idea/workspace.xml
1265
.idea/workspace.xml
File diff suppressed because it is too large
Load diff
|
@ -1,3 +1,8 @@
|
||||||
|
# 1.1.4
|
||||||
|
* Remove all usages of `async`/`await` in the core library.
|
||||||
|
* `AngelConfigurer` now accepts `FutureOr`.
|
||||||
|
* `AngelHttp` now has a `useZone` flag to disable custom zones.
|
||||||
|
|
||||||
# 1.1.3
|
# 1.1.3
|
||||||
* `AngelHttp` now handles requests in a `Zone`.
|
* `AngelHttp` now handles requests in a `Zone`.
|
||||||
* Use `package:dart2_constant`.
|
* Use `package:dart2_constant`.
|
||||||
|
|
|
@ -1,2 +1,5 @@
|
||||||
analyzer:
|
analyzer:
|
||||||
strong-mode: true
|
strong-mode: true
|
||||||
|
linter:
|
||||||
|
rules:
|
||||||
|
- avoid_slow_async_io
|
|
@ -1,5 +1,4 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:io';
|
|
||||||
import 'dart:isolate';
|
import 'dart:isolate';
|
||||||
import 'package:dart2_constant/convert.dart';
|
import 'package:dart2_constant/convert.dart';
|
||||||
import 'package:angel_framework/angel_framework.dart';
|
import 'package:angel_framework/angel_framework.dart';
|
||||||
|
@ -48,6 +47,6 @@ serverMain(_) async {
|
||||||
print(e.stackTrace);
|
print(e.stackTrace);
|
||||||
};
|
};
|
||||||
|
|
||||||
var server = await http.startServer(InternetAddress.LOOPBACK_IP_V4, 3000);
|
var server = await http.startServer('127.0.0.1', 3000);
|
||||||
print('Listening at http://${server.address.address}:${server.port}');
|
print('Listening at http://${server.address.address}:${server.port}');
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,19 +9,19 @@ import 'angel_framework.dart';
|
||||||
/// Sequentially runs a set of [listeners].
|
/// Sequentially runs a set of [listeners].
|
||||||
HookedServiceEventListener chainListeners(
|
HookedServiceEventListener chainListeners(
|
||||||
Iterable<HookedServiceEventListener> listeners) {
|
Iterable<HookedServiceEventListener> listeners) {
|
||||||
return (HookedServiceEvent e) async {
|
return (HookedServiceEvent e) {
|
||||||
for (HookedServiceEventListener listener in listeners) await listener(e);
|
for (HookedServiceEventListener listener in listeners) listener(e);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Runs a [callback] on every service, and listens for future services to run it again.
|
/// Runs a [callback] on every service, and listens for future services to run it again.
|
||||||
AngelConfigurer hookAllServices(callback(Service service)) {
|
AngelConfigurer hookAllServices(callback(Service service)) {
|
||||||
return (Angel app) async {
|
return (Angel app) {
|
||||||
List<Service> touched = [];
|
List<Service> touched = [];
|
||||||
|
|
||||||
for (var service in app.services.values) {
|
for (var service in app.services.values) {
|
||||||
if (!touched.contains(service)) {
|
if (!touched.contains(service)) {
|
||||||
await callback(service);
|
callback(service);
|
||||||
touched.add(service);
|
touched.add(service);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -52,9 +52,9 @@ HookedServiceEventListener transform(transformer(obj), [condition]) {
|
||||||
Iterable cond = condition is Iterable ? condition : [condition];
|
Iterable cond = condition is Iterable ? condition : [condition];
|
||||||
if (condition == null) cond = [];
|
if (condition == null) cond = [];
|
||||||
|
|
||||||
_condition(HookedServiceEvent e, condition) async {
|
_condition(HookedServiceEvent e, condition) {
|
||||||
if (condition is Function)
|
if (condition is Function)
|
||||||
return await condition(e);
|
return condition(e);
|
||||||
else if (condition == Providers)
|
else if (condition == Providers)
|
||||||
return true;
|
return true;
|
||||||
else {
|
else {
|
||||||
|
@ -70,11 +70,11 @@ HookedServiceEventListener transform(transformer(obj), [condition]) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
normalize(HookedServiceEvent e, obj) async {
|
normalize(HookedServiceEvent e, obj) {
|
||||||
bool transform = true;
|
bool transform = true;
|
||||||
|
|
||||||
for (var c in cond) {
|
for (var c in cond) {
|
||||||
var r = await _condition(e, c);
|
var r = _condition(e, c);
|
||||||
|
|
||||||
if (r != true) {
|
if (r != true) {
|
||||||
transform = false;
|
transform = false;
|
||||||
|
@ -97,7 +97,7 @@ HookedServiceEventListener transform(transformer(obj), [condition]) {
|
||||||
var r = [];
|
var r = [];
|
||||||
|
|
||||||
for (var o in obj) {
|
for (var o in obj) {
|
||||||
r.add(await normalize(e, o));
|
r.add(normalize(e, o));
|
||||||
}
|
}
|
||||||
|
|
||||||
return r;
|
return r;
|
||||||
|
@ -105,10 +105,10 @@ HookedServiceEventListener transform(transformer(obj), [condition]) {
|
||||||
return transformer(obj);
|
return transformer(obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (HookedServiceEvent e) async {
|
return (HookedServiceEvent e) {
|
||||||
if (e.isBefore) {
|
if (e.isBefore) {
|
||||||
e.data = await normalize(e, e.data);
|
e.data = normalize(e, e.data);
|
||||||
} else if (e.isAfter) e.result = await normalize(e, e.result);
|
} else if (e.isAfter) e.result = normalize(e, e.result);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -135,7 +135,7 @@ HookedServiceEventListener toType(Type type) {
|
||||||
/// Only applies to the client-side.
|
/// Only applies to the client-side.
|
||||||
HookedServiceEventListener remove(key, [remover(key, obj)]) {
|
HookedServiceEventListener remove(key, [remover(key, obj)]) {
|
||||||
return (HookedServiceEvent e) async {
|
return (HookedServiceEvent e) async {
|
||||||
_remover(key, obj) {
|
_remover(key, obj) async {
|
||||||
if (remover != null)
|
if (remover != null)
|
||||||
return remover(key, obj);
|
return remover(key, obj);
|
||||||
else if (obj is List)
|
else if (obj is List)
|
||||||
|
@ -171,15 +171,15 @@ HookedServiceEventListener remove(key, [remover(key, obj)]) {
|
||||||
if (obj is Iterable) {
|
if (obj is Iterable) {
|
||||||
return await Future.wait(obj.map(_removeAll));
|
return await Future.wait(obj.map(_removeAll));
|
||||||
} else
|
} else
|
||||||
return await _removeAll(obj);
|
return _removeAll(obj);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (e.params?.containsKey('provider') == true) {
|
if (e.params?.containsKey('provider') == true) {
|
||||||
if (e.isBefore) {
|
if (e.isBefore) {
|
||||||
e.data = await normalize(e.data);
|
e.data = normalize(e.data);
|
||||||
} else if (e.isAfter) {
|
} else if (e.isAfter) {
|
||||||
e.result = await normalize(e.result);
|
e.result = normalize(e.result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -193,12 +193,12 @@ HookedServiceEventListener remove(key, [remover(key, obj)]) {
|
||||||
///
|
///
|
||||||
/// If [provider] is `null`, then it will be disabled to all clients.
|
/// If [provider] is `null`, then it will be disabled to all clients.
|
||||||
HookedServiceEventListener disable([provider]) {
|
HookedServiceEventListener disable([provider]) {
|
||||||
return (HookedServiceEvent e) async {
|
return (HookedServiceEvent e) {
|
||||||
if (e.params.containsKey('provider')) {
|
if (e.params.containsKey('provider')) {
|
||||||
if (provider == null)
|
if (provider == null)
|
||||||
throw new AngelHttpException.methodNotAllowed();
|
throw new AngelHttpException.methodNotAllowed();
|
||||||
else if (provider is Function) {
|
else if (provider is Function) {
|
||||||
var r = await provider(e);
|
var r = provider(e);
|
||||||
if (r != true) throw new AngelHttpException.methodNotAllowed();
|
if (r != true) throw new AngelHttpException.methodNotAllowed();
|
||||||
} else {
|
} else {
|
||||||
_provide(p) => p is Providers ? p : new Providers(p.toString());
|
_provide(p) => p is Providers ? p : new Providers(p.toString());
|
||||||
|
@ -224,7 +224,7 @@ HookedServiceEventListener addCreatedAt(
|
||||||
{assign(obj, now), String key, bool serialize: true}) {
|
{assign(obj, now), String key, bool serialize: true}) {
|
||||||
var name = key?.isNotEmpty == true ? key : 'createdAt';
|
var name = key?.isNotEmpty == true ? key : 'createdAt';
|
||||||
|
|
||||||
return (HookedServiceEvent e) async {
|
return (HookedServiceEvent e) {
|
||||||
_assign(obj, now) {
|
_assign(obj, now) {
|
||||||
if (assign != null)
|
if (assign != null)
|
||||||
return assign(obj, now);
|
return assign(obj, now);
|
||||||
|
@ -242,17 +242,17 @@ HookedServiceEventListener addCreatedAt(
|
||||||
var d = new DateTime.now().toUtc();
|
var d = new DateTime.now().toUtc();
|
||||||
var now = serialize == false ? d : d.toIso8601String();
|
var now = serialize == false ? d : d.toIso8601String();
|
||||||
|
|
||||||
normalize(obj) async {
|
normalize(obj) {
|
||||||
if (obj != null) {
|
if (obj != null) {
|
||||||
if (obj is Iterable) {
|
if (obj is Iterable) {
|
||||||
obj.forEach(normalize);
|
obj.forEach(normalize);
|
||||||
} else {
|
} else {
|
||||||
await _assign(obj, now);
|
_assign(obj, now);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await normalize(e.isBefore ? e.data : e.result);
|
normalize(e.isBefore ? e.data : e.result);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -265,7 +265,7 @@ HookedServiceEventListener addUpdatedAt(
|
||||||
{assign(obj, now), String key, bool serialize: true}) {
|
{assign(obj, now), String key, bool serialize: true}) {
|
||||||
var name = key?.isNotEmpty == true ? key : 'updatedAt';
|
var name = key?.isNotEmpty == true ? key : 'updatedAt';
|
||||||
|
|
||||||
return (HookedServiceEvent e) async {
|
return (HookedServiceEvent e) {
|
||||||
_assign(obj, now) {
|
_assign(obj, now) {
|
||||||
if (assign != null)
|
if (assign != null)
|
||||||
return assign(obj, now);
|
return assign(obj, now);
|
||||||
|
@ -283,16 +283,16 @@ HookedServiceEventListener addUpdatedAt(
|
||||||
var d = new DateTime.now().toUtc();
|
var d = new DateTime.now().toUtc();
|
||||||
var now = serialize == false ? d : d.toIso8601String();
|
var now = serialize == false ? d : d.toIso8601String();
|
||||||
|
|
||||||
normalize(obj) async {
|
normalize(obj) {
|
||||||
if (obj != null) {
|
if (obj != null) {
|
||||||
if (obj is Iterable) {
|
if (obj is Iterable) {
|
||||||
obj.forEach(normalize);
|
obj.forEach(normalize);
|
||||||
} else {
|
} else {
|
||||||
await _assign(obj, now);
|
_assign(obj, now);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await normalize(e.isBefore ? e.data : e.result);
|
normalize(e.isBefore ? e.data : e.result);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,9 +9,8 @@ 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.
|
||||||
class AngelBase extends Routable {
|
class AngelBase extends Routable {
|
||||||
static ViewGenerator noViewEngineConfigured = (String view,
|
static ViewGenerator noViewEngineConfigured = (String view, [Map data]) =>
|
||||||
[Map data]) async =>
|
new Future<String>.value("No view engine has been configured yet.");
|
||||||
"No view engine has been configured yet.";
|
|
||||||
|
|
||||||
Container _container = new Container();
|
Container _container = new Container();
|
||||||
|
|
||||||
|
@ -35,9 +34,10 @@ class AngelBase extends Routable {
|
||||||
ViewGenerator viewGenerator = noViewEngineConfigured;
|
ViewGenerator viewGenerator = noViewEngineConfigured;
|
||||||
|
|
||||||
/// Closes this instance, rendering it **COMPLETELY DEFUNCT**.
|
/// Closes this instance, rendering it **COMPLETELY DEFUNCT**.
|
||||||
Future close() async {
|
Future close() {
|
||||||
await super.close();
|
super.close();
|
||||||
_container = null;
|
_container = null;
|
||||||
viewGenerator = noViewEngineConfigured;
|
viewGenerator = noViewEngineConfigured;
|
||||||
|
return new Future.value();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -67,7 +67,7 @@ class HookedService extends Service {
|
||||||
|
|
||||||
/// Closes any open [StreamController]s on this instance. **Internal use only**.
|
/// Closes any open [StreamController]s on this instance. **Internal use only**.
|
||||||
@override
|
@override
|
||||||
Future close() async {
|
Future close() {
|
||||||
_ctrl.forEach((c) => c.close());
|
_ctrl.forEach((c) => c.close());
|
||||||
beforeIndexed._close();
|
beforeIndexed._close();
|
||||||
beforeRead._close();
|
beforeRead._close();
|
||||||
|
@ -81,7 +81,8 @@ class HookedService extends Service {
|
||||||
afterModified._close();
|
afterModified._close();
|
||||||
afterUpdated._close();
|
afterUpdated._close();
|
||||||
afterRemoved._close();
|
afterRemoved._close();
|
||||||
await inner.close();
|
inner.close();
|
||||||
|
return new Future.value();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Adds hooks to this instance.
|
/// Adds hooks to this instance.
|
||||||
|
@ -120,149 +121,16 @@ class HookedService extends Service {
|
||||||
applyListeners(inner.remove, afterRemoved, true);
|
applyListeners(inner.remove, afterRemoved, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Adds routes to this instance.
|
List get bootstrappers => new List.from(super.bootstrappers)
|
||||||
@override
|
..add((RequestContext req, ResponseContext res) {
|
||||||
void addRoutes() {
|
req.serviceParams
|
||||||
// Set up our routes. We still need to copy middleware from inner service
|
..['__requestctx'] = req
|
||||||
Map restProvider = {'provider': Providers.rest};
|
..['__responsectx'] = res;
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
// Add global middleware if declared on the instance itself
|
void addRoutes([Service s]) {
|
||||||
Middleware before = getAnnotation(inner, Middleware);
|
super.addRoutes(s ?? inner);
|
||||||
List handlers = [
|
|
||||||
(RequestContext req, ResponseContext res) async {
|
|
||||||
req.serviceParams
|
|
||||||
..['__requestctx'] = req
|
|
||||||
..['__responsectx'] = res;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
if (before != null) handlers.addAll(before.handlers);
|
|
||||||
|
|
||||||
Middleware indexMiddleware = getAnnotation(inner.index, Middleware);
|
|
||||||
get('/', (req, res) async {
|
|
||||||
return await this.index(mergeMap([
|
|
||||||
{'query': req.query},
|
|
||||||
restProvider,
|
|
||||||
req.serviceParams
|
|
||||||
]));
|
|
||||||
},
|
|
||||||
middleware: []
|
|
||||||
..addAll(handlers)
|
|
||||||
..addAll((indexMiddleware == null) ? [] : indexMiddleware.handlers));
|
|
||||||
|
|
||||||
Middleware createMiddleware = getAnnotation(inner.create, Middleware);
|
|
||||||
|
|
||||||
post('/', (req, res) async {
|
|
||||||
var r = await this.create(
|
|
||||||
await req.lazyBody(),
|
|
||||||
mergeMap([
|
|
||||||
{'query': req.query},
|
|
||||||
restProvider,
|
|
||||||
req.serviceParams
|
|
||||||
]));
|
|
||||||
res.statusCode = 201;
|
|
||||||
return r;
|
|
||||||
},
|
|
||||||
middleware: []
|
|
||||||
..addAll(handlers)
|
|
||||||
..addAll(
|
|
||||||
(createMiddleware == null) ? [] : createMiddleware.handlers));
|
|
||||||
|
|
||||||
Middleware readMiddleware = getAnnotation(inner.read, Middleware);
|
|
||||||
|
|
||||||
get(
|
|
||||||
'/:id',
|
|
||||||
(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 modifyMiddleware = getAnnotation(inner.modify, Middleware);
|
|
||||||
patch(
|
|
||||||
'/:id',
|
|
||||||
(req, res) async => await this.modify(
|
|
||||||
toId(req.params['id']),
|
|
||||||
await req.lazyBody(),
|
|
||||||
mergeMap([
|
|
||||||
{'query': req.query},
|
|
||||||
restProvider,
|
|
||||||
req.serviceParams
|
|
||||||
])),
|
|
||||||
middleware: []
|
|
||||||
..addAll(handlers)
|
|
||||||
..addAll(
|
|
||||||
(modifyMiddleware == null) ? [] : modifyMiddleware.handlers));
|
|
||||||
|
|
||||||
Middleware updateMiddleware = getAnnotation(inner.update, Middleware);
|
|
||||||
post(
|
|
||||||
'/:id',
|
|
||||||
(req, res) async => await this.update(
|
|
||||||
toId(req.params['id']),
|
|
||||||
await req.lazyBody(),
|
|
||||||
mergeMap([
|
|
||||||
{'query': req.query},
|
|
||||||
restProvider,
|
|
||||||
req.serviceParams
|
|
||||||
])),
|
|
||||||
middleware: []
|
|
||||||
..addAll(handlers)
|
|
||||||
..addAll(
|
|
||||||
(updateMiddleware == null) ? [] : updateMiddleware.handlers));
|
|
||||||
put(
|
|
||||||
'/:id',
|
|
||||||
(req, res) async => await this.update(
|
|
||||||
toId(req.params['id']),
|
|
||||||
await req.lazyBody(),
|
|
||||||
mergeMap([
|
|
||||||
{'query': req.query},
|
|
||||||
restProvider,
|
|
||||||
req.serviceParams
|
|
||||||
])),
|
|
||||||
middleware: []
|
|
||||||
..addAll(handlers)
|
|
||||||
..addAll(
|
|
||||||
(updateMiddleware == null) ? [] : updateMiddleware.handlers));
|
|
||||||
|
|
||||||
Middleware removeMiddleware = getAnnotation(inner.remove, Middleware);
|
|
||||||
delete(
|
|
||||||
'/',
|
|
||||||
(req, res) async => await this.remove(
|
|
||||||
null,
|
|
||||||
mergeMap([
|
|
||||||
{'query': req.query},
|
|
||||||
restProvider,
|
|
||||||
req.serviceParams
|
|
||||||
])),
|
|
||||||
middleware: []
|
|
||||||
..addAll(handlers)
|
|
||||||
..addAll(
|
|
||||||
(removeMiddleware == null) ? [] : removeMiddleware.handlers));
|
|
||||||
delete(
|
|
||||||
'/:id',
|
|
||||||
(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));
|
|
||||||
|
|
||||||
// REST compliance
|
|
||||||
put('/', () => throw new AngelHttpException.notFound());
|
|
||||||
patch('/', () => throw new AngelHttpException.notFound());
|
|
||||||
|
|
||||||
addHooks();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Runs the [listener] before every service method specified.
|
/// Runs the [listener] before every service method specified.
|
||||||
|
@ -388,192 +256,165 @@ class HookedService extends Service {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future index([Map _params]) async {
|
Future index([Map _params]) {
|
||||||
var params = _stripReq(_params);
|
var params = _stripReq(_params);
|
||||||
HookedServiceEvent before = await beforeIndexed._emit(
|
return beforeIndexed
|
||||||
new HookedServiceEvent(false, _getRequest(_params),
|
._emit(new HookedServiceEvent(false, _getRequest(_params),
|
||||||
_getResponse(_params), inner, HookedServiceEvent.indexed,
|
_getResponse(_params), inner, HookedServiceEvent.indexed,
|
||||||
params: params));
|
params: params))
|
||||||
if (before._canceled) {
|
.then((before) {
|
||||||
HookedServiceEvent after = await beforeIndexed._emit(
|
if (before._canceled) {
|
||||||
new HookedServiceEvent(true, _getRequest(_params),
|
return beforeIndexed
|
||||||
_getResponse(_params), inner, HookedServiceEvent.indexed,
|
._emit(new HookedServiceEvent(true, _getRequest(_params),
|
||||||
params: params, result: before.result));
|
_getResponse(_params), inner, HookedServiceEvent.indexed,
|
||||||
return after.result;
|
params: params, result: before.result))
|
||||||
}
|
.then((after) => after.result);
|
||||||
|
}
|
||||||
|
|
||||||
var result = await inner.index(params);
|
return inner.index(params).then((result) {
|
||||||
HookedServiceEvent after = await afterIndexed._emit(new HookedServiceEvent(
|
return afterIndexed
|
||||||
true,
|
._emit(new HookedServiceEvent(true, _getRequest(_params),
|
||||||
_getRequest(_params),
|
_getResponse(_params), inner, HookedServiceEvent.indexed,
|
||||||
_getResponse(_params),
|
params: params, result: result))
|
||||||
inner,
|
.then((after) => after.result);
|
||||||
HookedServiceEvent.indexed,
|
});
|
||||||
params: params,
|
});
|
||||||
result: result));
|
|
||||||
return after.result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future read(id, [Map _params]) async {
|
Future read(id, [Map _params]) {
|
||||||
var params = _stripReq(_params);
|
var params = _stripReq(_params);
|
||||||
HookedServiceEvent before = await beforeRead._emit(new HookedServiceEvent(
|
return beforeRead
|
||||||
false,
|
._emit(new HookedServiceEvent(false, _getRequest(_params),
|
||||||
_getRequest(_params),
|
_getResponse(_params), inner, HookedServiceEvent.read,
|
||||||
_getResponse(_params),
|
id: id, params: params))
|
||||||
inner,
|
.then((before) {
|
||||||
HookedServiceEvent.indexed,
|
if (before._canceled) {
|
||||||
id: id,
|
return beforeRead
|
||||||
params: params));
|
._emit(new HookedServiceEvent(true, _getRequest(_params),
|
||||||
|
_getResponse(_params), inner, HookedServiceEvent.read,
|
||||||
|
id: id, params: params, result: before.result))
|
||||||
|
.then((after) => after.result);
|
||||||
|
}
|
||||||
|
|
||||||
if (before._canceled) {
|
return inner.read(id, params).then((result) {
|
||||||
HookedServiceEvent after = await afterRead._emit(new HookedServiceEvent(
|
return afterRead
|
||||||
true,
|
._emit(new HookedServiceEvent(true, _getRequest(_params),
|
||||||
_getRequest(_params),
|
_getResponse(_params), inner, HookedServiceEvent.read,
|
||||||
_getResponse(_params),
|
id: id, params: params, result: result))
|
||||||
inner,
|
.then((after) => after.result);
|
||||||
HookedServiceEvent.read,
|
});
|
||||||
id: id,
|
});
|
||||||
params: params,
|
|
||||||
result: before.result));
|
|
||||||
return after.result;
|
|
||||||
}
|
|
||||||
|
|
||||||
var result = await inner.read(id, params);
|
|
||||||
HookedServiceEvent after = await afterRead._emit(new HookedServiceEvent(
|
|
||||||
true,
|
|
||||||
_getRequest(_params),
|
|
||||||
_getResponse(_params),
|
|
||||||
inner,
|
|
||||||
HookedServiceEvent.read,
|
|
||||||
id: id,
|
|
||||||
params: params,
|
|
||||||
result: result));
|
|
||||||
return after.result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future create(data, [Map _params]) async {
|
Future create(data, [Map _params]) {
|
||||||
var params = _stripReq(_params);
|
var params = _stripReq(_params);
|
||||||
HookedServiceEvent before = await beforeCreated._emit(
|
return beforeCreated
|
||||||
new HookedServiceEvent(false, _getRequest(_params),
|
._emit(new HookedServiceEvent(false, _getRequest(_params),
|
||||||
_getResponse(_params), inner, HookedServiceEvent.created,
|
_getResponse(_params), inner, HookedServiceEvent.created,
|
||||||
data: data, params: params));
|
data: data, params: params))
|
||||||
|
.then((before) {
|
||||||
|
if (before._canceled) {
|
||||||
|
return beforeCreated
|
||||||
|
._emit(new HookedServiceEvent(true, _getRequest(_params),
|
||||||
|
_getResponse(_params), inner, HookedServiceEvent.created,
|
||||||
|
data: data, params: params, result: before.result))
|
||||||
|
.then((after) => after.result);
|
||||||
|
}
|
||||||
|
|
||||||
if (before._canceled) {
|
return inner.create(data, params).then((result) {
|
||||||
HookedServiceEvent after = await afterCreated._emit(
|
return afterCreated
|
||||||
new HookedServiceEvent(true, _getRequest(_params),
|
._emit(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: result))
|
||||||
return after.result;
|
.then((after) => after.result);
|
||||||
}
|
});
|
||||||
|
});
|
||||||
var result = await inner.create(data, params);
|
|
||||||
HookedServiceEvent after = await afterCreated._emit(new HookedServiceEvent(
|
|
||||||
true,
|
|
||||||
_getRequest(_params),
|
|
||||||
_getResponse(_params),
|
|
||||||
inner,
|
|
||||||
HookedServiceEvent.created,
|
|
||||||
data: data,
|
|
||||||
params: params,
|
|
||||||
result: result));
|
|
||||||
return after.result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future modify(id, data, [Map _params]) async {
|
Future modify(id, data, [Map _params]) {
|
||||||
var params = _stripReq(_params);
|
var params = _stripReq(_params);
|
||||||
HookedServiceEvent before = await beforeModified._emit(
|
return beforeModified
|
||||||
new HookedServiceEvent(false, _getRequest(_params),
|
._emit(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))
|
||||||
|
.then((before) {
|
||||||
|
if (before._canceled) {
|
||||||
|
return beforeModified
|
||||||
|
._emit(new HookedServiceEvent(true, _getRequest(_params),
|
||||||
|
_getResponse(_params), inner, HookedServiceEvent.modified,
|
||||||
|
id: id, data: data, params: params, result: before.result))
|
||||||
|
.then((after) => after.result);
|
||||||
|
}
|
||||||
|
|
||||||
if (before._canceled) {
|
return inner.modify(id, data, params).then((result) {
|
||||||
HookedServiceEvent after = await afterModified._emit(
|
return afterModified
|
||||||
new HookedServiceEvent(true, _getRequest(_params),
|
._emit(new HookedServiceEvent(true, _getRequest(_params),
|
||||||
_getResponse(_params), inner, HookedServiceEvent.modified,
|
_getResponse(_params), inner, HookedServiceEvent.created,
|
||||||
id: id, data: data, params: params, result: before.result));
|
id: id, data: data, params: params, result: result))
|
||||||
return after.result;
|
.then((after) => after.result);
|
||||||
}
|
});
|
||||||
|
});
|
||||||
var result = await inner.modify(id, data, params);
|
|
||||||
HookedServiceEvent after = await afterModified._emit(new HookedServiceEvent(
|
|
||||||
true,
|
|
||||||
_getRequest(_params),
|
|
||||||
_getResponse(_params),
|
|
||||||
inner,
|
|
||||||
HookedServiceEvent.modified,
|
|
||||||
id: id,
|
|
||||||
data: data,
|
|
||||||
params: params,
|
|
||||||
result: result));
|
|
||||||
return after.result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future update(id, data, [Map _params]) async {
|
Future update(id, data, [Map _params]) {
|
||||||
var params = _stripReq(_params);
|
var params = _stripReq(_params);
|
||||||
HookedServiceEvent before = await beforeUpdated._emit(
|
return beforeUpdated
|
||||||
new HookedServiceEvent(false, _getRequest(_params),
|
._emit(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))
|
||||||
|
.then((before) {
|
||||||
|
if (before._canceled) {
|
||||||
|
return beforeUpdated
|
||||||
|
._emit(new HookedServiceEvent(true, _getRequest(_params),
|
||||||
|
_getResponse(_params), inner, HookedServiceEvent.updated,
|
||||||
|
id: id, data: data, params: params, result: before.result))
|
||||||
|
.then((after) => after.result);
|
||||||
|
}
|
||||||
|
|
||||||
if (before._canceled) {
|
return inner.update(id, data, params).then((result) {
|
||||||
HookedServiceEvent after = await afterUpdated._emit(
|
return afterUpdated
|
||||||
new HookedServiceEvent(true, _getRequest(_params),
|
._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));
|
id: id, data: data, params: params, result: result))
|
||||||
return after.result;
|
.then((after) => after.result);
|
||||||
}
|
});
|
||||||
|
});
|
||||||
var result = await inner.update(id, data, params);
|
|
||||||
HookedServiceEvent after = await afterUpdated._emit(new HookedServiceEvent(
|
|
||||||
true,
|
|
||||||
_getRequest(_params),
|
|
||||||
_getResponse(_params),
|
|
||||||
inner,
|
|
||||||
HookedServiceEvent.updated,
|
|
||||||
id: id,
|
|
||||||
data: data,
|
|
||||||
params: params,
|
|
||||||
result: result));
|
|
||||||
return after.result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future remove(id, [Map _params]) async {
|
Future remove(id, [Map _params]) {
|
||||||
var params = _stripReq(_params);
|
var params = _stripReq(_params);
|
||||||
HookedServiceEvent before = await beforeRemoved._emit(
|
return beforeRemoved
|
||||||
new HookedServiceEvent(false, _getRequest(_params),
|
._emit(new HookedServiceEvent(false, _getRequest(_params),
|
||||||
_getResponse(_params), inner, HookedServiceEvent.removed,
|
_getResponse(_params), inner, HookedServiceEvent.removed,
|
||||||
id: id, params: params));
|
id: id, params: params))
|
||||||
|
.then((before) {
|
||||||
|
if (before._canceled) {
|
||||||
|
return beforeRemoved
|
||||||
|
._emit(new HookedServiceEvent(true, _getRequest(_params),
|
||||||
|
_getResponse(_params), inner, HookedServiceEvent.removed,
|
||||||
|
id: id, params: params, result: before.result))
|
||||||
|
.then((after) => after.result);
|
||||||
|
}
|
||||||
|
|
||||||
if (before._canceled) {
|
return inner.remove(id, params).then((result) {
|
||||||
HookedServiceEvent after = await afterRemoved._emit(
|
return afterRemoved
|
||||||
new HookedServiceEvent(true, _getRequest(_params),
|
._emit(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: result))
|
||||||
return after.result;
|
.then((after) => after.result);
|
||||||
}
|
});
|
||||||
|
});
|
||||||
var result = await inner.remove(id, params);
|
|
||||||
HookedServiceEvent after = await afterRemoved._emit(new HookedServiceEvent(
|
|
||||||
true,
|
|
||||||
_getRequest(_params),
|
|
||||||
_getResponse(_params),
|
|
||||||
inner,
|
|
||||||
HookedServiceEvent.removed,
|
|
||||||
id: id,
|
|
||||||
params: params,
|
|
||||||
result: result));
|
|
||||||
return after.result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Fires an `after` event. This will not be propagated to clients,
|
/// Fires an `after` event. This will not be propagated to clients,
|
||||||
/// but will be broadcasted to WebSockets, etc.
|
/// but will be broadcasted to WebSockets, etc.
|
||||||
Future<HookedServiceEvent> fire(String eventName, result,
|
Future<HookedServiceEvent> fire(String eventName, result,
|
||||||
[HookedServiceEventListener callback]) async {
|
[HookedServiceEventListener callback]) {
|
||||||
HookedServiceEventDispatcher dispatcher;
|
HookedServiceEventDispatcher dispatcher;
|
||||||
|
|
||||||
switch (eventName) {
|
switch (eventName) {
|
||||||
|
@ -600,15 +441,15 @@ class HookedService extends Service {
|
||||||
}
|
}
|
||||||
|
|
||||||
var ev = new HookedServiceEvent(true, null, null, this, eventName);
|
var ev = new HookedServiceEvent(true, null, null, this, eventName);
|
||||||
return await fireEvent(dispatcher, ev, callback);
|
return fireEvent(dispatcher, ev, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sends an arbitrary event down the hook chain.
|
/// Sends an arbitrary event down the hook chain.
|
||||||
Future<HookedServiceEvent> fireEvent(
|
Future<HookedServiceEvent> fireEvent(
|
||||||
HookedServiceEventDispatcher dispatcher, HookedServiceEvent event,
|
HookedServiceEventDispatcher dispatcher, HookedServiceEvent event,
|
||||||
[HookedServiceEventListener callback]) async {
|
[HookedServiceEventListener callback]) {
|
||||||
if (callback != null && event?._canceled != true) await callback(event);
|
if (callback != null && event?._canceled != true) callback(event);
|
||||||
return await dispatcher._emit(event);
|
return dispatcher._emit(event);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -690,16 +531,16 @@ class HookedServiceEventDispatcher {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Fires an event, and returns it once it is either canceled, or all listeners have run.
|
/// Fires an event, and returns it once it is either canceled, or all listeners have run.
|
||||||
Future<HookedServiceEvent> _emit(HookedServiceEvent event) async {
|
Future<HookedServiceEvent> _emit(HookedServiceEvent event) {
|
||||||
if (event?._canceled != true) {
|
if (event?._canceled != true) {
|
||||||
for (var listener in listeners) {
|
for (var listener in listeners) {
|
||||||
await listener(event);
|
listener(event);
|
||||||
|
|
||||||
if (event._canceled) return event;
|
if (event._canceled) return new Future.value(event);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return event;
|
return new Future.value(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a [Stream] containing all events fired by this dispatcher.
|
/// Returns a [Stream] containing all events fired by this dispatcher.
|
||||||
|
|
|
@ -78,10 +78,11 @@ bool suitableForInjection(
|
||||||
|
|
||||||
/// 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) {
|
||||||
if (injection.parameters.isNotEmpty &&
|
if (injection.parameters.isNotEmpty &&
|
||||||
injection.parameters.values.any((p) => p.match != null) &&
|
injection.parameters.values.any((p) => p.match != null) &&
|
||||||
!suitableForInjection(req, res, injection)) return true;
|
!suitableForInjection(req, res, injection))
|
||||||
|
return new Future.value(true);
|
||||||
|
|
||||||
List args = [];
|
List args = [];
|
||||||
|
|
||||||
|
@ -94,8 +95,7 @@ RequestHandler handleContained(handler, InjectionRequest injection) {
|
||||||
named[name] = resolveInjection([k, v], injection, req, res, false);
|
named[name] = resolveInjection([k, v], injection, req, res, false);
|
||||||
});
|
});
|
||||||
|
|
||||||
var result = Function.apply(handler, args, named);
|
return Function.apply(handler, args, named);
|
||||||
return result is Future ? await result : result;
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -181,6 +181,5 @@ InjectionRequest preInject(Function handler) {
|
||||||
injection.named[name] = type;
|
injection.named[name] = type;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return injection;
|
return injection;
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,13 +45,13 @@ class MapService extends Service {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<List> index([Map params]) async {
|
Future<List> index([Map params]) {
|
||||||
if (allowQuery == false || params == null || params['query'] is! Map)
|
if (allowQuery == false || params == null || params['query'] is! Map)
|
||||||
return items;
|
return new Future.value(items);
|
||||||
else {
|
else {
|
||||||
Map query = params['query'];
|
Map query = params['query'];
|
||||||
|
|
||||||
return items.where((item) {
|
return new Future.value(items.where((item) {
|
||||||
for (var key in query.keys) {
|
for (var key in query.keys) {
|
||||||
if (!item.containsKey(key))
|
if (!item.containsKey(key))
|
||||||
return false;
|
return false;
|
||||||
|
@ -59,19 +59,19 @@ class MapService extends Service {
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}).toList();
|
}).toList());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Map> read(id, [Map params]) async {
|
Future<Map> read(id, [Map params]) {
|
||||||
return items.firstWhere(_matchesId(id),
|
return new Future.value(items.firstWhere(_matchesId(id),
|
||||||
orElse: () => throw new AngelHttpException.notFound(
|
orElse: () => throw new AngelHttpException.notFound(
|
||||||
message: 'No record found for ID $id'));
|
message: 'No record found for ID $id')));
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Map> create(data, [Map params]) async {
|
Future<Map> create(data, [Map params]) {
|
||||||
if (data is! Map)
|
if (data is! Map)
|
||||||
throw new AngelHttpException.badRequest(
|
throw new AngelHttpException.badRequest(
|
||||||
message:
|
message:
|
||||||
|
@ -86,70 +86,71 @@ class MapService extends Service {
|
||||||
..[autoSnakeCaseNames == false ? 'updatedAt' : 'updated_at'] = now;
|
..[autoSnakeCaseNames == false ? 'updatedAt' : 'updated_at'] = now;
|
||||||
}
|
}
|
||||||
items.add(result);
|
items.add(result);
|
||||||
return result;
|
return new Future.value(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Map> modify(id, data, [Map params]) async {
|
Future<Map> modify(id, data, [Map params]) {
|
||||||
if (data is! Map)
|
if (data is! Map)
|
||||||
throw new AngelHttpException.badRequest(
|
throw new AngelHttpException.badRequest(
|
||||||
message:
|
message:
|
||||||
'MapService does not support `modify` with ${data.runtimeType}.');
|
'MapService does not support `modify` with ${data.runtimeType}.');
|
||||||
if (!items.any(_matchesId(id))) return await create(data, params);
|
if (!items.any(_matchesId(id))) return create(data, params);
|
||||||
|
|
||||||
var item = await read(id);
|
return read(id).then((item) {
|
||||||
var result = item..addAll(data);
|
var result = item..addAll(data);
|
||||||
|
|
||||||
if (autoIdAndDateFields == true)
|
if (autoIdAndDateFields == true)
|
||||||
result
|
result
|
||||||
..[autoSnakeCaseNames == false ? 'updatedAt' : 'updated_at'] =
|
..[autoSnakeCaseNames == false ? 'updatedAt' : 'updated_at'] =
|
||||||
new DateTime.now().toIso8601String();
|
new DateTime.now().toIso8601String();
|
||||||
return result;
|
return new Future.value(result);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Map> update(id, data, [Map params]) async {
|
Future<Map> update(id, data, [Map params]) {
|
||||||
if (data is! Map)
|
if (data is! Map)
|
||||||
throw new AngelHttpException.badRequest(
|
throw new AngelHttpException.badRequest(
|
||||||
message:
|
message:
|
||||||
'MapService does not support `update` with ${data.runtimeType}.');
|
'MapService does not support `update` with ${data.runtimeType}.');
|
||||||
if (!items.any(_matchesId(id))) return await create(data, params);
|
if (!items.any(_matchesId(id))) return create(data, params);
|
||||||
|
|
||||||
var old = await read(id);
|
return read(id).then((old) {
|
||||||
|
if (!items.remove(old))
|
||||||
|
throw new AngelHttpException.notFound(
|
||||||
|
message: 'No record found for ID $id');
|
||||||
|
|
||||||
if (!items.remove(old))
|
var result = data;
|
||||||
throw new AngelHttpException.notFound(
|
if (autoIdAndDateFields == true) {
|
||||||
message: 'No record found for ID $id');
|
result
|
||||||
|
..['id'] = id?.toString()
|
||||||
var result = data;
|
..[autoSnakeCaseNames == false ? 'createdAt' : 'created_at'] =
|
||||||
if (autoIdAndDateFields == true) {
|
old[autoSnakeCaseNames == false ? 'createdAt' : 'created_at']
|
||||||
result
|
..[autoSnakeCaseNames == false ? 'updatedAt' : 'updated_at'] =
|
||||||
..['id'] = id?.toString()
|
new DateTime.now().toIso8601String();
|
||||||
..[autoSnakeCaseNames == false ? 'createdAt' : 'created_at'] =
|
}
|
||||||
old[autoSnakeCaseNames == false ? 'createdAt' : 'created_at']
|
items.add(result);
|
||||||
..[autoSnakeCaseNames == false ? 'updatedAt' : 'updated_at'] =
|
return new Future.value(result);
|
||||||
new DateTime.now().toIso8601String();
|
});
|
||||||
}
|
|
||||||
items.add(result);
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Map> remove(id, [Map params]) async {
|
Future<Map> remove(id, [Map params]) {
|
||||||
if (id == null ||
|
if (id == null ||
|
||||||
id == 'null' &&
|
id == 'null' &&
|
||||||
(allowRemoveAll == true ||
|
(allowRemoveAll == true ||
|
||||||
params?.containsKey('provider') != true)) {
|
params?.containsKey('provider') != true)) {
|
||||||
items.clear();
|
items.clear();
|
||||||
return {};
|
return new Future.value({});
|
||||||
}
|
}
|
||||||
|
|
||||||
var result = await read(id, params);
|
return read(id, params).then((result) {
|
||||||
|
if (items.remove(result))
|
||||||
if (items.remove(result))
|
return result;
|
||||||
return result;
|
else
|
||||||
else
|
throw new AngelHttpException.notFound(
|
||||||
throw new AngelHttpException.notFound(
|
message: 'No record found for ID $id');
|
||||||
message: 'No record found for ID $id');
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ import 'metadata.dart';
|
||||||
import 'response_context.dart';
|
import 'response_context.dart';
|
||||||
import 'routable.dart';
|
import 'routable.dart';
|
||||||
import 'server.dart' show Angel;
|
import 'server.dart' show Angel;
|
||||||
|
|
||||||
part 'injection.dart';
|
part 'injection.dart';
|
||||||
|
|
||||||
/// A convenience wrapper around an incoming HTTP request.
|
/// A convenience wrapper around an incoming HTTP request.
|
||||||
|
@ -167,8 +168,8 @@ abstract class RequestContext {
|
||||||
void inject(type, value) {
|
void inject(type, value) {
|
||||||
if (!app.isProduction && type is Type) {
|
if (!app.isProduction && type is Type) {
|
||||||
if (!reflect(value).type.isAssignableTo(reflectType(type)))
|
if (!reflect(value).type.isAssignableTo(reflectType(type)))
|
||||||
throw new ArgumentError(
|
throw new ArgumentError('Cannot inject $value (${value
|
||||||
'Cannot inject $value (${value.runtimeType}) as an instance of $type.');
|
.runtimeType}) as an instance of $type.');
|
||||||
}
|
}
|
||||||
|
|
||||||
_injections[type] = value;
|
_injections[type] = value;
|
||||||
|
@ -230,12 +231,12 @@ abstract class RequestContext {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Manually parses the request body, if it has not already been parsed.
|
/// Manually parses the request body, if it has not already been parsed.
|
||||||
Future<BodyParseResult> parse() async {
|
Future<BodyParseResult> parse() {
|
||||||
if (_body != null)
|
if (_body != null)
|
||||||
return _body;
|
return new Future.value(_body);
|
||||||
else
|
else
|
||||||
_provisionalQuery = null;
|
_provisionalQuery = null;
|
||||||
return _body = await parseOnce();
|
return parseOnce().then((body) => _body = body);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Override this method to one-time parse an incoming request.
|
/// Override this method to one-time parse an incoming request.
|
||||||
|
@ -243,7 +244,7 @@ abstract class RequestContext {
|
||||||
Future<BodyParseResult> parseOnce();
|
Future<BodyParseResult> parseOnce();
|
||||||
|
|
||||||
/// Disposes of all resources.
|
/// Disposes of all resources.
|
||||||
Future close() async {
|
Future close() {
|
||||||
_body = null;
|
_body = null;
|
||||||
_acceptsAllCache = null;
|
_acceptsAllCache = null;
|
||||||
_acceptHeaderCache = null;
|
_acceptHeaderCache = null;
|
||||||
|
@ -252,5 +253,6 @@ abstract class RequestContext {
|
||||||
_injections.clear();
|
_injections.clear();
|
||||||
serviceParams.clear();
|
serviceParams.clear();
|
||||||
params.clear();
|
params.clear();
|
||||||
|
return new Future.value();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -123,10 +123,11 @@ abstract class ResponseContext implements StreamSink<List<int>>, StringSink {
|
||||||
/// If `true`, all response finalizers will be skipped.
|
/// If `true`, all response finalizers will be skipped.
|
||||||
bool willCloseItself = false;
|
bool willCloseItself = false;
|
||||||
|
|
||||||
static StateError closed() => new StateError('Cannot modify a closed response.');
|
static StateError closed() =>
|
||||||
|
new StateError('Cannot modify a closed response.');
|
||||||
|
|
||||||
/// Sends a download as a response.
|
/// Sends a download as a response.
|
||||||
Future download(File file, {String filename}) async {
|
void download(File file, {String filename}) {
|
||||||
if (!isOpen) throw closed();
|
if (!isOpen) throw closed();
|
||||||
|
|
||||||
headers["Content-Disposition"] =
|
headers["Content-Disposition"] =
|
||||||
|
@ -135,9 +136,9 @@ abstract class ResponseContext implements StreamSink<List<int>>, StringSink {
|
||||||
headers['content-length'] = file.lengthSync().toString();
|
headers['content-length'] = file.lengthSync().toString();
|
||||||
|
|
||||||
if (streaming) {
|
if (streaming) {
|
||||||
await file.openRead().pipe(this);
|
file.openRead().pipe(this);
|
||||||
} else {
|
} else {
|
||||||
buffer.add(await file.readAsBytes());
|
buffer.add(file.readAsBytesSync());
|
||||||
end();
|
end();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -157,8 +158,8 @@ abstract class ResponseContext implements StreamSink<List<int>>, StringSink {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Disposes of all resources.
|
/// Disposes of all resources.
|
||||||
Future dispose() async {
|
void dispose() {
|
||||||
await close();
|
close();
|
||||||
properties.clear();
|
properties.clear();
|
||||||
encoders.clear();
|
encoders.clear();
|
||||||
_buffer.clear();
|
_buffer.clear();
|
||||||
|
@ -197,11 +198,12 @@ abstract class ResponseContext implements StreamSink<List<int>>, StringSink {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Renders a view to the response stream, and closes the response.
|
/// Renders a view to the response stream, and closes the response.
|
||||||
Future render(String view, [Map data]) async {
|
Future render(String view, [Map data]) {
|
||||||
if (!isOpen) throw closed();
|
if (!isOpen) throw closed();
|
||||||
write(await app.viewGenerator(view, data));
|
write(app.viewGenerator(view, data));
|
||||||
headers['content-type'] = ContentType.HTML.toString();
|
headers['content-type'] = ContentType.HTML.toString();
|
||||||
end();
|
end();
|
||||||
|
return new Future.value();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Redirects to user to the given URL.
|
/// Redirects to user to the given URL.
|
||||||
|
@ -293,11 +295,11 @@ abstract class ResponseContext implements StreamSink<List<int>>, StringSink {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Copies a file's contents into the response buffer.
|
/// Copies a file's contents into the response buffer.
|
||||||
Future sendFile(File file) async {
|
void sendFile(File file) {
|
||||||
if (!isOpen) throw closed();
|
if (!isOpen) throw closed();
|
||||||
|
|
||||||
headers['content-type'] = lookupMimeType(file.path);
|
headers['content-type'] = lookupMimeType(file.path);
|
||||||
buffer.add(await file.readAsBytes());
|
buffer.add(file.readAsBytesSync());
|
||||||
end();
|
end();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -309,8 +311,7 @@ abstract class ResponseContext implements StreamSink<List<int>>, StringSink {
|
||||||
|
|
||||||
var text = serializer(value);
|
var text = serializer(value);
|
||||||
|
|
||||||
if (text.isEmpty)
|
if (text.isEmpty) return;
|
||||||
return;
|
|
||||||
|
|
||||||
if (contentType is String)
|
if (contentType is String)
|
||||||
headers['content-type'] = contentType;
|
headers['content-type'] = contentType;
|
||||||
|
@ -337,10 +338,8 @@ abstract class ResponseContext implements StreamSink<List<int>>, StringSink {
|
||||||
(correspondingRequest.injections[Stopwatch] as Stopwatch).stop();
|
(correspondingRequest.injections[Stopwatch] as Stopwatch).stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (correspondingRequest?.injections?.containsKey(PoolResource) ==
|
if (correspondingRequest?.injections?.containsKey(PoolResource) == true) {
|
||||||
true) {
|
(correspondingRequest.injections[PoolResource] as PoolResource).release();
|
||||||
(correspondingRequest.injections[PoolResource] as PoolResource)
|
|
||||||
.release();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,6 @@ 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 'hooked_service.dart';
|
import 'hooked_service.dart';
|
||||||
|
@ -22,13 +21,23 @@ typedef Future RequestHandler(RequestContext req, ResponseContext res);
|
||||||
/// Sequentially runs a list of [handlers] of middleware, and returns early if any does not
|
/// Sequentially runs a list of [handlers] of middleware, and returns early if any does not
|
||||||
/// return `true`. Works well with [Router].chain.
|
/// return `true`. Works well with [Router].chain.
|
||||||
RequestMiddleware waterfall(List handlers) {
|
RequestMiddleware waterfall(List handlers) {
|
||||||
return (RequestContext req, res) async {
|
return (req, res) {
|
||||||
|
Future<bool> Function() runPipeline;
|
||||||
|
|
||||||
for (var handler in handlers) {
|
for (var handler in handlers) {
|
||||||
var result = await req.app.executeHandler(handler, req, res);
|
if (handler == null) break;
|
||||||
if (result != true) return result;
|
|
||||||
|
if (runPipeline == null)
|
||||||
|
runPipeline = () => req.app.executeHandler(handler, req, res);
|
||||||
|
else {
|
||||||
|
var current = runPipeline;
|
||||||
|
runPipeline = () => current().then((result) =>
|
||||||
|
!result ? result : req.app.executeHandler(handler, req, res));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
runPipeline ??= () => new Future.value(true);
|
||||||
|
return runPipeline();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -39,7 +48,7 @@ class Routable extends Router {
|
||||||
|
|
||||||
Routable() : super();
|
Routable() : super();
|
||||||
|
|
||||||
Future close() async {
|
void close() {
|
||||||
_services.clear();
|
_services.clear();
|
||||||
configuration.clear();
|
configuration.clear();
|
||||||
requestMiddleware.clear();
|
requestMiddleware.clear();
|
||||||
|
@ -63,9 +72,11 @@ 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, @checked RequestHandler middleware) =>
|
registerMiddleware(String name, middleware) {
|
||||||
// ignore: deprecated_member_use
|
assert(middleware is RequestMiddleware);
|
||||||
super.registerMiddleware(name, middleware);
|
// ignore: deprecated_member_use
|
||||||
|
super.registerMiddleware(name, middleware);
|
||||||
|
}
|
||||||
|
|
||||||
/// Retrieves the service assigned to the given path.
|
/// Retrieves the service assigned to the given path.
|
||||||
Service service(Pattern path) =>
|
Service service(Pattern path) =>
|
||||||
|
@ -116,7 +127,7 @@ class Routable extends Router {
|
||||||
final handlers = [];
|
final handlers = [];
|
||||||
|
|
||||||
if (_router is AngelBase) {
|
if (_router is AngelBase) {
|
||||||
handlers.add((RequestContext req, ResponseContext res) async {
|
handlers.add((RequestContext req, ResponseContext res) {
|
||||||
req.app = _router;
|
req.app = _router;
|
||||||
res.app = _router;
|
res.app = _router;
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -9,7 +9,6 @@ import 'package:angel_route/angel_route.dart';
|
||||||
import 'package:combinator/combinator.dart';
|
import 'package:combinator/combinator.dart';
|
||||||
export 'package:container/container.dart';
|
export 'package:container/container.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:meta/meta.dart';
|
|
||||||
import 'package:tuple/tuple.dart';
|
import 'package:tuple/tuple.dart';
|
||||||
import '../http/http.dart';
|
import '../http/http.dart';
|
||||||
import 'angel_base.dart';
|
import 'angel_base.dart';
|
||||||
|
@ -27,7 +26,7 @@ final RegExp _straySlashes = new RegExp(r'(^/+)|(/+$)');
|
||||||
typedef Future<HttpServer> ServerGenerator(address, int port);
|
typedef Future<HttpServer> ServerGenerator(address, int port);
|
||||||
|
|
||||||
/// A function that configures an [Angel] server in some way.
|
/// A function that configures an [Angel] server in some way.
|
||||||
typedef Future AngelConfigurer(Angel app);
|
typedef FutureOr 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 {
|
||||||
|
@ -202,12 +201,12 @@ class Angel extends AngelBase {
|
||||||
/// Shuts down the server, and closes any open [StreamController]s.
|
/// Shuts down the server, and closes any open [StreamController]s.
|
||||||
///
|
///
|
||||||
/// The server will be **COMPLETE DEFUNCT** after this operation!
|
/// The server will be **COMPLETE DEFUNCT** after this operation!
|
||||||
Future close() async {
|
Future close() {
|
||||||
await Future.forEach(services.values, (Service service) async {
|
Future.forEach(services.values, (Service service) {
|
||||||
await service.close();
|
service.close();
|
||||||
});
|
});
|
||||||
|
|
||||||
await super.close();
|
super.close();
|
||||||
_preContained.clear();
|
_preContained.clear();
|
||||||
handlerCache.clear();
|
handlerCache.clear();
|
||||||
_injections.clear();
|
_injections.clear();
|
||||||
|
@ -220,8 +219,8 @@ class Angel extends AngelBase {
|
||||||
shutdownHooks.clear();
|
shutdownHooks.clear();
|
||||||
responseFinalizers.clear();
|
responseFinalizers.clear();
|
||||||
_flattened = null;
|
_flattened = null;
|
||||||
await _http?.close();
|
_http?.close();
|
||||||
return _http?.httpServer;
|
return new Future.value(_http?.httpServer);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -269,52 +268,50 @@ class Angel extends AngelBase {
|
||||||
this.serializer = serializer;
|
this.serializer = serializer;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future getHandlerResult(
|
Future getHandlerResult(handler, RequestContext req, ResponseContext res) {
|
||||||
handler, RequestContext req, ResponseContext res) async {
|
|
||||||
if (handler is RequestHandler) {
|
if (handler is RequestHandler) {
|
||||||
var result = await handler(req, res);
|
var result = handler(req, res);
|
||||||
return await getHandlerResult(result, req, res);
|
return getHandlerResult(result, req, res);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (handler is Future) {
|
if (handler is Future) {
|
||||||
var result = await handler;
|
return handler.then((result) => getHandlerResult(result, req, res));
|
||||||
return await getHandlerResult(result, req, res);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (handler is Function) {
|
if (handler is Function) {
|
||||||
var result = await runContained(handler, req, res);
|
var result = runContained(handler, req, res);
|
||||||
return await getHandlerResult(result, req, res);
|
return getHandlerResult(result, req, res);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (handler is Stream) {
|
if (handler is Stream) {
|
||||||
return await getHandlerResult(await handler.toList(), req, res);
|
return getHandlerResult(handler.toList(), req, res);
|
||||||
}
|
}
|
||||||
|
|
||||||
var middleware = (req.app ?? this).findMiddleware(handler);
|
var middleware = (req.app ?? this).findMiddleware(handler);
|
||||||
if (middleware != null) {
|
if (middleware != null) {
|
||||||
return await getHandlerResult(middleware, req, res);
|
return getHandlerResult(middleware, req, res);
|
||||||
}
|
}
|
||||||
|
|
||||||
return handler;
|
return new Future.value(handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Runs some [handler]. Returns `true` if request execution should continue.
|
/// Runs some [handler]. Returns `true` if request execution should continue.
|
||||||
Future<bool> executeHandler(
|
Future<bool> executeHandler(
|
||||||
handler, RequestContext req, ResponseContext res) async {
|
handler, RequestContext req, ResponseContext res) {
|
||||||
var result = await getHandlerResult(handler, req, res);
|
return getHandlerResult(handler, req, res).then((result) {
|
||||||
|
if (result == null)
|
||||||
if (result == null)
|
return false;
|
||||||
return false;
|
else if (result is bool) {
|
||||||
else if (result is bool) {
|
return result;
|
||||||
return result;
|
} else if (result != null) {
|
||||||
} else if (result != null) {
|
// TODO: Make `serialize` return a bool, return this as the value.
|
||||||
// TODO: Make `serialize` return a bool, return this as the value.
|
// Do this wherever applicable
|
||||||
// Do this wherever applicable
|
res.serialize(result,
|
||||||
res.serialize(result,
|
contentType: res.headers['content-type'] ?? 'application/json');
|
||||||
contentType: res.headers['content-type'] ?? 'application/json');
|
return false;
|
||||||
return false;
|
} else
|
||||||
} else
|
return res.isOpen;
|
||||||
return res.isOpen;
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Use the serving methods in [AngelHttp] instead.
|
/// Use the serving methods in [AngelHttp] instead.
|
||||||
|
@ -400,20 +397,22 @@ class Angel extends AngelBase {
|
||||||
/// the execution will be faster, as the injection requirements were stored beforehand.
|
/// the execution will be faster, as the injection requirements were stored beforehand.
|
||||||
Future runContained(
|
Future runContained(
|
||||||
Function handler, RequestContext req, ResponseContext res) {
|
Function handler, RequestContext req, ResponseContext res) {
|
||||||
if (_preContained.containsKey(handler)) {
|
return new Future.sync(() {
|
||||||
return handleContained(handler, _preContained[handler])(req, res);
|
if (_preContained.containsKey(handler)) {
|
||||||
}
|
return handleContained(handler, _preContained[handler])(req, res);
|
||||||
|
}
|
||||||
|
|
||||||
return runReflected(handler, req, res);
|
return runReflected(handler, req, res);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Runs with DI, and *always* reflects. Prefer [runContained].
|
/// Runs with DI, and *always* reflects. Prefer [runContained].
|
||||||
Future runReflected(
|
Future runReflected(
|
||||||
Function handler, RequestContext req, ResponseContext res) async {
|
Function handler, RequestContext req, ResponseContext res) {
|
||||||
var h =
|
var h =
|
||||||
handleContained(handler, _preContained[handler] = preInject(handler));
|
handleContained(handler, _preContained[handler] = preInject(handler));
|
||||||
return await h(req, res);
|
return new Future.sync(() => h(req, res));
|
||||||
// return await closureMirror.apply(args).reflectee;
|
// return closureMirror.apply(args).reflectee;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Use the serving methods in [AngelHttp] instead.
|
/// Use the serving methods in [AngelHttp] instead.
|
||||||
|
@ -432,8 +431,8 @@ class Angel extends AngelBase {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Applies an [AngelConfigurer] to this instance.
|
/// Applies an [AngelConfigurer] to this instance.
|
||||||
Future configure(AngelConfigurer configurer) async {
|
Future configure(AngelConfigurer configurer) {
|
||||||
await configurer(this);
|
return new Future.sync(() => configurer(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Mounts the child on this router. If [routable] is `null`,
|
/// Mounts the child on this router. If [routable] is `null`,
|
||||||
|
@ -447,7 +446,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(path, [@checked Routable routable, String namespace = null]) {
|
use(path, [Router routable, String namespace = null]) {
|
||||||
if (routable == null) return all('*', path);
|
if (routable == null) return all('*', path);
|
||||||
|
|
||||||
var head = path.toString().replaceAll(_straySlashes, '');
|
var head = path.toString().replaceAll(_straySlashes, '');
|
||||||
|
@ -457,13 +456,13 @@ class Angel extends AngelBase {
|
||||||
_preContained.addAll(routable._preContained);
|
_preContained.addAll(routable._preContained);
|
||||||
|
|
||||||
if (routable.responseFinalizers.isNotEmpty) {
|
if (routable.responseFinalizers.isNotEmpty) {
|
||||||
responseFinalizers.add((req, res) async {
|
responseFinalizers.add((req, res) {
|
||||||
if (req.path.replaceAll(_straySlashes, '').startsWith(head)) {
|
if (req.path.replaceAll(_straySlashes, '').startsWith(head)) {
|
||||||
for (var finalizer in routable.responseFinalizers)
|
for (var finalizer in routable.responseFinalizers)
|
||||||
await finalizer(req, res);
|
finalizer(req, res);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return new Future.value(true);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -493,15 +492,17 @@ class Angel extends AngelBase {
|
||||||
}
|
}
|
||||||
|
|
||||||
@deprecated
|
@deprecated
|
||||||
Future<ZoneSpecification> defaultZoneCreator(request, req, res) async {
|
Future<ZoneSpecification> defaultZoneCreator(request, req, res) {
|
||||||
return new ZoneSpecification(
|
return new Future.value(
|
||||||
print: (Zone self, ZoneDelegate parent, Zone zone, String line) {
|
new ZoneSpecification(
|
||||||
if (logger != null) {
|
print: (Zone self, ZoneDelegate parent, Zone zone, String line) {
|
||||||
logger.info(line);
|
if (logger != null) {
|
||||||
} else {
|
logger.info(line);
|
||||||
return parent.print(zone, line);
|
} else {
|
||||||
}
|
return parent.print(zone, line);
|
||||||
},
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -518,9 +519,8 @@ class Angel extends AngelBase {
|
||||||
factory Angel.fromSecurityContext(SecurityContext context) {
|
factory Angel.fromSecurityContext(SecurityContext context) {
|
||||||
var app = new Angel();
|
var app = new Angel();
|
||||||
|
|
||||||
app._http =
|
app._http = new AngelHttp.custom(app, (address, int port) {
|
||||||
new AngelHttp.custom(app, (address, int port) async {
|
return HttpServer.bindSecure(address, port, context);
|
||||||
return await HttpServer.bindSecure(address, port, context);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return app;
|
return app;
|
||||||
|
|
|
@ -7,6 +7,7 @@ import '../util.dart';
|
||||||
import 'angel_base.dart';
|
import 'angel_base.dart';
|
||||||
import 'hooked_service.dart' show HookedService;
|
import 'hooked_service.dart' show HookedService;
|
||||||
import 'metadata.dart';
|
import 'metadata.dart';
|
||||||
|
import 'request_context.dart';
|
||||||
import 'response_context.dart';
|
import 'response_context.dart';
|
||||||
import 'routable.dart';
|
import 'routable.dart';
|
||||||
|
|
||||||
|
@ -54,11 +55,14 @@ class Service extends Routable {
|
||||||
'token'
|
'token'
|
||||||
];
|
];
|
||||||
|
|
||||||
|
/// Handlers that must run to ensure this service's functionality.
|
||||||
|
List get bootstrappers => [];
|
||||||
|
|
||||||
/// The [Angel] app powering this service.
|
/// The [Angel] app powering this service.
|
||||||
AngelBase app;
|
AngelBase app;
|
||||||
|
|
||||||
/// Closes this service, including any database connections or stream controllers.
|
/// Closes this service, including any database connections or stream controllers.
|
||||||
Future close() async {}
|
void close() {}
|
||||||
|
|
||||||
/// Retrieves all resources.
|
/// Retrieves all resources.
|
||||||
Future index([Map params]) {
|
Future index([Map params]) {
|
||||||
|
@ -99,18 +103,22 @@ 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([Service service]) {
|
||||||
|
_addRoutesInner(service ?? this, bootstrappers);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _addRoutesInner(Service service,
|
||||||
|
List handlers) {
|
||||||
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(service, Middleware);
|
||||||
final handlers = [];
|
|
||||||
|
|
||||||
if (before != null) handlers.addAll(before.handlers);
|
if (before != null) handlers.addAll(before.handlers);
|
||||||
|
|
||||||
Middleware indexMiddleware = getAnnotation(this.index, Middleware);
|
Middleware indexMiddleware = getAnnotation(service.index, Middleware);
|
||||||
get('/', (req, res) async {
|
get('/', (req, res) {
|
||||||
return await this.index(mergeMap([
|
return this.index(mergeMap([
|
||||||
{'query': req.query},
|
{'query': req.query},
|
||||||
restProvider,
|
restProvider,
|
||||||
req.serviceParams
|
req.serviceParams
|
||||||
|
@ -120,28 +128,33 @@ class Service extends Routable {
|
||||||
..addAll(handlers)
|
..addAll(handlers)
|
||||||
..addAll((indexMiddleware == null) ? [] : indexMiddleware.handlers));
|
..addAll((indexMiddleware == null) ? [] : indexMiddleware.handlers));
|
||||||
|
|
||||||
Middleware createMiddleware = getAnnotation(this.create, Middleware);
|
Middleware createMiddleware = getAnnotation(service.create, Middleware);
|
||||||
post('/', (req, ResponseContext res) async {
|
post('/', (RequestContext req, ResponseContext res) {
|
||||||
var r = await this.create(
|
return req.lazyBody().then((body) {
|
||||||
await req.lazyBody(),
|
return this
|
||||||
mergeMap([
|
.create(
|
||||||
{'query': req.query},
|
body,
|
||||||
restProvider,
|
mergeMap([
|
||||||
req.serviceParams
|
{'query': req.query},
|
||||||
]));
|
restProvider,
|
||||||
res.statusCode = 201;
|
req.serviceParams
|
||||||
return r;
|
]))
|
||||||
|
.then((r) {
|
||||||
|
res.statusCode = 201;
|
||||||
|
return r;
|
||||||
|
});
|
||||||
|
});
|
||||||
},
|
},
|
||||||
middleware: []
|
middleware: []
|
||||||
..addAll(handlers)
|
..addAll(handlers)
|
||||||
..addAll(
|
..addAll(
|
||||||
(createMiddleware == null) ? [] : createMiddleware.handlers));
|
(createMiddleware == null) ? [] : createMiddleware.handlers));
|
||||||
|
|
||||||
Middleware readMiddleware = getAnnotation(this.read, Middleware);
|
Middleware readMiddleware = getAnnotation(service.read, Middleware);
|
||||||
|
|
||||||
get(
|
get(
|
||||||
'/:id',
|
'/:id',
|
||||||
(req, res) async => await this.read(
|
(req, res) => this.read(
|
||||||
toId(req.params['id']),
|
toId(req.params['id']),
|
||||||
mergeMap([
|
mergeMap([
|
||||||
{'query': req.query},
|
{'query': req.query},
|
||||||
|
@ -152,56 +165,56 @@ class Service extends Routable {
|
||||||
..addAll(handlers)
|
..addAll(handlers)
|
||||||
..addAll((readMiddleware == null) ? [] : readMiddleware.handlers));
|
..addAll((readMiddleware == null) ? [] : readMiddleware.handlers));
|
||||||
|
|
||||||
Middleware modifyMiddleware = getAnnotation(this.modify, Middleware);
|
Middleware modifyMiddleware = getAnnotation(service.modify, Middleware);
|
||||||
patch(
|
patch(
|
||||||
'/:id',
|
'/:id',
|
||||||
(req, res) async => await this.modify(
|
(RequestContext req, res) => req.lazyBody().then((body) => this.modify(
|
||||||
toId(req.params['id']),
|
toId(req.params['id']),
|
||||||
await req.lazyBody(),
|
body,
|
||||||
mergeMap([
|
mergeMap([
|
||||||
{'query': req.query},
|
{'query': req.query},
|
||||||
restProvider,
|
restProvider,
|
||||||
req.serviceParams
|
req.serviceParams
|
||||||
])),
|
]))),
|
||||||
middleware: []
|
middleware: []
|
||||||
..addAll(handlers)
|
..addAll(handlers)
|
||||||
..addAll(
|
..addAll(
|
||||||
(modifyMiddleware == null) ? [] : modifyMiddleware.handlers));
|
(modifyMiddleware == null) ? [] : modifyMiddleware.handlers));
|
||||||
|
|
||||||
Middleware updateMiddleware = getAnnotation(this.update, Middleware);
|
Middleware updateMiddleware = getAnnotation(service.update, Middleware);
|
||||||
post(
|
post(
|
||||||
'/:id',
|
'/:id',
|
||||||
(req, res) async => await this.update(
|
(RequestContext req, res) => req.lazyBody().then((body) => this.update(
|
||||||
toId(req.params['id']),
|
toId(req.params['id']),
|
||||||
await req.lazyBody(),
|
body,
|
||||||
mergeMap([
|
mergeMap([
|
||||||
{'query': req.query},
|
{'query': req.query},
|
||||||
restProvider,
|
restProvider,
|
||||||
req.serviceParams
|
req.serviceParams
|
||||||
])),
|
]))),
|
||||||
middleware: []
|
middleware: []
|
||||||
..addAll(handlers)
|
..addAll(handlers)
|
||||||
..addAll(
|
..addAll(
|
||||||
(updateMiddleware == null) ? [] : updateMiddleware.handlers));
|
(updateMiddleware == null) ? [] : updateMiddleware.handlers));
|
||||||
put(
|
put(
|
||||||
'/:id',
|
'/:id',
|
||||||
(req, res) async => await this.update(
|
(RequestContext req, res) => req.lazyBody().then((body) => this.update(
|
||||||
toId(req.params['id']),
|
toId(req.params['id']),
|
||||||
await req.lazyBody(),
|
body,
|
||||||
mergeMap([
|
mergeMap([
|
||||||
{'query': req.query},
|
{'query': req.query},
|
||||||
restProvider,
|
restProvider,
|
||||||
req.serviceParams
|
req.serviceParams
|
||||||
])),
|
]))),
|
||||||
middleware: []
|
middleware: []
|
||||||
..addAll(handlers)
|
..addAll(handlers)
|
||||||
..addAll(
|
..addAll(
|
||||||
(updateMiddleware == null) ? [] : updateMiddleware.handlers));
|
(updateMiddleware == null) ? [] : updateMiddleware.handlers));
|
||||||
|
|
||||||
Middleware removeMiddleware = getAnnotation(this.remove, Middleware);
|
Middleware removeMiddleware = getAnnotation(service.remove, Middleware);
|
||||||
delete(
|
delete(
|
||||||
'/',
|
'/',
|
||||||
(req, res) async => await this.remove(
|
(req, res) => this.remove(
|
||||||
null,
|
null,
|
||||||
mergeMap([
|
mergeMap([
|
||||||
{'query': req.query},
|
{'query': req.query},
|
||||||
|
@ -214,7 +227,7 @@ class Service extends Routable {
|
||||||
(removeMiddleware == null) ? [] : removeMiddleware.handlers));
|
(removeMiddleware == null) ? [] : removeMiddleware.handlers));
|
||||||
delete(
|
delete(
|
||||||
'/:id',
|
'/:id',
|
||||||
(req, res) async => await this.remove(
|
(req, res) => this.remove(
|
||||||
toId(req.params['id']),
|
toId(req.params['id']),
|
||||||
mergeMap([
|
mergeMap([
|
||||||
{'query': req.query},
|
{'query': req.query},
|
||||||
|
|
|
@ -1,9 +1,17 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:io';
|
import 'dart:io'
|
||||||
|
show
|
||||||
|
stderr,
|
||||||
|
HttpRequest,
|
||||||
|
HttpResponse,
|
||||||
|
HttpServer,
|
||||||
|
Platform,
|
||||||
|
SecurityContext;
|
||||||
import 'package:angel_http_exception/angel_http_exception.dart';
|
import 'package:angel_http_exception/angel_http_exception.dart';
|
||||||
import 'package:angel_route/angel_route.dart';
|
import 'package:angel_route/angel_route.dart';
|
||||||
import 'package:combinator/combinator.dart';
|
import 'package:combinator/combinator.dart';
|
||||||
|
import 'package:dart2_constant/io.dart';
|
||||||
import 'package:json_god/json_god.dart' as god;
|
import 'package:json_god/json_god.dart' as god;
|
||||||
import 'package:pool/pool.dart';
|
import 'package:pool/pool.dart';
|
||||||
import 'package:stack_trace/stack_trace.dart';
|
import 'package:stack_trace/stack_trace.dart';
|
||||||
|
@ -17,6 +25,7 @@ final RegExp _straySlashes = new RegExp(r'(^/+)|(/+$)');
|
||||||
/// Adapts `dart:io`'s [HttpServer] to serve Angel.
|
/// Adapts `dart:io`'s [HttpServer] to serve Angel.
|
||||||
class AngelHttp {
|
class AngelHttp {
|
||||||
final Angel app;
|
final Angel app;
|
||||||
|
final bool useZone;
|
||||||
bool _closed = false;
|
bool _closed = false;
|
||||||
HttpServer _server;
|
HttpServer _server;
|
||||||
Future<HttpServer> Function(dynamic, int) _serverGenerator = HttpServer.bind;
|
Future<HttpServer> Function(dynamic, int) _serverGenerator = HttpServer.bind;
|
||||||
|
@ -24,7 +33,7 @@ class AngelHttp {
|
||||||
|
|
||||||
Pool _pool;
|
Pool _pool;
|
||||||
|
|
||||||
AngelHttp(this.app);
|
AngelHttp(this.app, {this.useZone: true});
|
||||||
|
|
||||||
/// The function used to bind this instance to an HTTP server.
|
/// The function used to bind this instance to an HTTP server.
|
||||||
Future<HttpServer> Function(dynamic, int) get serverGenerator =>
|
Future<HttpServer> Function(dynamic, int) get serverGenerator =>
|
||||||
|
@ -32,15 +41,18 @@ class AngelHttp {
|
||||||
|
|
||||||
/// An instance mounted on a server started by the [serverGenerator].
|
/// An instance mounted on a server started by the [serverGenerator].
|
||||||
factory AngelHttp.custom(
|
factory AngelHttp.custom(
|
||||||
Angel app, Future<HttpServer> Function(dynamic, int) serverGenerator) {
|
Angel app, Future<HttpServer> Function(dynamic, int) serverGenerator,
|
||||||
return new AngelHttp(app).._serverGenerator = serverGenerator;
|
{bool useZone: true}) {
|
||||||
|
return new AngelHttp(app, useZone: useZone)
|
||||||
|
.._serverGenerator = serverGenerator;
|
||||||
}
|
}
|
||||||
|
|
||||||
factory AngelHttp.fromSecurityContext(Angel app, SecurityContext context) {
|
factory AngelHttp.fromSecurityContext(Angel app, SecurityContext context,
|
||||||
var http = new AngelHttp(app);
|
{bool useZone: true}) {
|
||||||
|
var http = new AngelHttp(app, useZone: useZone);
|
||||||
|
|
||||||
http._serverGenerator = (address, int port) async {
|
http._serverGenerator = (address, int port) {
|
||||||
return await HttpServer.bindSecure(address, port, context);
|
return HttpServer.bindSecure(address, port, context);
|
||||||
};
|
};
|
||||||
|
|
||||||
return http;
|
return http;
|
||||||
|
@ -53,7 +65,7 @@ class AngelHttp {
|
||||||
/// the server.
|
/// the server.
|
||||||
factory AngelHttp.secure(
|
factory AngelHttp.secure(
|
||||||
Angel app, String certificateChainPath, String serverKeyPath,
|
Angel app, String certificateChainPath, String serverKeyPath,
|
||||||
{bool debug: false, String password}) {
|
{bool debug: false, String password, bool useZone: true}) {
|
||||||
var certificateChain =
|
var certificateChain =
|
||||||
Platform.script.resolve(certificateChainPath).toFilePath();
|
Platform.script.resolve(certificateChainPath).toFilePath();
|
||||||
var serverKey = Platform.script.resolve(serverKeyPath).toFilePath();
|
var serverKey = Platform.script.resolve(serverKeyPath).toFilePath();
|
||||||
|
@ -61,7 +73,8 @@ class AngelHttp {
|
||||||
serverContext.useCertificateChain(certificateChain, password: password);
|
serverContext.useCertificateChain(certificateChain, password: password);
|
||||||
serverContext.usePrivateKey(serverKey, password: password);
|
serverContext.usePrivateKey(serverKey, password: password);
|
||||||
|
|
||||||
return new AngelHttp.fromSecurityContext(app, serverContext);
|
return new AngelHttp.fromSecurityContext(app, serverContext,
|
||||||
|
useZone: useZone);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The native HttpServer running this instance.
|
/// The native HttpServer running this instance.
|
||||||
|
@ -70,139 +83,153 @@ class AngelHttp {
|
||||||
/// Starts the server.
|
/// Starts the server.
|
||||||
///
|
///
|
||||||
/// Returns false on failure; otherwise, returns the HttpServer.
|
/// Returns false on failure; otherwise, returns the HttpServer.
|
||||||
Future<HttpServer> startServer([address, int port]) async {
|
Future<HttpServer> startServer([address, int port]) {
|
||||||
var host = address ?? InternetAddress.LOOPBACK_IP_V4;
|
var host = address ?? '127.0.0.1';
|
||||||
_server = await _serverGenerator(host, port ?? 0);
|
return _serverGenerator(host, port ?? 0).then((server) {
|
||||||
|
_server = server;
|
||||||
for (var configurer in app.startupHooks) {
|
return Future.wait(app.startupHooks.map(app.configure)).then((_) {
|
||||||
await app.configure(configurer);
|
app.optimizeForProduction();
|
||||||
}
|
_sub = _server.listen(handleRequest);
|
||||||
|
return _server;
|
||||||
app.optimizeForProduction();
|
});
|
||||||
_sub = _server.listen(handleRequest);
|
});
|
||||||
return _server;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Shuts down the underlying server.
|
/// Shuts down the underlying server.
|
||||||
Future<HttpServer> close() async {
|
Future<HttpServer> close() {
|
||||||
if (_closed) return _server;
|
if (_closed) return new Future.value(_server);
|
||||||
_closed = true;
|
_closed = true;
|
||||||
_sub?.cancel();
|
_sub?.cancel();
|
||||||
|
|
||||||
// TODO: Remove this try/catch in 1.2.0
|
// TODO: Remove this try/catch in 1.2.0
|
||||||
try {
|
var close = app.close().catchError((_) => null);
|
||||||
await app.close();
|
return close.then((_) =>
|
||||||
} catch (_) {}
|
Future.wait(app.shutdownHooks.map(app.configure)).then((_) => _server));
|
||||||
|
|
||||||
for (var configurer in app.shutdownHooks) await app.configure(configurer);
|
|
||||||
return _server;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handles a single request.
|
/// Handles a single request.
|
||||||
Future handleRequest(HttpRequest request) async {
|
Future handleRequest(HttpRequest request) {
|
||||||
var req = await createRequestContext(request);
|
return createRequestContext(request).then((req) {
|
||||||
var res = await createResponseContext(request.response, req);
|
return createResponseContext(request.response, req).then((res) {
|
||||||
|
handle() {
|
||||||
|
var path = req.path;
|
||||||
|
if (path == '/') path = '';
|
||||||
|
|
||||||
var zoneSpec = new ZoneSpecification(
|
Tuple3<List, Map, ParseResult<Map<String, String>>> resolveTuple() {
|
||||||
print: (self, parent, zone, line) {
|
Router r = app.optimizedRouter;
|
||||||
if (app.logger != null)
|
var resolved =
|
||||||
app.logger.info(line);
|
r.resolveAbsolute(path, method: req.method, strip: false);
|
||||||
else
|
|
||||||
parent.print(zone, line);
|
|
||||||
},
|
|
||||||
handleUncaughtError: (self, parent, zone, error, stackTrace) async {
|
|
||||||
var trace = new Trace.from(stackTrace).terse;
|
|
||||||
|
|
||||||
try {
|
return new Tuple3(
|
||||||
AngelHttpException e;
|
new MiddlewarePipeline(resolved).handlers,
|
||||||
|
resolved.fold<Map>({}, (out, r) => out..addAll(r.allParams)),
|
||||||
if (error is FormatException) {
|
resolved.isEmpty ? null : resolved.first.parseResult,
|
||||||
e = new AngelHttpException.badRequest(message: error.message);
|
);
|
||||||
} else if (error is AngelHttpException) {
|
|
||||||
e = error;
|
|
||||||
} else {
|
|
||||||
e = new AngelHttpException(error,
|
|
||||||
stackTrace: stackTrace, message: error?.toString());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (app.logger != null) {
|
var cacheKey = req.method + path;
|
||||||
app.logger.severe(e.message ?? e.toString(), error, trace);
|
var tuple = app.isProduction
|
||||||
|
? app.handlerCache.putIfAbsent(cacheKey, resolveTuple)
|
||||||
|
: resolveTuple();
|
||||||
|
|
||||||
|
req.params.addAll(tuple.item2);
|
||||||
|
req.inject(ParseResult, tuple.item3);
|
||||||
|
|
||||||
|
if (app.logger != null)
|
||||||
|
req.inject(Stopwatch, new Stopwatch()..start());
|
||||||
|
|
||||||
|
var pipeline = tuple.item1;
|
||||||
|
|
||||||
|
Future<bool> Function() runPipeline;
|
||||||
|
|
||||||
|
for (var handler in pipeline) {
|
||||||
|
if (handler == null) break;
|
||||||
|
|
||||||
|
if (runPipeline == null)
|
||||||
|
runPipeline = () => app.executeHandler(handler, req, res);
|
||||||
|
else {
|
||||||
|
var current = runPipeline;
|
||||||
|
runPipeline = () => current().then((result) =>
|
||||||
|
!result ? result : app.executeHandler(handler, req, res));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await handleAngelHttpException(e, trace, req, res, request);
|
return runPipeline == null
|
||||||
} catch (e, st) {
|
? sendResponse(request, req, res)
|
||||||
var trace = new Trace.from(st).terse;
|
: runPipeline().then((_) => sendResponse(request, req, res));
|
||||||
request.response.close();
|
|
||||||
// Ideally, we won't be in a position where an absolutely fatal error occurs,
|
|
||||||
// but if so, we'll need to log it.
|
|
||||||
if (app.logger != null) {
|
|
||||||
app.logger.severe(
|
|
||||||
'Fatal error occurred when processing ${request.uri}.',
|
|
||||||
e,
|
|
||||||
trace);
|
|
||||||
} else {
|
|
||||||
stderr
|
|
||||||
..writeln('Fatal error occurred when processing '
|
|
||||||
'${request.uri}:')
|
|
||||||
..writeln(e)
|
|
||||||
..writeln(trace);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
var zone = Zone.current.fork(specification: zoneSpec);
|
if (useZone == false) {
|
||||||
|
return handle().whenComplete(() => res.dispose());
|
||||||
|
} else {
|
||||||
|
var zoneSpec = new ZoneSpecification(
|
||||||
|
print: (self, parent, zone, line) {
|
||||||
|
if (app.logger != null)
|
||||||
|
app.logger.info(line);
|
||||||
|
else
|
||||||
|
parent.print(zone, line);
|
||||||
|
},
|
||||||
|
handleUncaughtError: (self, parent, zone, error, stackTrace) {
|
||||||
|
var trace = new Trace.from(stackTrace).terse;
|
||||||
|
|
||||||
return await zone.run(() async {
|
return new Future(() {
|
||||||
var path = req.path;
|
AngelHttpException e;
|
||||||
if (path == '/') path = '';
|
|
||||||
|
|
||||||
Tuple3<List, Map, ParseResult<Map<String, String>>> resolveTuple() {
|
if (error is FormatException) {
|
||||||
Router r = app.optimizedRouter;
|
e = new AngelHttpException.badRequest(message: error.message);
|
||||||
var resolved =
|
} else if (error is AngelHttpException) {
|
||||||
r.resolveAbsolute(path, method: req.method, strip: false);
|
e = error;
|
||||||
|
} else {
|
||||||
|
e = new AngelHttpException(error,
|
||||||
|
stackTrace: stackTrace, message: error?.toString());
|
||||||
|
}
|
||||||
|
|
||||||
return new Tuple3(
|
if (app.logger != null) {
|
||||||
new MiddlewarePipeline(resolved).handlers,
|
app.logger.severe(e.message ?? e.toString(), error, trace);
|
||||||
resolved.fold<Map>({}, (out, r) => out..addAll(r.allParams)),
|
}
|
||||||
resolved.isEmpty ? null : resolved.first.parseResult,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
var cacheKey = req.method + path;
|
return handleAngelHttpException(e, trace, req, res, request);
|
||||||
var tuple = app.isProduction
|
}).catchError((e, st) {
|
||||||
? app.handlerCache.putIfAbsent(cacheKey, resolveTuple)
|
var trace = new Trace.from(st).terse;
|
||||||
: resolveTuple();
|
request.response.close();
|
||||||
|
// Ideally, we won't be in a position where an absolutely fatal error occurs,
|
||||||
|
// but if so, we'll need to log it.
|
||||||
|
if (app.logger != null) {
|
||||||
|
app.logger.severe(
|
||||||
|
'Fatal error occurred when processing ${request.uri}.',
|
||||||
|
e,
|
||||||
|
trace);
|
||||||
|
} else {
|
||||||
|
stderr
|
||||||
|
..writeln('Fatal error occurred when processing '
|
||||||
|
'${request.uri}:')
|
||||||
|
..writeln(e)
|
||||||
|
..writeln(trace);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
req.inject(Zone, zone);
|
var zone = Zone.current.fork(specification: zoneSpec);
|
||||||
req.inject(ZoneSpecification, zoneSpec);
|
req.inject(Zone, zone);
|
||||||
req.params.addAll(tuple.item2);
|
req.inject(ZoneSpecification, zoneSpec);
|
||||||
req.inject(ParseResult, tuple.item3);
|
return zone.run(handle).whenComplete(() {
|
||||||
|
res.dispose();
|
||||||
if (app.logger != null) req.inject(Stopwatch, new Stopwatch()..start());
|
});
|
||||||
|
}
|
||||||
var pipeline = tuple.item1;
|
});
|
||||||
|
|
||||||
for (var handler in pipeline) {
|
|
||||||
if (handler == null || !await app.executeHandler(handler, req, res))
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
await sendResponse(request, req, res);
|
|
||||||
}).whenComplete(() {
|
|
||||||
res.dispose();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handles an [AngelHttpException].
|
/// Handles an [AngelHttpException].
|
||||||
Future handleAngelHttpException(AngelHttpException e, StackTrace st,
|
Future handleAngelHttpException(AngelHttpException e, StackTrace st,
|
||||||
RequestContext req, ResponseContext res, HttpRequest request,
|
RequestContext req, ResponseContext res, HttpRequest request,
|
||||||
{bool ignoreFinalizers: false}) async {
|
{bool ignoreFinalizers: false}) {
|
||||||
if (req == null || res == null) {
|
if (req == null || res == null) {
|
||||||
try {
|
try {
|
||||||
app.logger?.severe(e, st);
|
app.logger?.severe(e, st);
|
||||||
request.response
|
request.response
|
||||||
..statusCode = HttpStatus.INTERNAL_SERVER_ERROR
|
..statusCode = HttpStatus.internalServerError
|
||||||
..write('500 Internal Server Error')
|
..write('500 Internal Server Error')
|
||||||
..close();
|
..close();
|
||||||
} finally {
|
} finally {
|
||||||
|
@ -212,12 +239,12 @@ class AngelHttp {
|
||||||
|
|
||||||
if (res.isOpen) {
|
if (res.isOpen) {
|
||||||
res.statusCode = e.statusCode;
|
res.statusCode = e.statusCode;
|
||||||
var result = await app.errorHandler(e, req, res);
|
var result = app.errorHandler(e, req, res);
|
||||||
await app.executeHandler(result, req, res);
|
app.executeHandler(result, req, res);
|
||||||
res.end();
|
res.end();
|
||||||
}
|
}
|
||||||
|
|
||||||
return await sendResponse(request, req, res,
|
return sendResponse(request, req, res,
|
||||||
ignoreFinalizers: ignoreFinalizers == true);
|
ignoreFinalizers: ignoreFinalizers == true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -244,8 +271,12 @@ class AngelHttp {
|
||||||
List<int> outputBuffer = res.buffer.toBytes();
|
List<int> outputBuffer = res.buffer.toBytes();
|
||||||
|
|
||||||
if (res.encoders.isNotEmpty) {
|
if (res.encoders.isNotEmpty) {
|
||||||
var allowedEncodings =
|
var allowedEncodings = req.headers
|
||||||
req.headers[HttpHeaders.ACCEPT_ENCODING]?.map((str) {
|
.value('accept-encoding')
|
||||||
|
?.split(',')
|
||||||
|
?.map((s) => s.trim())
|
||||||
|
?.where((s) => s.isNotEmpty)
|
||||||
|
?.map((str) {
|
||||||
// Ignore quality specifications in accept-encoding
|
// Ignore quality specifications in accept-encoding
|
||||||
// ex. gzip;q=0.8
|
// ex. gzip;q=0.8
|
||||||
if (!str.contains(';')) return str;
|
if (!str.contains(';')) return str;
|
||||||
|
@ -264,7 +295,7 @@ class AngelHttp {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (encoder != null) {
|
if (encoder != null) {
|
||||||
request.response.headers.set(HttpHeaders.CONTENT_ENCODING, key);
|
request.response.headers.set('content-encoding', key);
|
||||||
outputBuffer = res.encoders[key].convert(outputBuffer);
|
outputBuffer = res.encoders[key].convert(outputBuffer);
|
||||||
request.response.contentLength = outputBuffer.length;
|
request.response.contentLength = outputBuffer.length;
|
||||||
break;
|
break;
|
||||||
|
@ -278,31 +309,30 @@ class AngelHttp {
|
||||||
..cookies.addAll(res.cookies)
|
..cookies.addAll(res.cookies)
|
||||||
..add(outputBuffer);
|
..add(outputBuffer);
|
||||||
|
|
||||||
return finalizers.then((_) async {
|
return finalizers.then((_) {
|
||||||
await request.response.flush();
|
return request.response.close().then((_) {
|
||||||
await request.response.close();
|
if (req.injections.containsKey(PoolResource)) {
|
||||||
|
req.injections[PoolResource].release();
|
||||||
if (req.injections.containsKey(PoolResource)) {
|
|
||||||
req.injections[PoolResource].release();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (app.logger != null) {
|
|
||||||
var sw = req.grab<Stopwatch>(Stopwatch);
|
|
||||||
|
|
||||||
if (sw.isRunning) {
|
|
||||||
sw?.stop();
|
|
||||||
app.logger.info("${res.statusCode} ${req.method} ${req.uri} (${sw
|
|
||||||
?.elapsedMilliseconds ?? 'unknown'} ms)");
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
if (app.logger != null) {
|
||||||
|
var sw = req.grab<Stopwatch>(Stopwatch);
|
||||||
|
|
||||||
|
if (sw.isRunning) {
|
||||||
|
sw?.stop();
|
||||||
|
app.logger.info("${res.statusCode} ${req.method} ${req.uri} (${sw
|
||||||
|
?.elapsedMilliseconds ?? 'unknown'} ms)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<HttpRequestContextImpl> createRequestContext(HttpRequest request) {
|
Future<HttpRequestContextImpl> createRequestContext(HttpRequest request) {
|
||||||
var path = request.uri.path.replaceAll(_straySlashes, '');
|
var path = request.uri.path.replaceAll(_straySlashes, '');
|
||||||
if (path.length == 0) path = '/';
|
if (path.length == 0) path = '/';
|
||||||
return HttpRequestContextImpl.from(request, app, path).then((req) async {
|
return HttpRequestContextImpl.from(request, app, path).then((req) {
|
||||||
if (_pool != null) req.inject(PoolResource, await _pool.request());
|
if (_pool != null) req.inject(PoolResource, _pool.request());
|
||||||
if (app.injections.isNotEmpty) app.injections.forEach(req.inject);
|
if (app.injections.isNotEmpty) app.injections.forEach(req.inject);
|
||||||
return req;
|
return req;
|
||||||
});
|
});
|
||||||
|
|
|
@ -27,7 +27,7 @@ class Controller {
|
||||||
Controller({this.debug: false, this.injectSingleton: true});
|
Controller({this.debug: false, this.injectSingleton: true});
|
||||||
|
|
||||||
@mustCallSuper
|
@mustCallSuper
|
||||||
Future configureServer(Angel app) async {
|
Future configureServer(Angel app) {
|
||||||
_app = app;
|
_app = app;
|
||||||
|
|
||||||
if (injectSingleton != false) _app.container.singleton(this);
|
if (injectSingleton != false) _app.container.singleton(this);
|
||||||
|
@ -56,6 +56,7 @@ class Controller {
|
||||||
final routeBuilder = _routeBuilder(instanceMirror, routable, handlers);
|
final routeBuilder = _routeBuilder(instanceMirror, routable, handlers);
|
||||||
classMirror.instanceMembers.forEach(routeBuilder);
|
classMirror.instanceMembers.forEach(routeBuilder);
|
||||||
configureRoutes(routable);
|
configureRoutes(routable);
|
||||||
|
return new Future.value();
|
||||||
}
|
}
|
||||||
|
|
||||||
Function _routeBuilder(
|
Function _routeBuilder(
|
||||||
|
@ -85,9 +86,9 @@ class Controller {
|
||||||
method.parameters[1].type.reflectedType == ResponseContext) {
|
method.parameters[1].type.reflectedType == ResponseContext) {
|
||||||
// Create a regular route
|
// Create a regular route
|
||||||
routeMappings[name] = routable
|
routeMappings[name] = routable
|
||||||
.addRoute(exposeDecl.method, exposeDecl.path, (req, res) async {
|
.addRoute(exposeDecl.method, exposeDecl.path, (req, res) {
|
||||||
var result = await reflectedMethod(req, res);
|
var result = reflectedMethod(req, res);
|
||||||
return result is RequestHandler ? await result(req, res) : result;
|
return result is RequestHandler ? result(req, res) : result;
|
||||||
}, middleware: middleware);
|
}, middleware: middleware);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,10 +14,10 @@ export 'http_response_context.dart';
|
||||||
|
|
||||||
/// Boots a shared server instance. Use this if launching multiple isolates
|
/// Boots a shared server instance. Use this if launching multiple isolates
|
||||||
Future<HttpServer> startShared(address, int port) => HttpServer
|
Future<HttpServer> startShared(address, int port) => HttpServer
|
||||||
.bind(address ?? InternetAddress.LOOPBACK_IP_V4, port ?? 0, shared: true);
|
.bind(address ?? '127.0.0.1', port ?? 0, shared: true);
|
||||||
|
|
||||||
Future<HttpServer> Function(dynamic, int) startSharedSecure(SecurityContext securityContext) {
|
Future<HttpServer> Function(dynamic, int) startSharedSecure(SecurityContext securityContext) {
|
||||||
return (address, int port) => HttpServer.bindSecure(
|
return (address, int port) => HttpServer.bindSecure(
|
||||||
address ?? InternetAddress.LOOPBACK_IP_V4, port ?? 0, securityContext,
|
address ?? '127.0.0.1', port ?? 0, securityContext,
|
||||||
shared: true);
|
shared: true);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'package:body_parser/body_parser.dart';
|
import 'package:body_parser/body_parser.dart';
|
||||||
|
import 'package:http_parser/http_parser.dart';
|
||||||
import '../core/core.dart';
|
import '../core/core.dart';
|
||||||
|
|
||||||
/// An implementation of [RequestContext] that wraps a [HttpRequest].
|
/// An implementation of [RequestContext] that wraps a [HttpRequest].
|
||||||
|
@ -34,7 +35,7 @@ class HttpRequestContextImpl extends RequestContext {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get method {
|
String get method {
|
||||||
return _override ?? originalMethod;
|
return _override ?? originalMethod;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -70,7 +71,7 @@ class HttpRequestContextImpl extends RequestContext {
|
||||||
|
|
||||||
/// Magically transforms an [HttpRequest] into a [RequestContext].
|
/// Magically transforms an [HttpRequest] into a [RequestContext].
|
||||||
static Future<HttpRequestContextImpl> from(
|
static Future<HttpRequestContextImpl> from(
|
||||||
HttpRequest request, Angel app, String path) async {
|
HttpRequest request, Angel app, String path) {
|
||||||
HttpRequestContextImpl ctx = new HttpRequestContextImpl();
|
HttpRequestContextImpl ctx = new HttpRequestContextImpl();
|
||||||
|
|
||||||
String override = request.method;
|
String override = request.method;
|
||||||
|
@ -116,10 +117,10 @@ class HttpRequestContextImpl extends RequestContext {
|
||||||
ctx._io = request;
|
ctx._io = request;
|
||||||
|
|
||||||
if (app.lazyParseBodies != true) {
|
if (app.lazyParseBodies != true) {
|
||||||
await ctx.parse();
|
return ctx.parse().then((_) => ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
return ctx;
|
return new Future.value(ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -131,8 +132,13 @@ class HttpRequestContextImpl extends RequestContext {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<BodyParseResult> parseOnce() async {
|
Future<BodyParseResult> parseOnce() {
|
||||||
return await parseBody(io,
|
return parseBodyFromStream(
|
||||||
|
io,
|
||||||
|
io.headers.contentType != null
|
||||||
|
? new MediaType.parse(io.headers.contentType.toString())
|
||||||
|
: null,
|
||||||
|
io.uri,
|
||||||
storeOriginalBuffer: app.storeOriginalBuffer == true);
|
storeOriginalBuffer: app.storeOriginalBuffer == true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -111,10 +111,10 @@ class HttpResponseContextImpl extends ResponseContext {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future close() async {
|
Future close() {
|
||||||
if (_useStream) {
|
if (_useStream) {
|
||||||
try {
|
try {
|
||||||
await io.close();
|
io.close();
|
||||||
} catch(_) {
|
} catch(_) {
|
||||||
// This only seems to occur on `MockHttpRequest`, but
|
// This only seems to occur on `MockHttpRequest`, but
|
||||||
// this try/catch prevents a crash.
|
// this try/catch prevents a crash.
|
||||||
|
@ -122,7 +122,8 @@ class HttpResponseContextImpl extends ResponseContext {
|
||||||
}
|
}
|
||||||
|
|
||||||
_isClosed = true;
|
_isClosed = true;
|
||||||
await super.close();
|
super.close();
|
||||||
_useStream = false;
|
_useStream = false;
|
||||||
|
return new Future.value();
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -58,10 +58,10 @@ class AngelMetrics extends Angel {
|
||||||
final AngelMetricsStats stats = new AngelMetricsStats._();
|
final AngelMetricsStats stats = new AngelMetricsStats._();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<HttpServer> close() async {
|
Future<HttpServer> close() {
|
||||||
_sub?.cancel();
|
_sub?.cancel();
|
||||||
await _inner.close();
|
_inner.close();
|
||||||
return await super.close();
|
return super.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
|
@ -23,13 +23,14 @@ main() async {
|
||||||
|
|
||||||
void start(int id) {
|
void start(int id) {
|
||||||
var app = new Angel()..lazyParseBodies = true;
|
var app = new Angel()..lazyParseBodies = true;
|
||||||
var http = new AngelHttp.custom(app, startShared);
|
var http = new AngelHttp.custom(app, startShared, useZone: false);
|
||||||
|
|
||||||
app.get('/', (req, ResponseContext res) {
|
app.get('/', (req, ResponseContext res) {
|
||||||
res.willCloseItself = true;
|
res.write('Hello, world!');
|
||||||
res.io
|
//res.willCloseItself = true;
|
||||||
..write('Hello, world!')
|
//res.io
|
||||||
..close();
|
// ..write('Hello, world!')
|
||||||
|
// ..close();
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -41,7 +42,7 @@ void start(int id) {
|
||||||
return oldHandler(e, req, res);
|
return oldHandler(e, req, res);
|
||||||
};
|
};
|
||||||
|
|
||||||
http.startServer(InternetAddress.LOOPBACK_IP_V4, 3000).then((server) {
|
http.startServer('127.0.0.1', 3000).then((server) {
|
||||||
print(
|
print(
|
||||||
'Instance #$id listening at http://${server.address.address}:${server.port}');
|
'Instance #$id listening at http://${server.address.address}:${server.port}');
|
||||||
});
|
});
|
||||||
|
|
|
@ -12,7 +12,7 @@ main() {
|
||||||
|
|
||||||
void start(int id) {
|
void start(int id) {
|
||||||
HttpServer
|
HttpServer
|
||||||
.bind(InternetAddress.LOOPBACK_IP_V4, 3000, shared: true)
|
.bind('127.0.0.1', 3000, shared: true)
|
||||||
.then((server) {
|
.then((server) {
|
||||||
print(
|
print(
|
||||||
'Instance #$id listening at http://${server.address.address}:${server.port}');
|
'Instance #$id listening at http://${server.address.address}:${server.port}');
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
name: angel_framework
|
name: angel_framework
|
||||||
version: 1.1.3
|
version: 1.1.4
|
||||||
description: A high-powered HTTP server with DI, routing and more.
|
description: A high-powered HTTP server with DI, routing and more.
|
||||||
author: Tobe O <thosakwe@gmail.com>
|
author: Tobe O <thosakwe@gmail.com>
|
||||||
homepage: https://github.com/angel-dart/angel_framework
|
homepage: https://github.com/angel-dart/angel_framework
|
||||||
|
@ -14,6 +14,7 @@ dependencies:
|
||||||
combinator: ">=1.0.0-beta <2.0.0"
|
combinator: ">=1.0.0-beta <2.0.0"
|
||||||
container: ^0.1.2
|
container: ^0.1.2
|
||||||
dart2_constant: ^1.0.0
|
dart2_constant: ^1.0.0
|
||||||
|
http_parser: ^3.0.0
|
||||||
json_god: ^2.0.0-beta
|
json_god: ^2.0.0-beta
|
||||||
logging: ">=0.11.3 <1.0.0"
|
logging: ">=0.11.3 <1.0.0"
|
||||||
matcher: ^0.12.0
|
matcher: ^0.12.0
|
||||||
|
@ -23,6 +24,7 @@ dependencies:
|
||||||
path: ">=1.0.0 <2.0.0"
|
path: ">=1.0.0 <2.0.0"
|
||||||
pool: ^1.0.0
|
pool: ^1.0.0
|
||||||
random_string: ^0.0.1
|
random_string: ^0.0.1
|
||||||
|
stack_trace: ^1.0.0
|
||||||
tuple: ^1.0.0
|
tuple: ^1.0.0
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
mock_request: ^1.0.0
|
mock_request: ^1.0.0
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:io';
|
import 'dart:io' show BytesBuilder;
|
||||||
import 'package:angel_framework/angel_framework.dart';
|
import 'package:angel_framework/angel_framework.dart';
|
||||||
import 'package:dart2_constant/convert.dart';
|
import 'package:dart2_constant/convert.dart';
|
||||||
|
import 'package:dart2_constant/io.dart';
|
||||||
import 'package:mock_request/mock_request.dart';
|
import 'package:mock_request/mock_request.dart';
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
@ -19,8 +20,8 @@ main() {
|
||||||
app = new Angel();
|
app = new Angel();
|
||||||
app.injectEncoders(
|
app.injectEncoders(
|
||||||
{
|
{
|
||||||
'deflate': ZLIB.encoder,
|
'deflate': zlib.encoder,
|
||||||
'gzip': GZIP.encoder,
|
'gzip': gzip.encoder,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -55,50 +56,50 @@ void encodingTests(Angel getApp()) {
|
||||||
|
|
||||||
test('encodes if wildcard', () async {
|
test('encodes if wildcard', () async {
|
||||||
var rq = new MockHttpRequest('GET', Uri.parse('/hello'))
|
var rq = new MockHttpRequest('GET', Uri.parse('/hello'))
|
||||||
..headers.set(HttpHeaders.ACCEPT_ENCODING, '*')
|
..headers.set('accept-encoding', '*')
|
||||||
..close();
|
..close();
|
||||||
var rs = rq.response;
|
var rs = rq.response;
|
||||||
await http.handleRequest(rq);
|
await http.handleRequest(rq);
|
||||||
|
|
||||||
var body = await getBody(rs);
|
var body = await getBody(rs);
|
||||||
expect(rs.headers.value(HttpHeaders.CONTENT_ENCODING), 'deflate');
|
expect(rs.headers.value('content-encoding'), 'deflate');
|
||||||
expect(body, ZLIB.encode(utf8.encode('Hello, world!')));
|
expect(body, zlib.encode(utf8.encode('Hello, world!')));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('encodes if wildcard + multiple', () async {
|
test('encodes if wildcard + multiple', () async {
|
||||||
var rq = new MockHttpRequest('GET', Uri.parse('/hello'))
|
var rq = new MockHttpRequest('GET', Uri.parse('/hello'))
|
||||||
..headers.set(HttpHeaders.ACCEPT_ENCODING, ['foo', 'bar', '*'])
|
..headers.set('accept-encoding', ['foo', 'bar', '*'])
|
||||||
..close();
|
..close();
|
||||||
var rs = rq.response;
|
var rs = rq.response;
|
||||||
await http.handleRequest(rq);
|
await http.handleRequest(rq);
|
||||||
|
|
||||||
var body = await getBody(rs);
|
var body = await getBody(rs);
|
||||||
expect(rs.headers.value(HttpHeaders.CONTENT_ENCODING), 'deflate');
|
expect(rs.headers.value('content-encoding'), 'deflate');
|
||||||
expect(body, ZLIB.encode(utf8.encode('Hello, world!')));
|
expect(body, zlib.encode(utf8.encode('Hello, world!')));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('encodes if explicit', () async {
|
test('encodes if explicit', () async {
|
||||||
var rq = new MockHttpRequest('GET', Uri.parse('/hello'))
|
var rq = new MockHttpRequest('GET', Uri.parse('/hello'))
|
||||||
..headers.set(HttpHeaders.ACCEPT_ENCODING, 'gzip');
|
..headers.set('accept-encoding', 'gzip');
|
||||||
await rq.close();
|
await rq.close();
|
||||||
var rs = rq.response;
|
var rs = rq.response;
|
||||||
await http.handleRequest(rq);
|
await http.handleRequest(rq);
|
||||||
|
|
||||||
var body = await getBody(rs);
|
var body = await getBody(rs);
|
||||||
expect(rs.headers.value(HttpHeaders.CONTENT_ENCODING), 'gzip');
|
expect(rs.headers.value('content-encoding'), 'gzip');
|
||||||
expect(body, GZIP.encode(utf8.encode('Hello, world!')));
|
expect(body, gzip.encode(utf8.encode('Hello, world!')));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('only uses one encoder', () async {
|
test('only uses one encoder', () async {
|
||||||
var rq = new MockHttpRequest('GET', Uri.parse('/hello'))
|
var rq = new MockHttpRequest('GET', Uri.parse('/hello'))
|
||||||
..headers.set(HttpHeaders.ACCEPT_ENCODING, ['gzip', 'deflate']);
|
..headers.set('accept-encoding', ['gzip', 'deflate']);
|
||||||
await rq.close();
|
await rq.close();
|
||||||
var rs = rq.response;
|
var rs = rq.response;
|
||||||
await http.handleRequest(rq);
|
await http.handleRequest(rq);
|
||||||
|
|
||||||
var body = await getBody(rs);
|
var body = await getBody(rs);
|
||||||
expect(rs.headers.value(HttpHeaders.CONTENT_ENCODING), 'gzip');
|
expect(rs.headers.value('content-encoding'), 'gzip');
|
||||||
expect(body, GZIP.encode(utf8.encode('Hello, world!')));
|
expect(body, gzip.encode(utf8.encode('Hello, world!')));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
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:dart2_constant/convert.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() {
|
||||||
|
@ -41,8 +40,8 @@ main() {
|
||||||
test('toMap = toJson', () {
|
test('toMap = toJson', () {
|
||||||
var exc = new AngelHttpException.badRequest();
|
var exc = new AngelHttpException.badRequest();
|
||||||
expect(exc.toMap(), exc.toJson());
|
expect(exc.toMap(), exc.toJson());
|
||||||
var json = JSON.encode(exc.toJson());
|
var json_ = json.encode(exc.toJson());
|
||||||
var exc2 = new AngelHttpException.fromJson(json);
|
var exc2 = new AngelHttpException.fromJson(json_);
|
||||||
expect(exc2.toJson(), exc.toJson());
|
expect(exc2.toJson(), exc.toJson());
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -68,7 +67,9 @@ 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(@checked AngelHttpException item, Map matchState) {
|
bool matches(item, Map matchState) {
|
||||||
return item.statusCode == statusCode && item.message == message;
|
return item is AngelHttpException &&
|
||||||
|
item.statusCode == statusCode &&
|
||||||
|
item.message == message;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
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:dart2_constant/convert.dart';
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
@ -32,6 +32,6 @@ main() {
|
||||||
var response = await client
|
var response = await client
|
||||||
.get('$url/foo', headers: {'X-HTTP-Method-Override': 'POST'});
|
.get('$url/foo', headers: {'X-HTTP-Method-Override': 'POST'});
|
||||||
print('Response: ${response.body}');
|
print('Response: ${response.body}');
|
||||||
expect(JSON.decode(response.body), equals({'hello': 'world'}));
|
expect(json.decode(response.body), equals({'hello': 'world'}));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,12 +51,13 @@ main() {
|
||||||
return 'DEFAULT $mode';
|
return 'DEFAULT $mode';
|
||||||
});
|
});
|
||||||
|
|
||||||
app.logger = new Logger('parameter_meta_test')
|
/*app.logger = new Logger('parameter_meta_test')
|
||||||
..onRecord.listen((rec) {
|
..onRecord.listen((rec) {
|
||||||
print(rec);
|
print(rec);
|
||||||
if (rec.error != null) print(rec.error);
|
if (rec.error != null) print(rec.error);
|
||||||
if (rec.stackTrace != null) print(rec.stackTrace);
|
if (rec.stackTrace != null) print(rec.stackTrace);
|
||||||
});
|
});
|
||||||
|
*/
|
||||||
});
|
});
|
||||||
|
|
||||||
test('injects header or throws', () async {
|
test('injects header or throws', () async {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import 'dart:convert';
|
|
||||||
import 'package:angel_framework/angel_framework.dart';
|
import 'package:angel_framework/angel_framework.dart';
|
||||||
|
import 'package:dart2_constant/convert.dart';
|
||||||
import 'package:mock_request/mock_request.dart';
|
import 'package:mock_request/mock_request.dart';
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
@ -16,8 +16,8 @@ main() {
|
||||||
rq.close();
|
rq.close();
|
||||||
await new AngelHttp(app).handleRequest(rq);
|
await new AngelHttp(app).handleRequest(rq);
|
||||||
var rs = rq.response;
|
var rs = rq.response;
|
||||||
var body = await rs.transform(UTF8.decoder).join();
|
var body = await rs.transform(utf8.decoder).join();
|
||||||
expect(body, JSON.encode('bar'));
|
expect(body, json.encode('bar'));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import 'dart:convert';
|
import 'dart:io' show stderr;
|
||||||
import 'dart:io';
|
|
||||||
import 'package:angel_framework/angel_framework.dart';
|
import 'package:angel_framework/angel_framework.dart';
|
||||||
|
import 'package:dart2_constant/convert.dart';
|
||||||
import 'package:mock_request/mock_request.dart';
|
import 'package:mock_request/mock_request.dart';
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
@ -36,22 +36,22 @@ main() {
|
||||||
test('String type annotation', () async {
|
test('String type annotation', () async {
|
||||||
var rq = new MockHttpRequest('GET', Uri.parse('/string/hello'))..close();
|
var rq = new MockHttpRequest('GET', Uri.parse('/string/hello'))..close();
|
||||||
await http.handleRequest(rq);
|
await http.handleRequest(rq);
|
||||||
var rs = await rq.response.transform(UTF8.decoder).join();
|
var rs = await rq.response.transform(utf8.decoder).join();
|
||||||
expect(rs, JSON.encode('hello'));
|
expect(rs, json.encode('hello'));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Primitive after parsed param injection', () async {
|
test('Primitive after parsed param injection', () async {
|
||||||
var rq = new MockHttpRequest('GET', Uri.parse('/num/parsed/24'))..close();
|
var rq = new MockHttpRequest('GET', Uri.parse('/num/parsed/24'))..close();
|
||||||
await http.handleRequest(rq);
|
await http.handleRequest(rq);
|
||||||
var rs = await rq.response.transform(UTF8.decoder).join();
|
var rs = await rq.response.transform(utf8.decoder).join();
|
||||||
expect(rs, JSON.encode(24));
|
expect(rs, json.encode(24));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('globally-injected primitive', () async {
|
test('globally-injected primitive', () async {
|
||||||
var rq = new MockHttpRequest('GET', Uri.parse('/num/global'))..close();
|
var rq = new MockHttpRequest('GET', Uri.parse('/num/global'))..close();
|
||||||
await http.handleRequest(rq);
|
await http.handleRequest(rq);
|
||||||
var rs = await rq.response.transform(UTF8.decoder).join();
|
var rs = await rq.response.transform(utf8.decoder).join();
|
||||||
expect(rs, JSON.encode(305));
|
expect(rs, json.encode(305));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('unparsed primitive throws error', () async {
|
test('unparsed primitive throws error', () async {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
|
||||||
import 'package:angel_framework/angel_framework.dart';
|
import 'package:angel_framework/angel_framework.dart';
|
||||||
|
import 'package:dart2_constant/convert.dart';
|
||||||
import 'package:mock_request/mock_request.dart';
|
import 'package:mock_request/mock_request.dart';
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
@ -13,9 +13,9 @@ main() {
|
||||||
var app = new Angel()..get('/test/:id', (id) => 'Hello $id');
|
var app = new Angel()..get('/test/:id', (id) => 'Hello $id');
|
||||||
var rq1 = mk(1), rq2 = mk(2), rq3 = mk(1);
|
var rq1 = mk(1), rq2 = mk(2), rq3 = mk(1);
|
||||||
await Future.wait([rq1, rq2, rq3].map(new AngelHttp(app).handleRequest));
|
await Future.wait([rq1, rq2, rq3].map(new AngelHttp(app).handleRequest));
|
||||||
var body1 = await rq1.response.transform(UTF8.decoder).join(),
|
var body1 = await rq1.response.transform(utf8.decoder).join(),
|
||||||
body2 = await rq2.response.transform(UTF8.decoder).join(),
|
body2 = await rq2.response.transform(utf8.decoder).join(),
|
||||||
body3 = await rq3.response.transform(UTF8.decoder).join();
|
body3 = await rq3.response.transform(utf8.decoder).join();
|
||||||
print('Response #1: $body1');
|
print('Response #1: $body1');
|
||||||
print('Response #2: $body2');
|
print('Response #2: $body2');
|
||||||
print('Response #3: $body3');
|
print('Response #3: $body3');
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import 'dart:convert';
|
|
||||||
import 'dart:io';
|
|
||||||
import 'package:angel_framework/angel_framework.dart';
|
import 'package:angel_framework/angel_framework.dart';
|
||||||
|
import 'package:dart2_constant/convert.dart';
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
@ -96,7 +95,7 @@ main() {
|
||||||
|
|
||||||
client = new http.Client();
|
client = new http.Client();
|
||||||
var server =
|
var server =
|
||||||
await new AngelHttp(app).startServer(InternetAddress.LOOPBACK_IP_V4, 0);
|
await new AngelHttp(app).startServer('127.0.0.1', 0);
|
||||||
url = "http://${server.address.host}:${server.port}";
|
url = "http://${server.address.host}:${server.port}";
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -118,10 +117,10 @@ main() {
|
||||||
test('Can match url with multiple parameters', () async {
|
test('Can match url with multiple parameters', () async {
|
||||||
var response = await client.get('$url/name/HELLO/last/WORLD');
|
var response = await client.get('$url/name/HELLO/last/WORLD');
|
||||||
print('Response: ${response.body}');
|
print('Response: ${response.body}');
|
||||||
var json = JSON.decode(response.body);
|
var json_ = json.decode(response.body);
|
||||||
expect(json, new isInstanceOf<Map<String, String>>());
|
expect(json_, new isInstanceOf<Map<String, String>>());
|
||||||
expect(json['first'], equals('HELLO'));
|
expect(json_['first'], equals('HELLO'));
|
||||||
expect(json['last'], equals('WORLD'));
|
expect(json_['last'], equals('WORLD'));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Chained routes', () async {
|
test('Chained routes', () async {
|
||||||
|
@ -131,16 +130,16 @@ main() {
|
||||||
|
|
||||||
test('Can nest another Angel instance', () async {
|
test('Can nest another Angel instance', () async {
|
||||||
var response = await client.post('$url/nes/ted/foo');
|
var response = await client.post('$url/nes/ted/foo');
|
||||||
var json = JSON.decode(response.body);
|
var json_ = json.decode(response.body);
|
||||||
expect(json['route'], equals('foo'));
|
expect(json_['route'], equals('foo'));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Can parse parameters from a nested Angel instance', () async {
|
test('Can parse parameters from a nested Angel instance', () async {
|
||||||
var response = await client.get('$url/todos/1337/action/test');
|
var response = await client.get('$url/todos/1337/action/test');
|
||||||
var json = JSON.decode(response.body);
|
var json_ = json.decode(response.body);
|
||||||
print('JSON: $json');
|
print('JSON: $json_');
|
||||||
expect(json['id'], equals('1337'));
|
expect(json_['id'], equals('1337'));
|
||||||
expect(json['action'], equals('test'));
|
expect(json_['action'], equals('test'));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Can add and use named middleware', () async {
|
test('Can add and use named middleware', () async {
|
||||||
|
@ -156,10 +155,11 @@ main() {
|
||||||
|
|
||||||
test('Can serialize function result as JSON', () async {
|
test('Can serialize function result as JSON', () async {
|
||||||
Map headers = {'Content-Type': 'application/json'};
|
Map headers = {'Content-Type': 'application/json'};
|
||||||
String postData = JSON.encode({'it': 'works'});
|
String postData = json.encode({'it': 'works'});
|
||||||
var response =
|
var response =
|
||||||
await client.post("$url/lambda", headers: headers, body: postData);
|
await client.post("$url/lambda", headers: headers, body: postData);
|
||||||
expect(JSON.decode(response.body)['it'], equals('works'));
|
print('Response: ${response.body}');
|
||||||
|
expect(json.decode(response.body)['it'], equals('works'));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Fallback routes', () async {
|
test('Fallback routes', () async {
|
||||||
|
@ -178,14 +178,14 @@ main() {
|
||||||
test('Redirect to named routes', () async {
|
test('Redirect to named routes', () async {
|
||||||
var response = await client.get('$url/named');
|
var response = await client.get('$url/named');
|
||||||
print(response.body);
|
print(response.body);
|
||||||
expect(JSON.decode(response.body), equals('Hello tests'));
|
expect(json.decode(response.body), equals('Hello tests'));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Match routes, even with query params', () async {
|
test('Match routes, even with query params', () async {
|
||||||
var response =
|
var response =
|
||||||
await client.get("$url/log?foo=bar&bar=baz&baz.foo=bar&baz.bar=foo");
|
await client.get("$url/log?foo=bar&bar=baz&baz.foo=bar&baz.bar=foo");
|
||||||
print(response.body);
|
print(response.body);
|
||||||
expect(JSON.decode(response.body), equals('Logged'));
|
expect(json.decode(response.body), equals('Logged'));
|
||||||
|
|
||||||
response = await client.get("$url/query/foo?bar=baz");
|
response = await client.get("$url/query/foo?bar=baz");
|
||||||
print(response.body);
|
print(response.body);
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
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:dart2_constant/convert.dart';
|
||||||
import 'package:matcher/matcher.dart';
|
import 'package:matcher/matcher.dart';
|
||||||
import 'package:mock_request/mock_request.dart';
|
import 'package:mock_request/mock_request.dart';
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
|
@ -51,7 +51,7 @@ main() {
|
||||||
ContentType.HTML.mimeType,
|
ContentType.HTML.mimeType,
|
||||||
);
|
);
|
||||||
expect(rs.statusCode, e.statusCode);
|
expect(rs.statusCode, e.statusCode);
|
||||||
var body = await rs.transform(UTF8.decoder).join();
|
var body = await rs.transform(utf8.decoder).join();
|
||||||
expect(body, contains('<title>${e.message}</title>'));
|
expect(body, contains('<title>${e.message}</title>'));
|
||||||
expect(body, contains('<li>foo</li>'));
|
expect(body, contains('<li>foo</li>'));
|
||||||
expect(body, contains('<li>bar</li>'));
|
expect(body, contains('<li>bar</li>'));
|
||||||
|
@ -59,9 +59,7 @@ main() {
|
||||||
|
|
||||||
test('plug-ins run on startup', () async {
|
test('plug-ins run on startup', () async {
|
||||||
var app = new Angel();
|
var app = new Angel();
|
||||||
app.startupHooks.add((app) async {
|
app.startupHooks.add((app) => app.configuration['two'] = 2);
|
||||||
app.configuration['two'] = 2;
|
|
||||||
});
|
|
||||||
|
|
||||||
var http = new AngelHttp(app);
|
var http = new AngelHttp(app);
|
||||||
await http.startServer();
|
await http.startServer();
|
||||||
|
@ -92,8 +90,8 @@ main() {
|
||||||
app.get('/', (String a) => a);
|
app.get('/', (String a) => a);
|
||||||
var rq = new MockHttpRequest('GET', Uri.parse('/'))..close();
|
var rq = new MockHttpRequest('GET', Uri.parse('/'))..close();
|
||||||
await http.handleRequest(rq);
|
await http.handleRequest(rq);
|
||||||
var body = await rq.response.transform(UTF8.decoder).join();
|
var body = await rq.response.transform(utf8.decoder).join();
|
||||||
expect(body, JSON.encode('b'));
|
expect(body, json.encode('b'));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('global injected serializer', () async {
|
test('global injected serializer', () async {
|
||||||
|
@ -102,7 +100,7 @@ main() {
|
||||||
app.get($foo.path, (req, ResponseContext res) => res.serialize(null));
|
app.get($foo.path, (req, ResponseContext res) => res.serialize(null));
|
||||||
var rq = new MockHttpRequest('GET', $foo)..close();
|
var rq = new MockHttpRequest('GET', $foo)..close();
|
||||||
await http.handleRequest(rq);
|
await http.handleRequest(rq);
|
||||||
var body = await rq.response.transform(UTF8.decoder).join();
|
var body = await rq.response.transform(utf8.decoder).join();
|
||||||
expect(body, 'x');
|
expect(body, 'x');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -157,7 +155,7 @@ main() {
|
||||||
app.get('/wtf', () => throw new AngelHttpException.forbidden());
|
app.get('/wtf', () => throw new AngelHttpException.forbidden());
|
||||||
app.get('/wtf2', () => throw new AngelHttpException.forbidden());
|
app.get('/wtf2', () => throw new AngelHttpException.forbidden());
|
||||||
http = new AngelHttp(app);
|
http = new AngelHttp(app);
|
||||||
await http.startServer(InternetAddress.LOOPBACK_IP_V4, 0);
|
await http.startServer('127.0.0.1', 0);
|
||||||
|
|
||||||
var oldHandler = app.errorHandler;
|
var oldHandler = app.errorHandler;
|
||||||
app.errorHandler = (e, req, res) {
|
app.errorHandler = (e, req, res) {
|
||||||
|
@ -208,9 +206,9 @@ class CustomCloseService extends Service {
|
||||||
int value = 2;
|
int value = 2;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future close() {
|
void close() {
|
||||||
value = 3;
|
value = 3;
|
||||||
return super.close();
|
super.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
import 'dart:convert';
|
|
||||||
import 'package:angel_framework/angel_framework.dart';
|
import 'package:angel_framework/angel_framework.dart';
|
||||||
|
import 'package:dart2_constant/convert.dart';
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
import 'package:json_god/json_god.dart' as god;
|
import 'package:json_god/json_god.dart' as god;
|
||||||
|
import 'package:stack_trace/stack_trace.dart';
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
class Todo extends Model {
|
class Todo extends Model {
|
||||||
|
@ -23,7 +24,7 @@ main() {
|
||||||
..use('/todos', new TypedService<Todo>(new MapService()))
|
..use('/todos', new TypedService<Todo>(new MapService()))
|
||||||
..errorHandler = (e, req, res) {
|
..errorHandler = (e, req, res) {
|
||||||
print('Whoops: ${e.error}');
|
print('Whoops: ${e.error}');
|
||||||
print(e.stackTrace);
|
if (e.stackTrace != null) print(new Chain.forTrace(e.stackTrace).terse);
|
||||||
};
|
};
|
||||||
|
|
||||||
var server = await new AngelHttp(app).startServer();
|
var server = await new AngelHttp(app).startServer();
|
||||||
|
@ -45,7 +46,7 @@ main() {
|
||||||
print(response.body);
|
print(response.body);
|
||||||
expect(response.body, equals('[]'));
|
expect(response.body, equals('[]'));
|
||||||
print(response.body);
|
print(response.body);
|
||||||
expect(JSON.decode(response.body).length, 0);
|
expect(json.decode(response.body).length, 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('can create data', () async {
|
test('can create data', () async {
|
||||||
|
@ -97,12 +98,12 @@ main() {
|
||||||
String postData = god.serialize({'text': 'Hello, world!'});
|
String postData = god.serialize({'text': 'Hello, world!'});
|
||||||
var created = await client
|
var created = await client
|
||||||
.post("$url/todos", headers: headers, body: postData)
|
.post("$url/todos", headers: headers, body: postData)
|
||||||
.then((r) => JSON.decode(r.body));
|
.then((r) => json.decode(r.body));
|
||||||
var response = await client.delete("$url/todos/${created['id']}");
|
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!'));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:io' show stderr;
|
||||||
import 'dart:io';
|
|
||||||
import 'package:angel_framework/angel_framework.dart';
|
import 'package:angel_framework/angel_framework.dart';
|
||||||
|
import 'package:dart2_constant/convert.dart';
|
||||||
|
import 'package:dart2_constant/io.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:mock_request/mock_request.dart';
|
import 'package:mock_request/mock_request.dart';
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
|
@ -23,8 +24,8 @@ main() {
|
||||||
|
|
||||||
app.injectEncoders(
|
app.injectEncoders(
|
||||||
{
|
{
|
||||||
'deflate': ZLIB.encoder,
|
'deflate': zlib.encoder,
|
||||||
'gzip': GZIP.encoder,
|
'gzip': gzip.encoder,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -73,7 +74,7 @@ main() {
|
||||||
_expectHelloBye(String path) async {
|
_expectHelloBye(String path) async {
|
||||||
var rq = new MockHttpRequest('GET', Uri.parse(path))..close();
|
var rq = new MockHttpRequest('GET', Uri.parse(path))..close();
|
||||||
await http.handleRequest(rq);
|
await http.handleRequest(rq);
|
||||||
var body = await rq.response.transform(UTF8.decoder).join();
|
var body = await rq.response.transform(utf8.decoder).join();
|
||||||
expect(body, 'Hello, world!bye');
|
expect(body, 'Hello, world!bye');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,7 +85,7 @@ main() {
|
||||||
test('cannot write after close', () async {
|
test('cannot write after close', () async {
|
||||||
var rq = new MockHttpRequest('GET', Uri.parse('/overwrite'))..close();
|
var rq = new MockHttpRequest('GET', Uri.parse('/overwrite'))..close();
|
||||||
await http.handleRequest(rq);
|
await http.handleRequest(rq);
|
||||||
var body = await rq.response.transform(UTF8.decoder).join();
|
var body = await rq.response.transform(utf8.decoder).join();
|
||||||
|
|
||||||
if (rq.response.statusCode != 32)
|
if (rq.response.statusCode != 32)
|
||||||
throw 'overwrite should throw error; response: $body';
|
throw 'overwrite should throw error; response: $body';
|
||||||
|
@ -94,7 +95,7 @@ main() {
|
||||||
try {
|
try {
|
||||||
var rq = new MockHttpRequest('GET', Uri.parse('/error'))..close();
|
var rq = new MockHttpRequest('GET', Uri.parse('/error'))..close();
|
||||||
await http.handleRequest(rq);
|
await http.handleRequest(rq);
|
||||||
var body = await rq.response.transform(UTF8.decoder).join();
|
var body = await rq.response.transform(utf8.decoder).join();
|
||||||
throw 'addError should throw error; response: $body';
|
throw 'addError should throw error; response: $body';
|
||||||
} on StateError {
|
} on StateError {
|
||||||
// Should throw error...
|
// Should throw error...
|
||||||
|
|
Loading…
Reference in a new issue