All tests pass
This commit is contained in:
parent
4017bc1682
commit
9dd355a4c5
27 changed files with 640 additions and 589 deletions
|
@ -12,7 +12,7 @@
|
||||||
<entry key="angel_container">
|
<entry key="angel_container">
|
||||||
<value>
|
<value>
|
||||||
<list>
|
<list>
|
||||||
<option value="$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/angel_container-1.0.0-alpha.7/lib" />
|
<option value="$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/angel_container-1.0.0-alpha.8/lib" />
|
||||||
</list>
|
</list>
|
||||||
</value>
|
</value>
|
||||||
</entry>
|
</entry>
|
||||||
|
@ -454,7 +454,7 @@
|
||||||
</properties>
|
</properties>
|
||||||
<CLASSES>
|
<CLASSES>
|
||||||
<root url="file://$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/analyzer-0.32.4/lib" />
|
<root url="file://$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/analyzer-0.32.4/lib" />
|
||||||
<root url="file://$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/angel_container-1.0.0-alpha.7/lib" />
|
<root url="file://$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/angel_container-1.0.0-alpha.8/lib" />
|
||||||
<root url="file://$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/angel_http_exception-1.0.0+3/lib" />
|
<root url="file://$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/angel_http_exception-1.0.0+3/lib" />
|
||||||
<root url="file://$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/angel_model-1.0.0+1/lib" />
|
<root url="file://$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/angel_model-1.0.0+1/lib" />
|
||||||
<root url="file://$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/angel_route-3.0.0/lib" />
|
<root url="file://$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/angel_route-3.0.0/lib" />
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="Middleware via metadata 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="Middleware via metadata" />
|
||||||
|
<option name="testRunnerOptions" value="-j 4" />
|
||||||
|
<method />
|
||||||
|
</configuration>
|
||||||
|
</component>
|
|
@ -0,0 +1,9 @@
|
||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="encoding in streaming_test.dart" type="DartTestRunConfigurationType" factoryName="Dart Test" singleton="true">
|
||||||
|
<option name="filePath" value="$PROJECT_DIR$/test/streaming_test.dart" />
|
||||||
|
<option name="scope" value="GROUP_OR_TEST_BY_NAME" />
|
||||||
|
<option name="testName" value="encoding" />
|
||||||
|
<option name="testRunnerOptions" value="-j 4" />
|
||||||
|
<method />
|
||||||
|
</configuration>
|
||||||
|
</component>
|
9
.idea/runConfigurations/metadata_in_hooked_test_dart.xml
Normal file
9
.idea/runConfigurations/metadata_in_hooked_test_dart.xml
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="metadata in hooked_test.dart" type="DartTestRunConfigurationType" factoryName="Dart Test" singleton="true" nameIsGenerated="true">
|
||||||
|
<option name="filePath" value="$PROJECT_DIR$/test/hooked_test.dart" />
|
||||||
|
<option name="scope" value="GROUP_OR_TEST_BY_NAME" />
|
||||||
|
<option name="testName" value="metadata" />
|
||||||
|
<option name="testRunnerOptions" value="-j 4" />
|
||||||
|
<method />
|
||||||
|
</configuration>
|
||||||
|
</component>
|
|
@ -1,5 +1,5 @@
|
||||||
<component name="ProjectRunConfigurationManager">
|
<component name="ProjectRunConfigurationManager">
|
||||||
<configuration default="false" name="All Tests (PRODUCTION)" type="DartTestRunConfigurationType" factoryName="Dart Test" singleton="true">
|
<configuration default="false" name="tests in framework (PRODUCTION)" type="DartTestRunConfigurationType" factoryName="Dart Test" singleton="true">
|
||||||
<option name="envs">
|
<option name="envs">
|
||||||
<entry key="ANGEL_ENV" value="production" />
|
<entry key="ANGEL_ENV" value="production" />
|
||||||
</option>
|
</option>
|
File diff suppressed because it is too large
Load diff
|
@ -7,7 +7,6 @@
|
||||||
* Changed view engine to use `Map<String, dynamic>`.
|
* Changed view engine to use `Map<String, dynamic>`.
|
||||||
* Remove dependency on `package:json_god` by default.
|
* Remove dependency on `package:json_god` by default.
|
||||||
* Remove dependency on `package:dart2_constant`.
|
* Remove dependency on `package:dart2_constant`.
|
||||||
* Remove `contentType` argument in `ResponseContext.serialize`.
|
|
||||||
* Moved `lib/hooks.dart` into `package:angel_hooks`.
|
* Moved `lib/hooks.dart` into `package:angel_hooks`.
|
||||||
* Moved `TypedService` into `package:angel_typed_service`.
|
* Moved `TypedService` into `package:angel_typed_service`.
|
||||||
* Completely removed the `AngelBase` class.
|
* Completely removed the `AngelBase` class.
|
||||||
|
@ -47,8 +46,6 @@ type-safe manner.
|
||||||
* Removed `RequestContext.properties`.
|
* Removed `RequestContext.properties`.
|
||||||
* Removed the defunct `debug` property where it still existed.
|
* Removed the defunct `debug` property where it still existed.
|
||||||
* `Routable.use` now only accepts a `Service`.
|
* `Routable.use` now only accepts a `Service`.
|
||||||
* The above change removes the concept of "nested apps," which are messy to maintain, and
|
|
||||||
not very elegant.
|
|
||||||
* Removed `Angel.createZoneForRequest`.
|
* Removed `Angel.createZoneForRequest`.
|
||||||
* Removed `Angel.defaultZoneCreator`.
|
* Removed `Angel.defaultZoneCreator`.
|
||||||
* Added all flags to the `Angel` constructor, ex. `Angel.eagerParseBodies`.
|
* Added all flags to the `Angel` constructor, ex. `Angel.eagerParseBodies`.
|
||||||
|
@ -65,3 +62,8 @@ as in many cases it is unnecessary and slows down response time.
|
||||||
* `preInject` now takes a `Reflector` as its second argument.
|
* `preInject` now takes a `Reflector` as its second argument.
|
||||||
* `Angel.reflector` defaults to `const EmptyReflector()`, disabling
|
* `Angel.reflector` defaults to `const EmptyReflector()`, disabling
|
||||||
reflection out-of-the-box.
|
reflection out-of-the-box.
|
||||||
|
* Removed `Angel.injectEncoders`.
|
||||||
|
* Added `Providers.toJson`.
|
||||||
|
* Moved `Providers.graphql` to `Providers.graphQL`.
|
||||||
|
* `Angel.optimizeForProduction` no longer calls `preInject`,
|
||||||
|
as it does not need to.
|
25
README.md
25
README.md
|
@ -9,25 +9,40 @@ This is the core of the [Angel](https://github.com/angel-dart/angel) framework.
|
||||||
To build real-world applications, please see the [homepage](https://angel-dart.github.io).
|
To build real-world applications, please see the [homepage](https://angel-dart.github.io).
|
||||||
|
|
||||||
```dart
|
```dart
|
||||||
|
import 'package:angel_container/mirrors.dart';
|
||||||
import 'package:angel_framework/angel_framework.dart';
|
import 'package:angel_framework/angel_framework.dart';
|
||||||
|
|
||||||
main() async {
|
main() async {
|
||||||
var app = new Angel(reflector: MirrorsReflector());
|
var app = new Angel(reflector: MirrorsReflector());
|
||||||
|
|
||||||
// Index route. Returns JSON.
|
// Index route. Returns JSON.
|
||||||
app.get('/', () => 'Welcome to Angel!');
|
app.get('/', (req, res) => res.write('Welcome to Angel!'));
|
||||||
|
|
||||||
// Accepts a URL like /greet/foo or /greet/bob.
|
// Accepts a URL like /greet/foo or /greet/bob.
|
||||||
app.get('/greet/:name', (String name) => 'Hello, $name!');
|
app.get(
|
||||||
|
'/greet/:name',
|
||||||
|
(req, res) {
|
||||||
|
var name = req.params['name'];
|
||||||
|
res
|
||||||
|
..write('Hello, $name!')
|
||||||
|
..close();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
// Pattern matching - only call this handler if the query value of `name` equals 'emoji'.
|
// Pattern matching - only call this handler if the query value of `name` equals 'emoji'.
|
||||||
app.get('/greet', (@Query('name', match: 'emoji') String name) => '😇🔥🔥🔥');
|
app.get(
|
||||||
|
'/greet',
|
||||||
|
ioc((@Query('name', match: 'emoji') String name) => '😇🔥🔥🔥'),
|
||||||
|
);
|
||||||
|
|
||||||
// Handle any other query value of `name`.
|
// Handle any other query value of `name`.
|
||||||
app.get('/greet', (@Query('name') String name) => 'Hello, $name!');
|
app.get(
|
||||||
|
'/greet',
|
||||||
|
ioc((@Query('name') String name) => 'Hello, $name!'),
|
||||||
|
);
|
||||||
|
|
||||||
// Simple fallback to throw a 404 on unknown paths.
|
// Simple fallback to throw a 404 on unknown paths.
|
||||||
app.use((RequestContext req) async {
|
app.fallback((req, res) {
|
||||||
throw new AngelHttpException.notFound(
|
throw new AngelHttpException.notFound(
|
||||||
message: 'Unknown path: "${req.uri.path}"',
|
message: 'Unknown path: "${req.uri.path}"',
|
||||||
);
|
);
|
||||||
|
|
|
@ -9,7 +9,8 @@ main() async {
|
||||||
..logger = (new Logger('angel')..onRecord.listen(print))
|
..logger = (new Logger('angel')..onRecord.listen(print))
|
||||||
..encoders.addAll({'gzip': gzip.encoder});
|
..encoders.addAll({'gzip': gzip.encoder});
|
||||||
|
|
||||||
app.fallback((req, res) => new Future.error('Throwing just because I feel like!'));
|
app.fallback(
|
||||||
|
(req, res) => new Future.error('Throwing just because I feel like!'));
|
||||||
|
|
||||||
var http = new AngelHttp(app);
|
var http = new AngelHttp(app);
|
||||||
var server = await http.startServer('127.0.0.1', 3000);
|
var server = await http.startServer('127.0.0.1', 3000);
|
||||||
|
|
|
@ -121,7 +121,8 @@ class HookedService extends Service {
|
||||||
applyListeners(inner.remove, afterRemoved, true);
|
applyListeners(inner.remove, afterRemoved, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<RequestHandler> get bootstrappers => new List<RequestHandler>.from(super.bootstrappers)
|
List<RequestHandler> get bootstrappers =>
|
||||||
|
new List<RequestHandler>.from(super.bootstrappers)
|
||||||
..add((RequestContext req, ResponseContext res) {
|
..add((RequestContext req, ResponseContext res) {
|
||||||
req.serviceParams
|
req.serviceParams
|
||||||
..['__requestctx'] = req
|
..['__requestctx'] = req
|
||||||
|
|
|
@ -103,7 +103,7 @@ abstract class ResponseContext<RawResponse>
|
||||||
|
|
||||||
headers["Content-Disposition"] =
|
headers["Content-Disposition"] =
|
||||||
'attachment; filename="${filename ?? file.path}"';
|
'attachment; filename="${filename ?? file.path}"';
|
||||||
headers['content-type'] = lookupMimeType(file.path);
|
contentType = MediaType.parse(lookupMimeType(file.path));
|
||||||
headers['content-length'] = file.lengthSync().toString();
|
headers['content-length'] = file.lengthSync().toString();
|
||||||
|
|
||||||
if (!isBuffered) {
|
if (!isBuffered) {
|
||||||
|
@ -150,7 +150,7 @@ abstract class ResponseContext<RawResponse>
|
||||||
new Map<String, dynamic>.from(renderParams)
|
new Map<String, dynamic>.from(renderParams)
|
||||||
..addAll(data ?? <String, dynamic>{}))).then((content) {
|
..addAll(data ?? <String, dynamic>{}))).then((content) {
|
||||||
write(content);
|
write(content);
|
||||||
headers['content-type'] = 'text/html';
|
contentType = new MediaType('text', 'html');
|
||||||
close();
|
close();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -241,8 +241,11 @@ abstract class ResponseContext<RawResponse>
|
||||||
throw new Exception(
|
throw new Exception(
|
||||||
"Controller '${split[0]}' does not contain any action named '${split[1]}'");
|
"Controller '${split[0]}' does not contain any action named '${split[1]}'");
|
||||||
|
|
||||||
final head =
|
final head = controller
|
||||||
controller.findExpose(app.container.reflector).path.toString().replaceAll(_straySlashes, '');
|
.findExpose(app.container.reflector)
|
||||||
|
.path
|
||||||
|
.toString()
|
||||||
|
.replaceAll(_straySlashes, '');
|
||||||
final tail = matched
|
final tail = matched
|
||||||
.makeUri(params.keys.fold<Map<String, dynamic>>({}, (out, k) {
|
.makeUri(params.keys.fold<Map<String, dynamic>>({}, (out, k) {
|
||||||
return out..[k.toString()] = params[k];
|
return out..[k.toString()] = params[k];
|
||||||
|
@ -256,14 +259,15 @@ abstract class ResponseContext<RawResponse>
|
||||||
void sendFile(File file) {
|
void sendFile(File file) {
|
||||||
if (!isOpen) throw closed();
|
if (!isOpen) throw closed();
|
||||||
|
|
||||||
headers['content-type'] = lookupMimeType(file.path);
|
contentType = MediaType.parse(lookupMimeType(file.path));
|
||||||
buffer.add(file.readAsBytesSync());
|
buffer.add(file.readAsBytesSync());
|
||||||
close();
|
close();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Serializes data to the response.
|
/// Serializes data to the response.
|
||||||
bool serialize(value) {
|
bool serialize(value, {MediaType contentType}) {
|
||||||
if (!isOpen) throw closed();
|
if (!isOpen) throw closed();
|
||||||
|
this.contentType = contentType ?? new MediaType('application', 'json');
|
||||||
var text = serializer(value);
|
var text = serializer(value);
|
||||||
if (text.isEmpty) return true;
|
if (text.isEmpty) return true;
|
||||||
write(text);
|
write(text);
|
||||||
|
@ -274,12 +278,12 @@ abstract class ResponseContext<RawResponse>
|
||||||
/// Streams a file to this response.
|
/// Streams a file to this response.
|
||||||
Future streamFile(File file) {
|
Future streamFile(File file) {
|
||||||
if (!isOpen) throw closed();
|
if (!isOpen) throw closed();
|
||||||
headers['content-type'] = lookupMimeType(file.path);
|
contentType = MediaType.parse(lookupMimeType(file.path));
|
||||||
return file.openRead().pipe(this);
|
return file.openRead().pipe(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Configure the response to write to an intermediate response buffer, rather than to the stream directly.
|
/// Configure the response to write to an intermediate response buffer, rather than to the stream directly.
|
||||||
void enableBuffer();
|
void useBuffer();
|
||||||
|
|
||||||
/// Adds a stream directly the underlying response.
|
/// Adds a stream directly the underlying response.
|
||||||
///
|
///
|
||||||
|
@ -304,9 +308,6 @@ abstract class ResponseContext<RawResponse>
|
||||||
else if (!isBuffered) {
|
else if (!isBuffered) {
|
||||||
add(encoding.encode(value.toString()));
|
add(encoding.encode(value.toString()));
|
||||||
} else {
|
} else {
|
||||||
if (value is List<int>)
|
|
||||||
buffer.add(value);
|
|
||||||
else
|
|
||||||
buffer.add(encoding.encode(value.toString()));
|
buffer.add(encoding.encode(value.toString()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ import 'package:angel_container/angel_container.dart';
|
||||||
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:http_parser/http_parser.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:tuple/tuple.dart';
|
import 'package:tuple/tuple.dart';
|
||||||
|
|
||||||
|
@ -135,7 +136,7 @@ class Angel extends Routable {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
res.headers['content-type'] = 'text/html';
|
res.contentType = new MediaType('text', 'html', {'charset': 'utf8'});
|
||||||
res.statusCode = e.statusCode;
|
res.statusCode = e.statusCode;
|
||||||
res.write("<!DOCTYPE html><html><head><title>${e.message}</title>");
|
res.write("<!DOCTYPE html><html><head><title>${e.message}</title>");
|
||||||
res.write("</head><body><h1>${e.message}</h1><ul>");
|
res.write("</head><body><h1>${e.message}</h1><ul>");
|
||||||
|
@ -170,6 +171,12 @@ class Angel extends Routable {
|
||||||
logger?.warning(
|
logger?.warning(
|
||||||
'This route will be ignored, and no requests will ever reach it.');
|
'This route will be ignored, and no requests will ever reach it.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (router is Angel) {
|
||||||
|
router._parent = this;
|
||||||
|
_children.add(router);
|
||||||
|
}
|
||||||
|
|
||||||
return super.mount(path.toString(), router);
|
return super.mount(path.toString(), router);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -238,11 +245,6 @@ class Angel extends Routable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Shortcuts for adding converters to transform the response buffer/stream of any request.
|
|
||||||
void injectEncoders(Map<String, Converter<List<int>, List<int>>> encoders) {
|
|
||||||
this.encoders.addAll(encoders);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future getHandlerResult(handler, RequestContext req, ResponseContext res) {
|
Future getHandlerResult(handler, RequestContext req, ResponseContext res) {
|
||||||
if (handler is RequestHandler) {
|
if (handler is RequestHandler) {
|
||||||
var result = handler(req, res);
|
var result = handler(req, res);
|
||||||
|
@ -296,24 +298,7 @@ class Angel extends Routable {
|
||||||
void optimizeForProduction({bool force: false}) {
|
void optimizeForProduction({bool force: false}) {
|
||||||
if (isProduction == true || force == true) {
|
if (isProduction == true || force == true) {
|
||||||
_isProduction = true;
|
_isProduction = true;
|
||||||
_add(v) {
|
|
||||||
if (v is Function && !_preContained.containsKey(v)) {
|
|
||||||
_preContained[v] = preInject(v, container.reflector);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _walk(Router router) {
|
|
||||||
router.middleware.forEach(_add);
|
|
||||||
router.routes.forEach((r) {
|
|
||||||
r.handlers.forEach(_add);
|
|
||||||
if (r is SymlinkRoute) _walk(r.router);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
_flattened ??= flatten(this);
|
_flattened ??= flatten(this);
|
||||||
|
|
||||||
_walk(_flattened);
|
|
||||||
|
|
||||||
logger?.config('Angel is running in production mode.');
|
logger?.config('Angel is running in production mode.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,11 +33,15 @@ class Providers {
|
||||||
static const Providers websocket = Providers(viaWebsocket);
|
static const Providers websocket = Providers(viaWebsocket);
|
||||||
|
|
||||||
/// Represents a request parsed from GraphQL.
|
/// Represents a request parsed from GraphQL.
|
||||||
static const Providers graphql = Providers(viaGraphQL);
|
static const Providers graphQL = Providers(viaGraphQL);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(other) => other is Providers && other.via == via;
|
bool operator ==(other) => other is Providers && other.via == via;
|
||||||
|
|
||||||
|
Map<String, String> toJson() {
|
||||||
|
return {'via': via};
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'via:$via';
|
return 'via:$via';
|
||||||
|
@ -154,11 +158,13 @@ class Service extends Routable {
|
||||||
var handlers = new List<RequestHandler>.from(handlerss);
|
var handlers = new List<RequestHandler>.from(handlerss);
|
||||||
|
|
||||||
// Add global middleware if declared on the instance itself
|
// Add global middleware if declared on the instance itself
|
||||||
Middleware before = getAnnotation(service, Middleware, app.container.reflector);
|
Middleware before =
|
||||||
|
getAnnotation(service, Middleware, app.container.reflector);
|
||||||
|
|
||||||
if (before != null) handlers.addAll(before.handlers);
|
if (before != null) handlers.addAll(before.handlers);
|
||||||
|
|
||||||
Middleware indexMiddleware = getAnnotation(service.index, Middleware, app.container.reflector);
|
Middleware indexMiddleware =
|
||||||
|
getAnnotation(service.index, Middleware, app.container.reflector);
|
||||||
get('/', (req, res) {
|
get('/', (req, res) {
|
||||||
return req.parseQuery().then((query) {
|
return req.parseQuery().then((query) {
|
||||||
return this.index(mergeMap([
|
return this.index(mergeMap([
|
||||||
|
@ -172,7 +178,8 @@ class Service extends Routable {
|
||||||
..addAll(handlers)
|
..addAll(handlers)
|
||||||
..addAll((indexMiddleware == null) ? [] : indexMiddleware.handlers));
|
..addAll((indexMiddleware == null) ? [] : indexMiddleware.handlers));
|
||||||
|
|
||||||
Middleware createMiddleware = getAnnotation(service.create, Middleware, app.container.reflector);
|
Middleware createMiddleware =
|
||||||
|
getAnnotation(service.create, Middleware, app.container.reflector);
|
||||||
post('/', (req, ResponseContext res) {
|
post('/', (req, ResponseContext res) {
|
||||||
return req.parseQuery().then((query) {
|
return req.parseQuery().then((query) {
|
||||||
return req.parseBody().then((body) {
|
return req.parseBody().then((body) {
|
||||||
|
@ -196,7 +203,8 @@ class Service extends Routable {
|
||||||
..addAll(
|
..addAll(
|
||||||
(createMiddleware == null) ? [] : createMiddleware.handlers));
|
(createMiddleware == null) ? [] : createMiddleware.handlers));
|
||||||
|
|
||||||
Middleware readMiddleware = getAnnotation(service.read, Middleware, app.container.reflector);
|
Middleware readMiddleware =
|
||||||
|
getAnnotation(service.read, Middleware, app.container.reflector);
|
||||||
|
|
||||||
get('/:id', (req, res) {
|
get('/:id', (req, res) {
|
||||||
return req.parseQuery().then((query) {
|
return req.parseQuery().then((query) {
|
||||||
|
@ -213,7 +221,8 @@ class Service extends Routable {
|
||||||
..addAll(handlers)
|
..addAll(handlers)
|
||||||
..addAll((readMiddleware == null) ? [] : readMiddleware.handlers));
|
..addAll((readMiddleware == null) ? [] : readMiddleware.handlers));
|
||||||
|
|
||||||
Middleware modifyMiddleware = getAnnotation(service.modify, Middleware, app.container.reflector);
|
Middleware modifyMiddleware =
|
||||||
|
getAnnotation(service.modify, Middleware, app.container.reflector);
|
||||||
patch(
|
patch(
|
||||||
'/:id',
|
'/:id',
|
||||||
(req, res) => req.parseBody().then((body) {
|
(req, res) => req.parseBody().then((body) {
|
||||||
|
@ -233,7 +242,8 @@ class Service extends Routable {
|
||||||
..addAll(
|
..addAll(
|
||||||
(modifyMiddleware == null) ? [] : modifyMiddleware.handlers));
|
(modifyMiddleware == null) ? [] : modifyMiddleware.handlers));
|
||||||
|
|
||||||
Middleware updateMiddleware = getAnnotation(service.update, Middleware, app.container.reflector);
|
Middleware updateMiddleware =
|
||||||
|
getAnnotation(service.update, Middleware, app.container.reflector);
|
||||||
post(
|
post(
|
||||||
'/:id',
|
'/:id',
|
||||||
(req, res) => req.parseBody().then((body) {
|
(req, res) => req.parseBody().then((body) {
|
||||||
|
@ -271,7 +281,8 @@ class Service extends Routable {
|
||||||
..addAll(
|
..addAll(
|
||||||
(updateMiddleware == null) ? [] : updateMiddleware.handlers));
|
(updateMiddleware == null) ? [] : updateMiddleware.handlers));
|
||||||
|
|
||||||
Middleware removeMiddleware = getAnnotation(service.remove, Middleware, app.container.reflector);
|
Middleware removeMiddleware =
|
||||||
|
getAnnotation(service.remove, Middleware, app.container.reflector);
|
||||||
delete('/', (req, res) {
|
delete('/', (req, res) {
|
||||||
return req.parseQuery().then((query) {
|
return req.parseQuery().then((query) {
|
||||||
return this.remove(
|
return this.remove(
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
|
|
@ -147,11 +147,13 @@ class AngelHttp {
|
||||||
if (handler == null) break;
|
if (handler == null) break;
|
||||||
|
|
||||||
if (runPipeline == null)
|
if (runPipeline == null)
|
||||||
runPipeline = () => Future.sync(() => handler(req, res));
|
runPipeline = () =>
|
||||||
|
Future.sync(() => app.executeHandler(handler, req, res));
|
||||||
else {
|
else {
|
||||||
var current = runPipeline;
|
var current = runPipeline;
|
||||||
runPipeline = () => current().then((result) =>
|
runPipeline = () => current().then((result) => !res.isOpen
|
||||||
!res.isOpen ? new Future.value(result) : handler(req, res));
|
? new Future.value(result)
|
||||||
|
: app.executeHandler(handler, req, res));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -45,9 +45,8 @@ class Controller {
|
||||||
var routable = new Routable();
|
var routable = new Routable();
|
||||||
app.mount(exposeDecl.path, routable);
|
app.mount(exposeDecl.path, routable);
|
||||||
var typeMirror = app.container.reflector.reflectType(this.runtimeType);
|
var typeMirror = app.container.reflector.reflectType(this.runtimeType);
|
||||||
String name = exposeDecl.as?.isNotEmpty == true
|
String name =
|
||||||
? exposeDecl.as
|
exposeDecl.as?.isNotEmpty == true ? exposeDecl.as : typeMirror.name;
|
||||||
: typeMirror.name;
|
|
||||||
|
|
||||||
app.controllers[name] = this;
|
app.controllers[name] = this;
|
||||||
|
|
||||||
|
@ -85,9 +84,8 @@ class Controller {
|
||||||
var middleware = <RequestHandler>[]
|
var middleware = <RequestHandler>[]
|
||||||
..addAll(handlers)
|
..addAll(handlers)
|
||||||
..addAll(exposeDecl.middleware);
|
..addAll(exposeDecl.middleware);
|
||||||
String name = exposeDecl.as?.isNotEmpty == true
|
String name =
|
||||||
? exposeDecl.as
|
exposeDecl.as?.isNotEmpty == true ? exposeDecl.as : methodName;
|
||||||
: methodName;
|
|
||||||
|
|
||||||
// Check if normal
|
// Check if normal
|
||||||
var method = decl.function;
|
var method = decl.function;
|
||||||
|
@ -120,7 +118,8 @@ class Controller {
|
||||||
void configureRoutes(Routable routable) {}
|
void configureRoutes(Routable routable) {}
|
||||||
|
|
||||||
/// Finds the [Expose] declaration for this class.
|
/// Finds the [Expose] declaration for this class.
|
||||||
Expose findExpose(Reflector reflector) => reflector.reflectClass(runtimeType)
|
Expose findExpose(Reflector reflector) => reflector
|
||||||
|
.reflectClass(runtimeType)
|
||||||
.annotations
|
.annotations
|
||||||
.map((m) => m.reflectee)
|
.map((m) => m.reflectee)
|
||||||
.firstWhere((r) => r is Expose, orElse: () => null) as Expose;
|
.firstWhere((r) => r is Expose, orElse: () => null) as Expose;
|
||||||
|
|
|
@ -42,34 +42,14 @@ class HttpResponseContext extends ResponseContext<HttpResponse> {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void enableBuffer() {
|
void useBuffer() {
|
||||||
_buffer = new LockableBytesBuilder();
|
_buffer = new LockableBytesBuilder();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool _openStream() {
|
Iterable<String> __allowedEncodings;
|
||||||
if (!_streamInitialized) {
|
|
||||||
// If this is the first stream added to this response,
|
|
||||||
// then add headers, status code, etc.
|
|
||||||
rawResponse
|
|
||||||
..statusCode = statusCode
|
|
||||||
..cookies.addAll(cookies);
|
|
||||||
headers.forEach(rawResponse.headers.set);
|
|
||||||
//_isClosed = true;
|
|
||||||
return _streamInitialized = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
Iterable<String> get _allowedEncodings {
|
||||||
}
|
return __allowedEncodings ??= correspondingRequest.headers
|
||||||
|
|
||||||
@override
|
|
||||||
Future addStream(Stream<List<int>> stream) {
|
|
||||||
if (_isClosed && isBuffered) throw ResponseContext.closed();
|
|
||||||
var firstStream = _openStream();
|
|
||||||
|
|
||||||
Stream<List<int>> output = stream;
|
|
||||||
|
|
||||||
if (encoders.isNotEmpty && correspondingRequest != null) {
|
|
||||||
var allowedEncodings = correspondingRequest.headers
|
|
||||||
.value('accept-encoding')
|
.value('accept-encoding')
|
||||||
?.split(',')
|
?.split(',')
|
||||||
?.map((s) => s.trim())
|
?.map((s) => s.trim())
|
||||||
|
@ -80,9 +60,24 @@ class HttpResponseContext extends ResponseContext<HttpResponse> {
|
||||||
if (!str.contains(';')) return str;
|
if (!str.contains(';')) return str;
|
||||||
return str.split(';')[0];
|
return str.split(';')[0];
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (allowedEncodings != null) {
|
bool _openStream() {
|
||||||
for (var encodingName in allowedEncodings) {
|
if (!_streamInitialized) {
|
||||||
|
// If this is the first stream added to this response,
|
||||||
|
// then add headers, status code, etc.
|
||||||
|
rawResponse
|
||||||
|
..statusCode = statusCode
|
||||||
|
..cookies.addAll(cookies);
|
||||||
|
headers.forEach(rawResponse.headers.set);
|
||||||
|
rawResponse.headers.contentType = new ContentType(
|
||||||
|
contentType.type, contentType.subtype,
|
||||||
|
charset: contentType.parameters['charset'],
|
||||||
|
parameters: contentType.parameters);
|
||||||
|
|
||||||
|
if (encoders.isNotEmpty && correspondingRequest != null) {
|
||||||
|
if (_allowedEncodings != null) {
|
||||||
|
for (var encodingName in _allowedEncodings) {
|
||||||
Converter<List<int>, List<int>> encoder;
|
Converter<List<int>, List<int>> encoder;
|
||||||
String key = encodingName;
|
String key = encodingName;
|
||||||
|
|
||||||
|
@ -93,10 +88,40 @@ class HttpResponseContext extends ResponseContext<HttpResponse> {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (encoder != null) {
|
if (encoder != null) {
|
||||||
if (firstStream) {
|
|
||||||
rawResponse.headers.set('content-encoding', key);
|
rawResponse.headers.set('content-encoding', key);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//_isClosed = true;
|
||||||
|
return _streamInitialized = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future addStream(Stream<List<int>> stream) {
|
||||||
|
if (_isClosed && isBuffered) throw ResponseContext.closed();
|
||||||
|
_openStream();
|
||||||
|
|
||||||
|
Stream<List<int>> output = stream;
|
||||||
|
|
||||||
|
if (encoders.isNotEmpty && correspondingRequest != null) {
|
||||||
|
if (_allowedEncodings != null) {
|
||||||
|
for (var encodingName in _allowedEncodings) {
|
||||||
|
Converter<List<int>, List<int>> encoder;
|
||||||
|
String key = encodingName;
|
||||||
|
|
||||||
|
if (encoders.containsKey(encodingName))
|
||||||
|
encoder = encoders[encodingName];
|
||||||
|
else if (encodingName == '*') {
|
||||||
|
encoder = encoders[key = encoders.keys.first];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (encoder != null) {
|
||||||
output = encoders[key].bind(output);
|
output = encoders[key].bind(output);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -113,6 +138,27 @@ class HttpResponseContext extends ResponseContext<HttpResponse> {
|
||||||
throw ResponseContext.closed();
|
throw ResponseContext.closed();
|
||||||
else if (!isBuffered) {
|
else if (!isBuffered) {
|
||||||
_openStream();
|
_openStream();
|
||||||
|
|
||||||
|
if (encoders.isNotEmpty && correspondingRequest != null) {
|
||||||
|
if (_allowedEncodings != null) {
|
||||||
|
for (var encodingName in _allowedEncodings) {
|
||||||
|
Converter<List<int>, List<int>> encoder;
|
||||||
|
String key = encodingName;
|
||||||
|
|
||||||
|
if (encoders.containsKey(encodingName))
|
||||||
|
encoder = encoders[encodingName];
|
||||||
|
else if (encodingName == '*') {
|
||||||
|
encoder = encoders[key = encoders.keys.first];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (encoder != null) {
|
||||||
|
data = encoders[key].convert(data);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
rawResponse.add(data);
|
rawResponse.add(data);
|
||||||
} else
|
} else
|
||||||
buffer.add(data);
|
buffer.add(data);
|
||||||
|
|
|
@ -4,10 +4,11 @@ final RegExp straySlashes = new RegExp(r'(^/+)|(/+$)');
|
||||||
|
|
||||||
matchingAnnotation(List<ReflectedInstance> metadata, Type T) {
|
matchingAnnotation(List<ReflectedInstance> metadata, Type T) {
|
||||||
for (ReflectedInstance metaDatum in metadata) {
|
for (ReflectedInstance metaDatum in metadata) {
|
||||||
if (metaDatum.type == T) {
|
if (metaDatum.type.reflectedType == T) {
|
||||||
return metaDatum.reflectee;
|
return metaDatum.reflectee;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,13 @@ class Todo extends Model {
|
||||||
String over;
|
String over;
|
||||||
|
|
||||||
Todo({String this.text, String this.over});
|
Todo({String this.text, String this.over});
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {
|
||||||
|
'text': text,
|
||||||
|
'over': over,
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class BookService extends Service {
|
class BookService extends Service {
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
import 'dart:convert';
|
||||||
import 'dart:io' show BytesBuilder;
|
import 'dart:io' show BytesBuilder;
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:angel_container/mirrors.dart';
|
import 'package:angel_container/mirrors.dart';
|
||||||
import 'package:angel_framework/angel_framework.dart';
|
import 'package:angel_framework/angel_framework.dart';
|
||||||
import 'dart:convert';
|
|
||||||
import 'dart:io';
|
|
||||||
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,7 +20,7 @@ main() {
|
||||||
|
|
||||||
setUp(() {
|
setUp(() {
|
||||||
app = new Angel(reflector: MirrorsReflector());
|
app = new Angel(reflector: MirrorsReflector());
|
||||||
app.injectEncoders(
|
app.encoders.addAll(
|
||||||
{
|
{
|
||||||
'deflate': zlib.encoder,
|
'deflate': zlib.encoder,
|
||||||
'gzip': gzip.encoder,
|
'gzip': gzip.encoder,
|
||||||
|
@ -27,7 +28,9 @@ main() {
|
||||||
);
|
);
|
||||||
|
|
||||||
app.get('/hello', (req, res) {
|
app.get('/hello', (req, res) {
|
||||||
res.write('Hello, world!');
|
res
|
||||||
|
..useBuffer()
|
||||||
|
..write('Hello, world!');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -63,6 +66,7 @@ void encodingTests(Angel getApp()) {
|
||||||
await http.handleRequest(rq);
|
await http.handleRequest(rq);
|
||||||
|
|
||||||
var body = await getBody(rs);
|
var body = await getBody(rs);
|
||||||
|
print(rs.headers);
|
||||||
expect(rs.headers.value('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!')));
|
||||||
});
|
});
|
||||||
|
|
|
@ -18,10 +18,7 @@ Future printResponse(MockHttpResponse rs) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
group('parameter_meta', parameterMetaTests,
|
group('parameter_meta', parameterMetaTests);
|
||||||
skip: !Platform.version.contains('2.0.0')
|
|
||||||
? null
|
|
||||||
: 'Blocked on https://github.com/dart-lang/sdk/issues/33774');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
parameterMetaTests() {
|
parameterMetaTests() {
|
||||||
|
|
|
@ -12,7 +12,7 @@ main() {
|
||||||
..get('/foo', ioc(echoAppFoo));
|
..get('/foo', ioc(echoAppFoo));
|
||||||
app.optimizeForProduction(force: true);
|
app.optimizeForProduction(force: true);
|
||||||
print(app.preContained);
|
print(app.preContained);
|
||||||
expect(app.preContained, contains(echoAppFoo));
|
expect(app.preContained.keys, contains(echoAppFoo));
|
||||||
|
|
||||||
var rq = new MockHttpRequest('GET', new Uri(path: '/foo'));
|
var rq = new MockHttpRequest('GET', new Uri(path: '/foo'));
|
||||||
rq.close();
|
rq.close();
|
||||||
|
@ -20,7 +20,7 @@ main() {
|
||||||
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'));
|
||||||
});
|
}, skip: 'Angel no longer has to preinject functions');
|
||||||
}
|
}
|
||||||
|
|
||||||
echoAppFoo(String foo) => foo;
|
echoAppFoo(String foo) => foo;
|
||||||
|
|
|
@ -99,7 +99,7 @@ main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
app.chain([write('a')]).chain([write('b'), write('c')]).get(
|
app.chain([write('a')]).chain([write('b'), write('c')]).get(
|
||||||
'/chained', (req, res) => false);
|
'/chained', (req, res) => res.close());
|
||||||
|
|
||||||
app.fallback((req, res) => 'MJ');
|
app.fallback((req, res) => 'MJ');
|
||||||
|
|
||||||
|
|
|
@ -16,8 +16,8 @@ main() {
|
||||||
app = new Angel(reflector: MirrorsReflector())
|
app = new Angel(reflector: MirrorsReflector())
|
||||||
..get('/foo', ioc(() => {'hello': 'world'}))
|
..get('/foo', ioc(() => {'hello': 'world'}))
|
||||||
..get('/bar', (req, res) async {
|
..get('/bar', (req, res) async {
|
||||||
res.contentType = new MediaType('text', 'html');
|
await res.serialize({'hello': 'world'},
|
||||||
await res.serialize({'hello': 'world'});
|
contentType: new MediaType('text', 'html'));
|
||||||
});
|
});
|
||||||
client = new http.Client();
|
client = new http.Client();
|
||||||
|
|
||||||
|
|
|
@ -145,7 +145,7 @@ main() {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('end response', () async {
|
test('end response', () async {
|
||||||
var handler = (req, res) => res.end();
|
var handler = (req, ResponseContext res) => res.close();
|
||||||
expect(await app.executeHandler(handler, req, res), isFalse);
|
expect(await app.executeHandler(handler, req, res), isFalse);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -17,7 +17,7 @@ main() {
|
||||||
|
|
||||||
setUp(() {
|
setUp(() {
|
||||||
app = new Angel(reflector: MirrorsReflector());
|
app = new Angel(reflector: MirrorsReflector());
|
||||||
http = new AngelHttp(app);
|
http = new AngelHttp(app, useZone: true);
|
||||||
|
|
||||||
app.logger = new Logger('streaming_test')
|
app.logger = new Logger('streaming_test')
|
||||||
..onRecord.listen((rec) {
|
..onRecord.listen((rec) {
|
||||||
|
@ -25,7 +25,7 @@ main() {
|
||||||
if (rec.stackTrace != null) print(rec.stackTrace);
|
if (rec.stackTrace != null) print(rec.stackTrace);
|
||||||
});
|
});
|
||||||
|
|
||||||
app.injectEncoders(
|
app.encoders.addAll(
|
||||||
{
|
{
|
||||||
'deflate': zlib.encoder,
|
'deflate': zlib.encoder,
|
||||||
'gzip': gzip.encoder,
|
'gzip': gzip.encoder,
|
||||||
|
@ -33,7 +33,8 @@ main() {
|
||||||
);
|
);
|
||||||
|
|
||||||
app.get('/hello', (req, res) {
|
app.get('/hello', (req, res) {
|
||||||
new Stream<List<int>>.fromIterable(['Hello, world!'.codeUnits]).pipe(res);
|
return new Stream<List<int>>.fromIterable(['Hello, world!'.codeUnits])
|
||||||
|
.pipe(res);
|
||||||
});
|
});
|
||||||
|
|
||||||
app.get('/write', (req, res) async {
|
app.get('/write', (req, res) async {
|
||||||
|
@ -56,13 +57,12 @@ main() {
|
||||||
await new Stream<List<int>>.fromIterable(['Hello, world!'.codeUnits])
|
await new Stream<List<int>>.fromIterable(['Hello, world!'.codeUnits])
|
||||||
.pipe(res);
|
.pipe(res);
|
||||||
|
|
||||||
try {
|
var f = new Stream<List<int>>.fromIterable(['Hello, world!'.codeUnits])
|
||||||
await new Stream<List<int>>.fromIterable(['Hello, world!'.codeUnits])
|
.pipe(res)
|
||||||
.pipe(res);
|
.then((_) => false)
|
||||||
throw new Exception('Should throw on rewrite...');
|
.catchError((_) => true);
|
||||||
} on StateError {
|
|
||||||
// Yay!!!
|
expect(f, completion(true));
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
app.get('/error', (req, res) => res.addError(new StateError('wtf')));
|
app.get('/error', (req, res) => res.addError(new StateError('wtf')));
|
||||||
|
@ -86,12 +86,16 @@ main() {
|
||||||
test('multiple addStream', () => _expectHelloBye('/multiple'));
|
test('multiple addStream', () => _expectHelloBye('/multiple'));
|
||||||
|
|
||||||
test('cannot write after close', () async {
|
test('cannot write after close', () async {
|
||||||
|
try {
|
||||||
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';
|
||||||
|
} on StateError {
|
||||||
|
// Success
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
test('res => addError', () async {
|
test('res => addError', () async {
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
import 'package:angel_container/mirrors.dart';
|
|
||||||
import 'package:angel_framework/angel_framework.dart';
|
import 'package:angel_framework/angel_framework.dart';
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
main() {
|
main() {
|
||||||
test('default view generator', () async {
|
test('default view generator', () async {
|
||||||
var app = new Angel(reflector: MirrorsReflector());
|
var app = new Angel();
|
||||||
var view = await app.viewGenerator('foo', {'bar': 'baz'});
|
var view = await app.viewGenerator('foo', {'bar': 'baz'});
|
||||||
expect(view, contains('No view engine'));
|
expect(view, contains('No view engine'));
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue