diff --git a/example/angel_in_shelf.dart b/example/angel_in_shelf.dart new file mode 100644 index 00000000..b3bcd628 --- /dev/null +++ b/example/angel_in_shelf.dart @@ -0,0 +1,49 @@ +import 'dart:io'; +import 'package:angel_container/mirrors.dart'; +import 'package:angel_framework/angel_framework.dart'; +import 'package:angel_shelf/angel_shelf.dart'; +import 'package:logging/logging.dart'; +import 'package:pretty_logging/pretty_logging.dart'; +import 'package:shelf/shelf.dart' as shelf; +import 'package:shelf/shelf_io.dart' as shelf_io; +import 'package:shelf_static/shelf_static.dart'; + +main() async { + Logger.root + ..level = Level.ALL + ..onRecord.listen(prettyLog); + + // Create a basic Angel server, with some routes. + var app = Angel( + logger: Logger('angel_shelf_demo'), + reflector: MirrorsReflector(), + ); + + app.get('/angel', (req, res) { + res.write('Angel embedded within shelf!'); + return false; + }); + + app.get('/hello', ioc((@Query('name') String name) { + return {'hello': name}; + })); + + // Next, create an AngelShelf driver. + // If we have startup hooks we want to run, we need to call + // `startServer`. Otherwise, it can be omitted. + var angelShelf = AngelShelf(app); + await angelShelf.startServer(); + + // Create, and mount, a shelf pipeline... + // You can also embed Angel as a middleware... + var mwHandler = shelf.Pipeline() + .addMiddleware(angelShelf.middleware) + .addHandler(createStaticHandler('.', + defaultDocument: 'index.html', listDirectories: true)); + + // Run the servers. + await shelf_io.serve(mwHandler, InternetAddress.loopbackIPv4, 8080); + await shelf_io.serve(angelShelf.handler, InternetAddress.loopbackIPv4, 8081); + print('Angel as middleware: http://localhost:8080'); + print('Angel as only handler: http://localhost:8081'); +} diff --git a/example/main.dart b/example/main.dart index 1803eae3..6a7b0fb5 100644 --- a/example/main.dart +++ b/example/main.dart @@ -10,7 +10,7 @@ main() async { Logger.root ..level = Level.ALL ..onRecord.listen(prettyLog); - + var app = Angel(logger: Logger('angel_shelf_demo')); var http = AngelHttp(app); diff --git a/lib/angel_shelf.dart b/lib/angel_shelf.dart index 43f6647d..e251ade2 100644 --- a/lib/angel_shelf.dart +++ b/lib/angel_shelf.dart @@ -1,2 +1,5 @@ export 'src/convert.dart'; export 'src/embed_shelf.dart'; +export 'src/shelf_driver.dart'; +export 'src/shelf_request.dart'; +export 'src/shelf_response.dart'; diff --git a/lib/src/shelf_driver.dart b/lib/src/shelf_driver.dart index 0467175a..17fc6792 100644 --- a/lib/src/shelf_driver.dart +++ b/lib/src/shelf_driver.dart @@ -7,15 +7,68 @@ import 'shelf_response.dart'; class AngelShelf extends Driver, ShelfRequestContext, ShelfResponseContext> { - AngelShelf.custom(Angel app, {bool useZone = true}) - : super(app, (_, __) => throw _unsupported(), useZone: useZone); + final StreamController incomingRequests = StreamController(); + + final FutureOr Function() notFound; + + AngelShelf(Angel app, {FutureOr Function() notFound}) + : this.notFound = + notFound ?? (() => shelf.Response.notFound('Not Found')), + super(app, null, useZone: false) { + // Inject a final handler that will keep responses open, if we are using the + // driver as a middleware. + app.fallback((req, res) { + if (res is ShelfResponseContext) { + res.closeSilently(); + } + return true; + }); + } + + Future> Function(dynamic, int) get serverGenerator => + (_, __) async => incomingRequests.stream; static UnsupportedError _unsupported() => UnsupportedError( 'AngelShelf cannot mount a standalone server, or return a URI.'); Future handler(shelf.Request request) async { var response = ShelfResponseContext(app); - await handleRawRequest(request, response); + var result = await handleRawRequest(request, response); + if (result is shelf.Response) { + return result; + } else if (!response.isOpen) { + return response.shelfResponse; + } else { + // return await handler(request); + return notFound(); + } + } + + shelf.Handler middleware(shelf.Handler handler) { + return (request) async { + var response = ShelfResponseContext(app); + var result = await handleRawRequest(request, response); + if (result is shelf.Response) { + return result; + } else if (!response.isOpen) { + return response.shelfResponse; + } else { + return await handler(request); + } + }; + } + + @override + Future handleAngelHttpException( + AngelHttpException e, + StackTrace st, + RequestContext req, + ResponseContext res, + shelf.Request request, + ShelfResponseContext response, + {bool ignoreFinalizers = false}) async { + await super.handleAngelHttpException(e, st, req, res, request, response, + ignoreFinalizers: ignoreFinalizers); return response.shelfResponse; } @@ -38,7 +91,7 @@ class AngelShelf extends Driver createRequestContext( shelf.Request request, ShelfResponseContext response) { - var path = uri.path.replaceAll(_straySlashes, ''); + var path = request.url.path.replaceAll(_straySlashes, ''); if (path.isEmpty) path = '/'; var rq = ShelfRequestContext(app, app.container.createChild(), request, path); diff --git a/lib/src/shelf_response.dart b/lib/src/shelf_response.dart index 8e03fda9..c3d180a2 100644 --- a/lib/src/shelf_response.dart +++ b/lib/src/shelf_response.dart @@ -8,12 +8,19 @@ import 'shelf_request.dart'; class ShelfResponseContext extends ResponseContext { final Angel app; final StreamController> _ctrl = StreamController(); - bool _isOpen = true, _isDetached = false; + bool _isOpen = true, + _isDetached = false, + _wasClosedByHandler = false, + _handlersAreDone = false; ShelfResponseContext(this.app); ShelfRequestContext _correspondingRequest; + bool get wasClosedByHandler => _wasClosedByHandler; + + void closeSilently() => _handlersAreDone = true; + ShelfRequestContext get correspondingRequest => _correspondingRequest; set correspondingRequest(ShelfRequestContext value) { @@ -30,7 +37,9 @@ class ShelfResponseContext extends ResponseContext { @override Future close() { - _isOpen = false; + if (!_handlersAreDone) { + _isOpen = false; + } _ctrl.close(); return super.close(); }