2016-11-23 17:22:23 +00:00
|
|
|
import 'dart:async';
|
|
|
|
import 'dart:io';
|
|
|
|
import 'package:angel_framework/angel_framework.dart';
|
2016-11-23 20:14:05 +00:00
|
|
|
import 'package:angel_route/angel_route.dart';
|
2017-01-28 16:33:22 +00:00
|
|
|
import 'package:mime/mime.dart';
|
2016-11-23 17:22:23 +00:00
|
|
|
|
2017-01-25 22:40:41 +00:00
|
|
|
typedef StaticFileCallback(File file, RequestContext req, ResponseContext res);
|
|
|
|
|
2016-11-23 17:22:23 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2017-02-27 00:19:34 +00:00
|
|
|
/// A static server plug-in.
|
2017-02-22 23:43:27 +00:00
|
|
|
class VirtualDirectory implements AngelPlugin {
|
2016-11-23 20:14:05 +00:00
|
|
|
final bool debug;
|
|
|
|
String _prefix;
|
2016-11-23 17:22:23 +00:00
|
|
|
Directory _source;
|
2017-02-27 00:19:34 +00:00
|
|
|
|
|
|
|
/// The directory to serve files from.
|
2016-11-23 17:22:23 +00:00
|
|
|
Directory get source => _source;
|
2017-02-27 00:19:34 +00:00
|
|
|
|
|
|
|
/// An optional callback to run before serving files.
|
2017-01-25 22:40:41 +00:00
|
|
|
final StaticFileCallback callback;
|
2017-02-27 00:19:34 +00:00
|
|
|
|
|
|
|
/// Filenames to be resolved within directories as indices.
|
|
|
|
final Iterable<String> indexFileNames;
|
|
|
|
|
|
|
|
/// An optional public path to map requests to.
|
2016-11-23 17:22:23 +00:00
|
|
|
final String publicPath;
|
|
|
|
|
2017-02-22 23:43:27 +00:00
|
|
|
/// If set to `true`, files will be streamed to `res.io`, instead of added to `res.buffer`.
|
|
|
|
final bool streamToIO;
|
|
|
|
|
2016-11-23 17:22:23 +00:00
|
|
|
VirtualDirectory(
|
|
|
|
{Directory source,
|
2016-11-23 20:14:05 +00:00
|
|
|
this.debug: false,
|
2016-11-23 17:22:23 +00:00
|
|
|
this.indexFileNames: const ['index.html'],
|
2017-01-25 22:40:41 +00:00
|
|
|
this.publicPath: '/',
|
2017-02-22 23:43:27 +00:00
|
|
|
this.callback,
|
|
|
|
this.streamToIO: false}) {
|
2016-11-23 20:14:05 +00:00
|
|
|
_prefix = publicPath.replaceAll(_straySlashes, '');
|
|
|
|
|
2016-11-23 17:22:23 +00:00
|
|
|
if (source != null) {
|
|
|
|
_source = source;
|
|
|
|
} else {
|
|
|
|
String dirPath = Platform.environment['ANGEL_ENV'] == 'production'
|
|
|
|
? './build/web'
|
|
|
|
: './web';
|
|
|
|
_source = new Directory(dirPath);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
_printDebug(msg) {
|
|
|
|
if (debug) print(msg);
|
|
|
|
}
|
|
|
|
|
2017-02-22 23:43:27 +00:00
|
|
|
call(Angel app) async => serve(app);
|
|
|
|
|
|
|
|
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<bool> serveStat(String absolute, FileStat stat, RequestContext req,
|
|
|
|
ResponseContext res) async {
|
|
|
|
if (stat.type == FileSystemEntityType.NOT_FOUND)
|
|
|
|
return true;
|
|
|
|
else if (stat.type == FileSystemEntityType.DIRECTORY)
|
2017-02-27 00:19:34 +00:00
|
|
|
return await serveDirectory(new Directory(absolute), stat, req, res);
|
2017-02-22 23:43:27 +00:00
|
|
|
else if (stat.type == FileSystemEntityType.FILE)
|
2017-02-27 00:19:34 +00:00
|
|
|
return await serveFile(new File(absolute), stat, req, res);
|
2017-02-22 23:43:27 +00:00
|
|
|
else if (stat.type == FileSystemEntityType.LINK) {
|
|
|
|
var link = new Link(absolute);
|
|
|
|
return await servePath(await link.resolveSymbolicLinks(), req, res);
|
|
|
|
} else
|
|
|
|
return true;
|
|
|
|
}
|
2016-11-23 20:14:05 +00:00
|
|
|
|
2017-02-22 23:43:27 +00:00
|
|
|
Future<bool> serveFile(
|
2017-02-27 00:19:34 +00:00
|
|
|
File file, FileStat stat, RequestContext req, ResponseContext res) async {
|
2017-01-25 22:40:41 +00:00
|
|
|
_printDebug('Sending file ${file.absolute.path}...');
|
2017-01-28 16:33:22 +00:00
|
|
|
_printDebug('MIME type for ${file.path}: ${lookupMimeType(file.path)}');
|
2016-12-21 17:51:43 +00:00
|
|
|
res.statusCode = 200;
|
2017-01-25 22:40:41 +00:00
|
|
|
|
|
|
|
if (callback != null) {
|
|
|
|
var r = callback(file, req, res);
|
|
|
|
r = r is Future ? await r : r;
|
|
|
|
if (r != null && r != true) return r;
|
|
|
|
}
|
|
|
|
|
2017-01-28 16:33:22 +00:00
|
|
|
res.headers[HttpHeaders.CONTENT_TYPE] = lookupMimeType(file.path);
|
2016-11-23 20:14:05 +00:00
|
|
|
|
2017-04-26 22:39:47 +00:00
|
|
|
if (streamToIO == true) {
|
|
|
|
res
|
|
|
|
..io.headers.set(HttpHeaders.CONTENT_TYPE, lookupMimeType(file.path))
|
|
|
|
..io.headers.set(HttpHeaders.CONTENT_ENCODING, 'gzip')
|
|
|
|
..end()
|
|
|
|
..willCloseItself = true;
|
|
|
|
|
|
|
|
await file.openRead().transform(GZIP.encoder).pipe(res.io);
|
|
|
|
} else
|
2017-02-22 23:43:27 +00:00
|
|
|
await res.sendFile(file);
|
|
|
|
return false;
|
2016-11-23 20:14:05 +00:00
|
|
|
}
|
|
|
|
|
2017-02-27 00:19:34 +00:00
|
|
|
Future<bool> serveDirectory(Directory directory, FileStat stat,
|
|
|
|
RequestContext req, ResponseContext res) async {
|
2017-02-22 23:43:27 +00:00
|
|
|
for (String indexFileName in indexFileNames) {
|
|
|
|
final index =
|
|
|
|
new File.fromUri(directory.absolute.uri.resolve(indexFileName));
|
|
|
|
if (await index.exists()) {
|
2017-02-27 00:19:34 +00:00
|
|
|
return await serveFile(index, stat, req, res);
|
2016-11-23 20:14:05 +00:00
|
|
|
}
|
|
|
|
}
|
2017-02-22 23:43:27 +00:00
|
|
|
|
|
|
|
return true;
|
2016-11-23 20:14:05 +00:00
|
|
|
}
|
2016-11-23 17:22:23 +00:00
|
|
|
}
|