diff --git a/README.md b/README.md index 0850305d..b04d11a7 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # angel_static -![version 1.1.1](https://img.shields.io/badge/version-1.1.1-red.svg) -![build status](https://travis-ci.org/angel-dart/static.svg?branch=master) +[![version 1.1.2](https://img.shields.io/badge/pub-1.1.2-brightgreen.svg)](https://pub.dartlang.org/packages/angel_static) +[![build status](https://travis-ci.org/angel-dart/static.svg?branch=master)](https://travis-ci.org/angel-dart/static) Static server middleware for Angel. @@ -10,8 +10,7 @@ In `pubspec.yaml`: ```yaml dependencies: - angel_framework: ^1.0.0-dev - angel_static: ^1.1.0-dev + angel_static: ^1.1.0 ``` # Usage @@ -41,3 +40,4 @@ The `VirtualDirectory` API accepts a few named parameters: - **debug**: Print verbose debug output. - **callback**: Runs before sending a file to a client. Use this to set headers, etc. If it returns anything other than `null` or `true`, then the callback's result will be sent to the user, instead of the file contents. +- **streamToIO**: If set to `true`, files will be streamed to `res.io`, instead of added to `res.buffer`.. Default is `false`. \ No newline at end of file diff --git a/lib/src/virtual_directory.dart b/lib/src/virtual_directory.dart index 7eb30f17..48e41fc0 100644 --- a/lib/src/virtual_directory.dart +++ b/lib/src/virtual_directory.dart @@ -25,7 +25,7 @@ String _pathify(String path) { return p; } -class VirtualDirectory { +class VirtualDirectory implements AngelPlugin { final bool debug; String _prefix; Directory _source; @@ -34,12 +34,16 @@ class VirtualDirectory { final List indexFileNames; final String publicPath; + /// If set to `true`, files will be streamed to `res.io`, instead of added to `res.buffer`. + final bool streamToIO; + VirtualDirectory( {Directory source, this.debug: false, this.indexFileNames: const ['index.html'], this.publicPath: '/', - this.callback}) { + this.callback, + this.streamToIO: false}) { _prefix = publicPath.replaceAll(_straySlashes, ''); if (source != null) { @@ -56,9 +60,46 @@ class VirtualDirectory { if (debug) print(msg); } - call(AngelBase app) async => serve(app); + call(Angel app) async => serve(app); - Future sendFile( + void serve(Router router) { + _printDebug('Source directory: ${source.absolute.path}'); + _printDebug('Public path prefix: "$_prefix"'); + router.get('$publicPath/*', + (RequestContext req, ResponseContext res) async { + var path = req.path.replaceAll(_straySlashes, ''); + return servePath(path, req, res); + }); + } + + servePath(String path, RequestContext req, ResponseContext res) async { + if (_prefix.isNotEmpty) { + path = path.replaceAll(new RegExp('^' + _pathify(_prefix)), ''); + } + + if (path.isEmpty) path = '.'; + + var absolute = source.absolute.uri.resolve(path).toFilePath(); + var stat = await FileStat.stat(absolute); + return await serveStat(absolute, stat, req, res); + } + + Future serveStat(String absolute, FileStat stat, RequestContext req, + ResponseContext res) async { + if (stat.type == FileSystemEntityType.NOT_FOUND) + return true; + else if (stat.type == FileSystemEntityType.DIRECTORY) + return await serveDirectory(new Directory(absolute), req, res); + else if (stat.type == FileSystemEntityType.FILE) + return await serveFile(new File(absolute), req, res); + else if (stat.type == FileSystemEntityType.LINK) { + var link = new Link(absolute); + return await servePath(await link.resolveSymbolicLinks(), req, res); + } else + return true; + } + + Future serveFile( File file, RequestContext req, ResponseContext res) async { _printDebug('Sending file ${file.absolute.path}...'); _printDebug('MIME type for ${file.path}: ${lookupMimeType(file.path)}'); @@ -71,44 +112,24 @@ class VirtualDirectory { } res.headers[HttpHeaders.CONTENT_TYPE] = lookupMimeType(file.path); - await res.streamFile(file); + + if (streamToIO == true) + await res.streamFile(file); + else + await res.sendFile(file); return false; } - void serve(Router router) { - _printDebug('Source directory: ${source.absolute.path}'); - _printDebug('Public path prefix: "$_prefix"'); - router.get('$publicPath/*', - (RequestContext req, ResponseContext res) async { - var path = req.path.replaceAll(_straySlashes, ''); - return serveFile(path, req, res); - }); - } - - serveFile(String path, RequestContext req, ResponseContext res) async { - if (_prefix.isNotEmpty) { - path = path.replaceAll(new RegExp('^' + _pathify(_prefix)), ''); - } - - final file = new File.fromUri(source.absolute.uri.resolve(path)); - _printDebug('Attempting to statically serve file: ${file.absolute.path}'); - - if (await file.exists()) { - return sendFile(file, req, res); - } else { - // Try to resolve index - if (path.isEmpty) { - for (String indexFileName in indexFileNames) { - final index = - new File.fromUri(source.absolute.uri.resolve(indexFileName)); - if (await index.exists()) { - return await sendFile(index, req, res); - } - } - } else { - _printDebug('File "$path" does not exist, and is not an index.'); - return true; + Future serveDirectory( + Directory directory, RequestContext req, ResponseContext res) async { + for (String indexFileName in indexFileNames) { + final index = + new File.fromUri(directory.absolute.uri.resolve(indexFileName)); + if (await index.exists()) { + return await serveFile(index, req, res); } } + + return true; } } diff --git a/pubspec.yaml b/pubspec.yaml index 001fa82f..2e7ad7c6 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -4,7 +4,7 @@ environment: sdk: ">=1.19.0" homepage: https://github.com/angel-dart/angel_static author: thosakwe -version: 1.1.1 +version: 1.1.2 dependencies: angel_framework: ^1.0.0-dev mime: ^0.9.3 diff --git a/test/all_test.dart b/test/all_test.dart index d30b7554..80be5b20 100644 --- a/test/all_test.dart +++ b/test/all_test.dart @@ -42,6 +42,12 @@ main() { expect(response.headers[HttpHeaders.CONTENT_TYPE], contains("text/plain")); }); + test('can serve child directories', () async { + var response = await client.get("$url/nested"); + expect(response.body, equals("Bird")); + expect(response.headers[HttpHeaders.CONTENT_TYPE], contains("text/plain")); + }); + test('non-existent files are skipped', () async { var response = await client.get("$url/nonexist.ent"); expect(response.body, equals('"Fallback"')); diff --git a/test/nested/index.txt b/test/nested/index.txt new file mode 100644 index 00000000..d402a929 --- /dev/null +++ b/test/nested/index.txt @@ -0,0 +1 @@ +Bird \ No newline at end of file