Make redirects + download async

This commit is contained in:
Tobe O 2019-04-19 03:46:44 -04:00
parent 1c07aa5243
commit 2004877696
5 changed files with 26 additions and 16 deletions

View file

@ -1,3 +1,6 @@
# 2.0.0-rc.6
* Make `redirect` and `download` methods asynchronous.
# 2.0.0-rc.5 # 2.0.0-rc.5
* Make `serializer` `FutureOr<String> Function(Object)`. * Make `serializer` `FutureOr<String> Function(Object)`.
* Make `ResponseContext.serialize` return `Future<bool>`. * Make `ResponseContext.serialize` return `Future<bool>`.

View file

@ -139,7 +139,7 @@ abstract class ResponseContext<RawResponse>
new StateError('Cannot modify a closed response.'); new StateError('Cannot modify a closed response.');
/// Sends a download as a response. /// Sends a download as a response.
void download(File file, {String filename}) { Future<void> download(File file, {String filename}) async {
if (!isOpen) throw closed(); if (!isOpen) throw closed();
headers["Content-Disposition"] = headers["Content-Disposition"] =
@ -148,15 +148,15 @@ abstract class ResponseContext<RawResponse>
headers['content-length'] = file.lengthSync().toString(); headers['content-length'] = file.lengthSync().toString();
if (!isBuffered) { if (!isBuffered) {
file.openRead().pipe(this); await file.openRead().pipe(this);
} else { } else {
buffer.add(file.readAsBytesSync()); buffer.add(file.readAsBytesSync());
close(); await close();
} }
} }
/// Prevents more data from being written to the response, and locks it entire from further editing. /// Prevents more data from being written to the response, and locks it entire from further editing.
Future close() { Future<void> close() {
if (buffer is LockableBytesBuilder) { if (buffer is LockableBytesBuilder) {
(buffer as LockableBytesBuilder).lock(); (buffer as LockableBytesBuilder).lock();
} }
@ -173,16 +173,17 @@ abstract class ResponseContext<RawResponse>
/// Returns a JSONP response. /// Returns a JSONP response.
/// ///
/// You can override the [contentType] sent; by default it is `application/javascript`. /// You can override the [contentType] sent; by default it is `application/javascript`.
void jsonp(value, {String callbackName = "callback", MediaType contentType}) { Future<void> jsonp(value,
{String callbackName = "callback", MediaType contentType}) {
if (!isOpen) throw closed(); if (!isOpen) throw closed();
this.contentType = this.contentType =
contentType ?? new MediaType('application', 'javascript'); contentType ?? new MediaType('application', 'javascript');
write("$callbackName(${serializer(value)})"); write("$callbackName(${serializer(value)})");
close(); return close();
} }
/// 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<String, dynamic> data]) { Future<void> render(String view, [Map<String, dynamic> data]) {
if (!isOpen) throw closed(); if (!isOpen) throw closed();
contentType = new MediaType('text', 'html', {'charset': 'utf-8'}); contentType = new MediaType('text', 'html', {'charset': 'utf-8'});
return Future<String>.sync(() => app.viewGenerator( return Future<String>.sync(() => app.viewGenerator(
@ -190,7 +191,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);
close(); return close();
}); });
} }
@ -201,7 +202,7 @@ abstract class ResponseContext<RawResponse>
/// based on the provided params. /// based on the provided params.
/// ///
/// See [Router]#navigate for more. :) /// See [Router]#navigate for more. :)
void redirect(url, {bool absolute = true, int code = 302}) { Future<void> redirect(url, {bool absolute = true, int code = 302}) {
if (!isOpen) throw closed(); if (!isOpen) throw closed();
headers headers
..['content-type'] = 'text/html' ..['content-type'] = 'text/html'
@ -226,11 +227,11 @@ abstract class ResponseContext<RawResponse>
</body> </body>
</html> </html>
'''); ''');
close(); return close();
} }
/// Redirects to the given named [Route]. /// Redirects to the given named [Route].
void redirectTo(String name, [Map params, int code]) { Future<void> redirectTo(String name, [Map params, int code]) async {
if (!isOpen) throw closed(); if (!isOpen) throw closed();
Route _findRoute(Router r) { Route _findRoute(Router r) {
for (Route route in r.routes) { for (Route route in r.routes) {
@ -247,7 +248,7 @@ abstract class ResponseContext<RawResponse>
Route matched = _findRoute(app); Route matched = _findRoute(app);
if (matched != null) { if (matched != null) {
redirect( await redirect(
matched.makeUri(params.keys.fold<Map<String, dynamic>>({}, (out, k) { matched.makeUri(params.keys.fold<Map<String, dynamic>>({}, (out, k) {
return out..[k.toString()] = params[k]; return out..[k.toString()] = params[k];
})), })),
@ -259,7 +260,7 @@ abstract class ResponseContext<RawResponse>
} }
/// Redirects to the given [Controller] action. /// Redirects to the given [Controller] action.
void redirectToAction(String action, [Map params, int code]) { Future<void> redirectToAction(String action, [Map params, int code]) {
if (!isOpen) throw closed(); if (!isOpen) throw closed();
// UserController@show // UserController@show
List<String> split = action.split("@"); List<String> split = action.split("@");
@ -291,7 +292,7 @@ abstract class ResponseContext<RawResponse>
})) }))
.replaceAll(_straySlashes, ''); .replaceAll(_straySlashes, '');
redirect('$head/$tail'.replaceAll(_straySlashes, ''), code: code); return redirect('$head/$tail'.replaceAll(_straySlashes, ''), code: code);
} }
/// Serializes data to the response. /// Serializes data to the response.

View file

@ -1,5 +1,5 @@
name: angel_framework name: angel_framework
version: 2.0.0-rc.5 version: 2.0.0-rc.6
description: A high-powered HTTP server with dependency injection, routing and much more. description: A high-powered HTTP server with dependency injection, routing and much 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

View file

@ -99,6 +99,12 @@ main() {
expect(result[0]["angel"], equals("framework")); expect(result[0]["angel"], equals("framework"));
}); });
test('asStream() fires', () async {
var stream = todoService.afterCreated.asStream();
await todoService.create({'angel': 'framework'});
expect(await stream.first.then((e) => e.result['angel']), 'framework');
});
test('metadata', () async { test('metadata', () async {
final service = new HookedService(new IncrementService())..addHooks(app); final service = new HookedService(new IncrementService())..addHooks(app);
expect(service.inner, isNot(const IsInstanceOf<MapService>())); expect(service.inner, isNot(const IsInstanceOf<MapService>()));

View file

@ -85,7 +85,7 @@ main() {
(RequestContext req, res) async => "Hello ${req.params['name']}") (RequestContext req, res) async => "Hello ${req.params['name']}")
.name = 'Named routes'; .name = 'Named routes';
app.get('/named', (req, ResponseContext res) async { app.get('/named', (req, ResponseContext res) async {
res.redirectTo('Named routes', {'name': 'tests'}); await res.redirectTo('Named routes', {'name': 'tests'});
}); });
app.get('/log', (RequestContext req, res) async { app.get('/log', (RequestContext req, res) async {
print("Query: ${req.queryParameters}"); print("Query: ${req.queryParameters}");