All tests pass

This commit is contained in:
Tobe O 2018-08-21 14:50:43 -04:00
parent 4017bc1682
commit 9dd355a4c5
27 changed files with 640 additions and 589 deletions

View file

@ -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" />

View file

@ -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>

View file

@ -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>

View 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>

View file

@ -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

View file

@ -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.

View file

@ -9,38 +9,53 @@ 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',
// Pattern matching - only call this handler if the query value of `name` equals 'emoji'. (req, res) {
app.get('/greet', (@Query('name', match: 'emoji') String name) => '😇🔥🔥🔥'); var name = req.params['name'];
res
// Handle any other query value of `name`. ..write('Hello, $name!')
app.get('/greet', (@Query('name') String name) => 'Hello, $name!'); ..close();
},
// Simple fallback to throw a 404 on unknown paths.
app.use((RequestContext req) async {
throw new AngelHttpException.notFound(
message: 'Unknown path: "${req.uri.path}"',
); );
});
var http = new AngelHttp(app); // Pattern matching - only call this handler if the query value of `name` equals 'emoji'.
var server = await http.startServer('127.0.0.1', 3000); app.get(
var url = 'http://${server.address.address}:${server.port}'; '/greet',
print('Listening at $url'); ioc((@Query('name', match: 'emoji') String name) => '😇🔥🔥🔥'),
print('Visit these pages to see Angel in action:'); );
print('* $url/greet/bob');
print('* $url/greet/?name=emoji'); // Handle any other query value of `name`.
print('* $url/greet/?name=jack'); app.get(
print('* $url/nonexistent_page'); '/greet',
ioc((@Query('name') String name) => 'Hello, $name!'),
);
// Simple fallback to throw a 404 on unknown paths.
app.fallback((req, res) {
throw new AngelHttpException.notFound(
message: 'Unknown path: "${req.uri.path}"',
);
});
var http = new AngelHttp(app);
var server = await http.startServer('127.0.0.1', 3000);
var url = 'http://${server.address.address}:${server.port}';
print('Listening at $url');
print('Visit these pages to see Angel in action:');
print('* $url/greet/bob');
print('* $url/greet/?name=emoji');
print('* $url/greet/?name=jack');
print('* $url/nonexistent_page');
} }
``` ```

View file

@ -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);

View file

@ -121,13 +121,14 @@ 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 =>
..add((RequestContext req, ResponseContext res) { new List<RequestHandler>.from(super.bootstrappers)
req.serviceParams ..add((RequestContext req, ResponseContext res) {
..['__requestctx'] = req req.serviceParams
..['__responsectx'] = res; ..['__requestctx'] = req
return true; ..['__responsectx'] = res;
}); return true;
});
void addRoutes([Service s]) { void addRoutes([Service s]) {
super.addRoutes(s ?? inner); super.addRoutes(s ?? inner);

View file

@ -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,10 +308,7 @@ 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(encoding.encode(value.toString()));
buffer.add(value);
else
buffer.add(encoding.encode(value.toString()));
} }
} }

View file

@ -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.');
} }
} }

View file

@ -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(

View file

@ -0,0 +1 @@

View file

@ -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));
} }
} }

View file

@ -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;

View file

@ -42,10 +42,26 @@ class HttpResponseContext extends ResponseContext<HttpResponse> {
} }
@override @override
void enableBuffer() { void useBuffer() {
_buffer = new LockableBytesBuilder(); _buffer = new LockableBytesBuilder();
} }
Iterable<String> __allowedEncodings;
Iterable<String> get _allowedEncodings {
return __allowedEncodings ??= correspondingRequest.headers
.value('accept-encoding')
?.split(',')
?.map((s) => s.trim())
?.where((s) => s.isNotEmpty)
?.map((str) {
// Ignore quality specifications in accept-encoding
// ex. gzip;q=0.8
if (!str.contains(';')) return str;
return str.split(';')[0];
});
}
bool _openStream() { bool _openStream() {
if (!_streamInitialized) { if (!_streamInitialized) {
// If this is the first stream added to this response, // If this is the first stream added to this response,
@ -54,6 +70,31 @@ class HttpResponseContext extends ResponseContext<HttpResponse> {
..statusCode = statusCode ..statusCode = statusCode
..cookies.addAll(cookies); ..cookies.addAll(cookies);
headers.forEach(rawResponse.headers.set); 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;
String key = encodingName;
if (encoders.containsKey(encodingName))
encoder = encoders[encodingName];
else if (encodingName == '*') {
encoder = encoders[key = encoders.keys.first];
}
if (encoder != null) {
rawResponse.headers.set('content-encoding', key);
break;
}
}
}
}
//_isClosed = true; //_isClosed = true;
return _streamInitialized = true; return _streamInitialized = true;
} }
@ -64,25 +105,13 @@ class HttpResponseContext extends ResponseContext<HttpResponse> {
@override @override
Future addStream(Stream<List<int>> stream) { Future addStream(Stream<List<int>> stream) {
if (_isClosed && isBuffered) throw ResponseContext.closed(); if (_isClosed && isBuffered) throw ResponseContext.closed();
var firstStream = _openStream(); _openStream();
Stream<List<int>> output = stream; Stream<List<int>> output = stream;
if (encoders.isNotEmpty && correspondingRequest != null) { if (encoders.isNotEmpty && correspondingRequest != null) {
var allowedEncodings = correspondingRequest.headers if (_allowedEncodings != null) {
.value('accept-encoding') for (var encodingName in _allowedEncodings) {
?.split(',')
?.map((s) => s.trim())
?.where((s) => s.isNotEmpty)
?.map((str) {
// Ignore quality specifications in accept-encoding
// ex. gzip;q=0.8
if (!str.contains(';')) return str;
return str.split(';')[0];
});
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 +122,6 @@ class HttpResponseContext extends ResponseContext<HttpResponse> {
} }
if (encoder != null) { if (encoder != null) {
if (firstStream) {
rawResponse.headers.set('content-encoding', key);
}
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);

View file

@ -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;
} }

View file

@ -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 {

View file

@ -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!')));
}); });

View file

@ -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() {

View file

@ -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;

View file

@ -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');

View file

@ -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();

View file

@ -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);
}); });
}); });

View file

@ -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,15 +25,16 @@ 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,
}, },
); );
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 {
var rq = new MockHttpRequest('GET', Uri.parse('/overwrite'))..close(); try {
await http.handleRequest(rq); var rq = new MockHttpRequest('GET', Uri.parse('/overwrite'))..close();
var body = await rq.response.transform(utf8.decoder).join(); await http.handleRequest(rq);
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 {

View file

@ -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'));
}); });