From e7d5f6428f9d22e214ff22e04653d282fdb5e45a Mon Sep 17 00:00:00 2001 From: thosakwe Date: Wed, 23 Nov 2016 12:22:23 -0500 Subject: [PATCH] Will be finished when router can flatten properly... --- README.md | 30 ++++++----- lib/angel_static.dart | 57 +-------------------- lib/src/serve_static.dart | 11 ++++ lib/src/virtual_directory.dart | 94 ++++++++++++++++++++++++++++++++++ pubspec.yaml | 2 +- test/all_test.dart | 86 +++++++++++++++++-------------- 6 files changed, 172 insertions(+), 108 deletions(-) create mode 100644 lib/src/serve_static.dart create mode 100644 lib/src/virtual_directory.dart diff --git a/README.md b/README.md index b101adbf..f7b4f14a 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # angel_static -![version 1.0.1](https://img.shields.io/badge/version-1.0.1-green.svg) +![version 1.1.0-dev](https://img.shields.io/badge/version-1.1.0--dev-red.svg) ![build status](https://travis-ci.org/angel-dart/static.svg?branch=master) Static server middleware for Angel. @@ -11,13 +11,12 @@ In `pubspec.yaml`: ```yaml dependencies: angel_framework: ^1.0.0-dev - angel_static: ^1.0.0 + angel_static: ^1.1.0-dev ``` # Usage -As with all Angel middleware, this can be used simply via a function -call within the route declaration, or registered under a name and invoked -under that same name. +To serve files from a directory, your app needs to have a +`VirtualDirectory` mounted on it. ```dart import 'dart:io'; @@ -25,20 +24,23 @@ import 'package:angel_framework/angel_framework.dart'; import 'package:angel_static/angel_static.dart'; main() async { - Angel angel = new Angel(); - angel.registerMiddleware("static", serveStatic()); - angel.get('/virtual*', serveStatic(virtualRoot: '/virtual')); - angel.get("*", "static"); - - await angel.startServer(InternetAddress.LOOPBACK_IP_V4, 8080); + final app = new Angel(); + + app.mount('/virtual', new VirtualDirectory( + source: new Directory('./public'), + publicPath: '/virtual')); + app.mount('/', new VirtualDirectory(source: new Directory('./public'))); + + await app.startServer(); } ``` # Options -`serveStatic` accepts two named parameters. -- **sourceDirectory**: A `Directory` containing the files to be served. If left null, then Angel will serve either from `web` (in development) or +The `VirtualDirectory` API accepts a few named parameters: +- **source**: A `Directory` containing the files to be served. If left null, then Angel will serve either from `web` (in development) or `build/web` (in production), depending on your `ANGEL_ENV`. - **indexFileNames**: A `List _sendFile(File file, ResponseContext res) async { - res - ..willCloseItself = true - ..header(HttpHeaders.CONTENT_TYPE, lookupMimeType(file.path)) - ..status(200); - await res.streamFile(file); - await res.underlyingResponse.close(); - return false; -} - -/// Serves files statically from a given directory. -RequestMiddleware serveStatic({ -Directory sourceDirectory, -List indexFileNames: const['index.html'], -String virtualRoot: '/' -}) { - if (sourceDirectory == null) { - String dirPath = Platform.environment['ANGEL_ENV'] == 'production' - ? './build/web' - : './web'; - sourceDirectory = new Directory(dirPath); - } - - RegExp requestingIndex = new RegExp(r'^((\/)|(\\))*$'); - - return (RequestContext req, ResponseContext res) async { - String requested = req.path.replaceAll(new RegExp(r'^\/'), ''); - File file = new File.fromUri( - sourceDirectory.absolute.uri.resolve(requested)); - if (await file.exists()) { - return await _sendFile(file, res); - } - - // Try to resolve index - String relative = req.path.replaceFirst(virtualRoot, "") - .replaceAll(new RegExp(r'^\/+'), ""); - if (requestingIndex.hasMatch(relative) || relative.isEmpty) { - for (String indexFileName in indexFileNames) { - file = - new File.fromUri(sourceDirectory.absolute.uri.resolve(indexFileName)); - if (await file.exists()) { - return await _sendFile(file, res); - } - } - } - - return true; - }; -} - +export 'src/serve_static.dart'; +export 'src/virtual_directory.dart'; diff --git a/lib/src/serve_static.dart b/lib/src/serve_static.dart new file mode 100644 index 00000000..53cd6698 --- /dev/null +++ b/lib/src/serve_static.dart @@ -0,0 +1,11 @@ +import 'dart:io'; +import 'package:angel_framework/angel_framework.dart'; + +@deprecated +RequestMiddleware serveStatic( + {Directory sourceDirectory, + List indexFileNames: const ['index.html'], + String virtualRoot: '/'}) { + throw new Exception( + 'The `serveStatic` API is now deprecated. Please update your application to use the new `VirtualDirectory` API.'); +} diff --git a/lib/src/virtual_directory.dart b/lib/src/virtual_directory.dart new file mode 100644 index 00000000..5037d5a9 --- /dev/null +++ b/lib/src/virtual_directory.dart @@ -0,0 +1,94 @@ +import 'dart:async'; +import 'dart:io'; +import 'package:angel_framework/angel_framework.dart'; +import 'package:mime/mime.dart' show lookupMimeType; + +final RegExp _param = new RegExp(r':([A-Za-z0-9_]+)(\((.+)\))?'); +final RegExp _straySlashes = new RegExp(r'(^/+)|(/+$)'); + +String _pathify(String path) { + var p = path.replaceAll(_straySlashes, ''); + + Map replace = {}; + + for (Match match in _param.allMatches(p)) { + if (match[3] != null) replace[match[0]] = ':${match[1]}'; + } + + replace.forEach((k, v) { + p = p.replaceAll(k, v); + }); + + return p; +} + +class VirtualDirectory extends Router { + Directory _source; + Directory get source => _source; + final List indexFileNames; + final String publicPath; + + VirtualDirectory( + {Directory source, + bool debug: false, + this.indexFileNames: const ['index.html'], + this.publicPath: '/'}) + : super(debug: debug) { + if (source != null) { + _source = source; + } else { + String dirPath = Platform.environment['ANGEL_ENV'] == 'production' + ? './build/web' + : './web'; + _source = new Directory(dirPath); + } + + final prefix = publicPath.replaceAll(_straySlashes, ''); + _printDebug('Source directory: ${source.absolute.path}'); + _printDebug('Public path prefix: "$prefix"'); + + get('*', (RequestContext req, ResponseContext res) async { + var path = req.path.replaceAll(_straySlashes, ''); + + 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, 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, res); + } + } + } else { + _printDebug('File "$path" does not exist, and is not an index.'); + return true; + } + } + }); + } + + _printDebug(msg) { + if (debug) print(msg); + } + + Future sendFile(File file, ResponseContext res) async { + _printDebug('Streaming file ${file.absolute.path}...'); + res + ..willCloseItself = true + ..header(HttpHeaders.CONTENT_TYPE, lookupMimeType(file.path)) + ..status(200); + await res.streamFile(file); + await res.underlyingResponse.close(); + return false; + } +} diff --git a/pubspec.yaml b/pubspec.yaml index d0c0ad31..b593fd00 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -2,7 +2,7 @@ name: angel_static description: Static server middleware for Angel. homepage: https://github.com/angel-dart/angel_static author: thosakwe -version: 1.0.1 +version: 1.1.0-dev dependencies: angel_framework: ">=1.0.0-dev < 2.0.0" mime: ">= 0.9.3 < 0.10.0" diff --git a/test/all_test.dart b/test/all_test.dart index 23d1e389..a4a7baaa 100644 --- a/test/all_test.dart +++ b/test/all_test.dart @@ -5,50 +5,60 @@ import 'package:http/http.dart' show Client; import 'package:test/test.dart'; main() { - group('angel_static', () { - Angel angel; - String url; - Client client = new Client(); + Angel app; + Directory testDir = new Directory('test'); + String url; + Client client = new Client(); - setUp(() async { - angel = new Angel(); - angel.registerMiddleware( - "static", serveStatic(sourceDirectory: new Directory("test"), - indexFileNames: ['index.php', 'index.txt'])); - angel.get('/virtual/*', "Fallback", - middleware: [serveStatic(sourceDirectory: new Directory("test"), - virtualRoot: '/virtual', - indexFileNames: ['index.txt']) - ]); - angel.get("*", "Fallback", middleware: ["static"]); + setUp(() async { + app = new Angel(); - await angel.startServer(InternetAddress.LOOPBACK_IP_V4, 0); - url = "http://${angel.httpServer.address.host}:${angel.httpServer.port}"; - }); + app.mount( + '/virtual', + new VirtualDirectory( + debug: true, + source: testDir, + publicPath: '/virtual', + indexFileNames: ['index.txt'])); - tearDown(() async { - await angel.httpServer.close(force: true); - }); + app.mount( + '/', + new VirtualDirectory( + debug: true, + source: testDir, + indexFileNames: ['index.php', 'index.txt'])); - test('can serve files, with correct Content-Type', () async { - var response = await client.get("$url/sample.txt"); - expect(response.body, equals("Hello world")); - expect(response.headers[HttpHeaders.CONTENT_TYPE], equals("text/plain")); - }); + app.get('*', 'Fallback'); + app + ..normalize() + ..dumpTree(); - test('non-existent files are skipped', () async { - var response = await client.get("$url/nonexist.ent"); - expect(response.body, equals('"Fallback"')); - }); + await app.startServer(InternetAddress.LOOPBACK_IP_V4, 0); + url = "http://${app.httpServer.address.host}:${app.httpServer.port}"; + }); - test('can match index files', () async { - var response = await client.get(url); - expect(response.body, equals("index!")); - }); + tearDown(() async { + await app.httpServer.close(force: true); + }); - test('virtualRoots can match index', () async { - var response = await client.get("$url/virtual"); - expect(response.body, equals("index!")); - }); + test('can serve files, with correct Content-Type', () async { + var response = await client.get("$url/sample.txt"); + expect(response.body, equals("Hello world")); + expect(response.headers[HttpHeaders.CONTENT_TYPE], equals("text/plain")); + }); + + test('non-existent files are skipped', () async { + var response = await client.get("$url/nonexist.ent"); + expect(response.body, equals('"Fallback"')); + }); + + test('can match index files', () async { + var response = await client.get(url); + expect(response.body, equals("index!")); + }); + + test('virtualRoots can match index', () async { + var response = await client.get("$url/virtual"); + expect(response.body, equals("index!")); }); }