Will be finished when router can flatten properly...

This commit is contained in:
thosakwe 2016-11-23 12:22:23 -05:00
parent eef450cfc9
commit e7d5f6428f
6 changed files with 172 additions and 108 deletions

View file

@ -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<String` of filenames that should be served as index pages. Default is `['index.html']`.
- **virtualRoot**: To serve index files, you need to specify the virtual path under which
- **publicPath**: To serve index files, you need to specify the virtual path under which
angel_static is serving your files. If you are not serving static files at the site root,
please include this.
- **debug**: Print verbose debug output.

View file

@ -1,57 +1,4 @@
library angel_static;
import 'dart:async';
import 'dart:io';
import 'package:angel_framework/angel_framework.dart';
import 'package:mime/mime.dart' show lookupMimeType;
Future<bool> _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<String> 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';

11
lib/src/serve_static.dart Normal file
View file

@ -0,0 +1,11 @@
import 'dart:io';
import 'package:angel_framework/angel_framework.dart';
@deprecated
RequestMiddleware serveStatic(
{Directory sourceDirectory,
List<String> 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.');
}

View file

@ -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<String, String> 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<String> 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<bool> 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;
}
}

View file

@ -2,7 +2,7 @@ name: angel_static
description: Static server middleware for Angel.
homepage: https://github.com/angel-dart/angel_static
author: thosakwe <thosakwe@gmail.com>
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"

View file

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