Will be finished when router can flatten properly...
This commit is contained in:
parent
eef450cfc9
commit
e7d5f6428f
6 changed files with 172 additions and 108 deletions
30
README.md
30
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<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.
|
||||
|
|
|
@ -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
11
lib/src/serve_static.dart
Normal 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.');
|
||||
}
|
94
lib/src/virtual_directory.dart
Normal file
94
lib/src/virtual_directory.dart
Normal 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;
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
|
|
|
@ -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!"));
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue