Migrated static

This commit is contained in:
thomashii@dukefirehawk.com 2021-05-01 11:53:04 +08:00
parent 3b2fc97da1
commit 3f30f8a56e
10 changed files with 90 additions and 75 deletions

View file

@ -27,8 +27,10 @@
* Added html_builder and migrated to 2.0.0 (1/1 tests passed) * Added html_builder and migrated to 2.0.0 (1/1 tests passed)
* Migrated hot to 4.0.0 (0/0 tests passed) * Migrated hot to 4.0.0 (0/0 tests passed)
* Added range_header and migrated to 3.0.0 (12/12 tests passed) * Added range_header and migrated to 3.0.0 (12/12 tests passed)
* Updated static to 3.0.0 (in progress) * Updated static to 4.0.0 (in progress)
* Update basic-sdk-2.12.x boilerplate (in progress) * Update basic-sdk-2.12.x_nnbd boilerplate (in progress)
* Updated angel_serialize to 5.0.0 (in progress)
* Updated angel_serialize_generator to 5.0.0 (in progress)
# 3.0.0 (Non NNBD) # 3.0.0 (Non NNBD)
* Changed Dart SDK requirements for all packages to ">=2.10.0 <3.0.0" * Changed Dart SDK requirements for all packages to ">=2.10.0 <3.0.0"

View file

@ -35,6 +35,6 @@ void main(List<String> args) async {
app.fallback((req, res) => throw AngelHttpException.notFound()); app.fallback((req, res) => throw AngelHttpException.notFound());
var server = await http.startServer('127.0.0.1', 3000); var server = await http.startServer('127.0.0.1', 3000);
print('Serving from ${vDir.source.path}'); print('Serving from ${vDir.source!.path}');
print('Listening at http://${server.address.address}:${server.port}'); print('Listening at http://${server.address.address}:${server.port}');
} }

View file

@ -39,17 +39,17 @@ class CachingVirtualDirectory extends VirtualDirectory {
CachingVirtualDirectory(Angel app, FileSystem fileSystem, CachingVirtualDirectory(Angel app, FileSystem fileSystem,
{this.accessLevel = CacheAccessLevel.PUBLIC, {this.accessLevel = CacheAccessLevel.PUBLIC,
Directory source, Directory? source,
bool debug, bool? debug,
Iterable<String> indexFileNames, Iterable<String>? indexFileNames,
this.maxAge = 0, this.maxAge = 0,
this.noCache = false, this.noCache = false,
this.onlyInProduction = false, this.onlyInProduction = false,
this.useEtags = true, this.useEtags = true,
bool allowDirectoryListing, bool? allowDirectoryListing,
bool useBuffer = false, bool useBuffer = false,
String publicPath, String? publicPath,
Function(File file, RequestContext req, ResponseContext res) callback}) Function(File file, RequestContext req, ResponseContext res)? callback})
: super(app, fileSystem, : super(app, fileSystem,
source: source, source: source,
indexFileNames: indexFileNames ?? ['index.html'], indexFileNames: indexFileNames ?? ['index.html'],
@ -63,26 +63,26 @@ class CachingVirtualDirectory extends VirtualDirectory {
File file, FileStat stat, RequestContext req, ResponseContext res) { File file, FileStat stat, RequestContext req, ResponseContext res) {
res.headers['accept-ranges'] = 'bytes'; res.headers['accept-ranges'] = 'bytes';
if (onlyInProduction == true && req.app.environment.isProduction != true) { if (onlyInProduction == true && req.app!.environment.isProduction != true) {
return super.serveFile(file, stat, req, res); return super.serveFile(file, stat, req, res);
} }
var shouldNotCache = noCache == true; var shouldNotCache = noCache == true;
if (!shouldNotCache) { if (!shouldNotCache) {
shouldNotCache = req.headers.value('cache-control') == 'no-cache' || shouldNotCache = req.headers!.value('cache-control') == 'no-cache' ||
req.headers.value('pragma') == 'no-cache'; req.headers!.value('pragma') == 'no-cache';
} }
if (shouldNotCache) { if (shouldNotCache) {
res.headers['cache-control'] = 'private, max-age=0, no-cache'; res.headers['cache-control'] = 'private, max-age=0, no-cache';
return super.serveFile(file, stat, req, res); return super.serveFile(file, stat, req, res);
} else { } else {
var ifModified = req.headers.ifModifiedSince; var ifModified = req.headers!.ifModifiedSince;
var ifRange = false; var ifRange = false;
try { try {
ifModified = HttpDate.parse(req.headers.value('if-range')); ifModified = HttpDate.parse(req.headers!.value('if-range')!);
ifRange = true; ifRange = true;
} catch (_) { } catch (_) {
// Fail silently... // Fail silently...
@ -97,7 +97,7 @@ class CachingVirtualDirectory extends VirtualDirectory {
setCachedHeaders(stat.modified, req, res); setCachedHeaders(stat.modified, req, res);
if (useEtags && _etags.containsKey(file.absolute.path)) { if (useEtags && _etags.containsKey(file.absolute.path)) {
res.headers['ETag'] = _etags[file.absolute.path]; res.headers['ETag'] = _etags[file.absolute.path]!;
} }
if (ifRange) { if (ifRange) {
@ -120,18 +120,18 @@ class CachingVirtualDirectory extends VirtualDirectory {
// If-modified didn't work; try etags // If-modified didn't work; try etags
if (useEtags == true) { if (useEtags == true) {
var etagsToMatchAgainst = req.headers['if-none-match']; var etagsToMatchAgainst = req.headers!['if-none-match'];
ifRange = false; ifRange = false;
if (etagsToMatchAgainst?.isNotEmpty != true) { if (etagsToMatchAgainst?.isNotEmpty != true) {
etagsToMatchAgainst = req.headers['if-range']; etagsToMatchAgainst = req.headers!['if-range'];
ifRange = etagsToMatchAgainst?.isNotEmpty == true; ifRange = etagsToMatchAgainst?.isNotEmpty == true;
} }
if (etagsToMatchAgainst?.isNotEmpty == true) { if (etagsToMatchAgainst?.isNotEmpty == true) {
var hasBeenModified = false; var hasBeenModified = false;
for (var etag in etagsToMatchAgainst) { for (var etag in etagsToMatchAgainst!) {
if (etag == '*') { if (etag == '*') {
hasBeenModified = true; hasBeenModified = true;
} else { } else {
@ -166,16 +166,16 @@ class CachingVirtualDirectory extends VirtualDirectory {
void setCachedHeaders( void setCachedHeaders(
DateTime modified, RequestContext req, ResponseContext res) { DateTime modified, RequestContext req, ResponseContext res) {
var privacy = accessLevelToString(accessLevel ?? CacheAccessLevel.PUBLIC); var privacy = accessLevelToString(accessLevel);
res.headers res.headers
..['cache-control'] = '$privacy, max-age=${maxAge ?? 0}' ..['cache-control'] = '$privacy, max-age=$maxAge'
..['last-modified'] = HttpDate.format(modified); ..['last-modified'] = HttpDate.format(modified);
if (maxAge != null) { //if (maxAge != null) {
var expiry = DateTime.now().add(Duration(seconds: maxAge ?? 0)); var expiry = DateTime.now().add(Duration(seconds: maxAge));
res.headers['expires'] = HttpDate.format(expiry); res.headers['expires'] = HttpDate.format(expiry);
} //}
} }
} }

View file

@ -28,14 +28,14 @@ String _pathify(String path) {
/// A static server plug-in. /// A static server plug-in.
class VirtualDirectory { class VirtualDirectory {
String _prefix; String? _prefix;
Directory _source; Directory? _source;
/// The directory to serve files from. /// The directory to serve files from.
Directory get source => _source; Directory? get source => _source;
/// An optional callback to run before serving files. /// An optional callback to run before serving files.
final Function(File file, RequestContext req, ResponseContext res) callback; final Function(File file, RequestContext req, ResponseContext res)? callback;
final Angel app; final Angel app;
final FileSystem fileSystem; final FileSystem fileSystem;
@ -47,7 +47,7 @@ class VirtualDirectory {
final String publicPath; final String publicPath;
/// If `true` (default: `false`), then if a directory does not contain any of the specific [indexFileNames], a default directory listing will be served. /// If `true` (default: `false`), then if a directory does not contain any of the specific [indexFileNames], a default directory listing will be served.
final bool allowDirectoryListing; final bool? allowDirectoryListing;
/// If `true` (default: `true`), then files will be opened as streams and piped into the request. /// If `true` (default: `true`), then files will be opened as streams and piped into the request.
/// ///
@ -55,7 +55,7 @@ class VirtualDirectory {
final bool useBuffer; final bool useBuffer;
VirtualDirectory(this.app, this.fileSystem, VirtualDirectory(this.app, this.fileSystem,
{Directory source, {Directory? source,
this.indexFileNames = const ['index.html'], this.indexFileNames = const ['index.html'],
this.publicPath = '/', this.publicPath = '/',
this.callback, this.callback,
@ -75,9 +75,9 @@ class VirtualDirectory {
if (req.method != 'GET' && req.method != 'HEAD') { if (req.method != 'GET' && req.method != 'HEAD') {
return Future<bool>.value(true); return Future<bool>.value(true);
} }
var path = req.uri.path.replaceAll(_straySlashes, ''); var path = req.uri!.path.replaceAll(_straySlashes, '');
if (_prefix?.isNotEmpty == true && !path.startsWith(_prefix)) { if (_prefix?.isNotEmpty == true && !path.startsWith(_prefix!)) {
return Future<bool>.value(true); return Future<bool>.value(true);
} }
@ -89,7 +89,7 @@ class VirtualDirectory {
/// You can also limit this functionality to specific values of the `Accept` header, ex. `text/html`. /// You can also limit this functionality to specific values of the `Accept` header, ex. `text/html`.
/// If [accepts] is `null`, OR at least one of the content types in [accepts] is present, /// If [accepts] is `null`, OR at least one of the content types in [accepts] is present,
/// the view will be served. /// the view will be served.
RequestHandler pushState(String path, {Iterable accepts}) { RequestHandler pushState(String path, {Iterable? accepts}) {
var vPath = path.replaceAll(_straySlashes, ''); var vPath = path.replaceAll(_straySlashes, '');
if (_prefix?.isNotEmpty == true) vPath = '$_prefix/$vPath'; if (_prefix?.isNotEmpty == true) vPath = '$_prefix/$vPath';
@ -98,7 +98,7 @@ class VirtualDirectory {
if (path == vPath) return Future<bool>.value(true); if (path == vPath) return Future<bool>.value(true);
if (accepts?.isNotEmpty == true) { if (accepts?.isNotEmpty == true) {
if (!accepts.any((x) => req.accepts(x, strict: true))) { if (!accepts!.any((x) => req.accepts(x, strict: true))) {
return Future<bool>.value(true); return Future<bool>.value(true);
} }
} }
@ -110,17 +110,17 @@ class VirtualDirectory {
/// Writes the file at the given virtual [path] to a response. /// Writes the file at the given virtual [path] to a response.
Future<bool> servePath( Future<bool> servePath(
String path, RequestContext req, ResponseContext res) async { String path, RequestContext req, ResponseContext res) async {
if (_prefix.isNotEmpty) { if (_prefix!.isNotEmpty) {
// Only replace the *first* incidence // Only replace the *first* incidence
// Resolve: https://github.com/angel-dart/angel/issues/41 // Resolve: https://github.com/angel-dart/angel/issues/41
path = path.replaceFirst(RegExp('^' + _pathify(_prefix)), ''); path = path.replaceFirst(RegExp('^' + _pathify(_prefix!)), '');
} }
if (path.isEmpty) path = '.'; if (path.isEmpty) path = '.';
path = path.replaceAll(_straySlashes, ''); path = path.replaceAll(_straySlashes, '');
var absolute = source.absolute.uri.resolve(path).toFilePath(); var absolute = source!.absolute.uri.resolve(path).toFilePath();
var parent = source.absolute.uri.toFilePath(); var parent = source!.absolute.uri.toFilePath();
if (!p.isWithin(parent, absolute) && !p.equals(parent, absolute)) { if (!p.isWithin(parent, absolute) && !p.equals(parent, absolute)) {
return true; return true;
@ -199,8 +199,8 @@ class VirtualDirectory {
}); });
for (var entity in entities) { for (var entity in entities) {
String stub; String? stub;
String type; String? type;
if (entity is File) { if (entity is File) {
type = '[File]'; type = '[File]';
@ -217,10 +217,19 @@ class VirtualDirectory {
} }
var href = stub; var href = stub;
if (relative.isNotEmpty) href = '/' + relative + '/' + stub; if (relative.isNotEmpty) {
stub ??= '';
href = '/' + relative + '/' + stub;
}
if (entity is Directory) href += '/'; if (entity is Directory) {
href = Uri.encodeFull(href); if (href == null) {
href = '/';
} else {
href += '/';
}
}
href = Uri.encodeFull(href!);
res.write('<li><a href="$href">$type $stub</a></li>'); res.write('<li><a href="$href">$type $stub</a></li>');
} }
@ -233,11 +242,11 @@ class VirtualDirectory {
} }
void _ensureContentTypeAllowed(String mimeType, RequestContext req) { void _ensureContentTypeAllowed(String mimeType, RequestContext req) {
var value = req.headers.value('accept'); var value = req.headers!.value('accept');
var acceptable = value == null || var acceptable = value == null ||
value?.isNotEmpty != true || value.isNotEmpty != true ||
(mimeType?.isNotEmpty == true && value?.contains(mimeType) == true) || (mimeType.isNotEmpty == true && value.contains(mimeType) == true) ||
value?.contains('*/*') == true; value.contains('*/*') == true;
if (!acceptable) { if (!acceptable) {
throw AngelHttpException( throw AngelHttpException(
UnsupportedError( UnsupportedError(
@ -253,8 +262,9 @@ class VirtualDirectory {
res.headers['accept-ranges'] = 'bytes'; res.headers['accept-ranges'] = 'bytes';
if (callback != null) { if (callback != null) {
return await req.app.executeHandler( return await req.app!.executeHandler(
(RequestContext req, ResponseContext res) => callback(file, req, res), (RequestContext req, ResponseContext res) =>
callback!(file, req, res),
req, req,
res); res);
} }
@ -267,10 +277,10 @@ class VirtualDirectory {
res.contentType = MediaType.parse(type); res.contentType = MediaType.parse(type);
if (useBuffer == true) res.useBuffer(); if (useBuffer == true) res.useBuffer();
if (req.headers.value('range')?.startsWith('bytes=') != true) { if (req.headers!.value('range')?.startsWith('bytes=') != true) {
await res.streamFile(file); await res.streamFile(file);
} else { } else {
var header = RangeHeader.parse(req.headers.value('range')); var header = RangeHeader.parse(req.headers!.value('range')!);
var items = RangeHeader.foldItems(header.items); var items = RangeHeader.foldItems(header.items);
var totalFileSize = await file.length(); var totalFileSize = await file.length();
header = RangeHeader(items); header = RangeHeader(items);

View file

@ -1,8 +1,8 @@
name: angel_static name: angel_static
description: Static server middleware for Angel. Also capable of serving Range responses. description: Static server middleware for Angel. Also capable of serving Range responses.
version: 3.0.0 version: 4.0.0
environment: environment:
sdk: ">=2.10.0 <3.0.0" sdk: '>=2.12.0 <3.0.0'
homepage: https://github.com/angel-dart/static homepage: https://github.com/angel-dart/static
author: Tobe O <thosakwe@gmail.com> author: Tobe O <thosakwe@gmail.com>
publish_to: none publish_to: none
@ -12,21 +12,26 @@ dependencies:
url: https://github.com/dukefirehawk/angel.git url: https://github.com/dukefirehawk/angel.git
ref: sdk-2.12.x_nnbd ref: sdk-2.12.x_nnbd
path: packages/framework path: packages/framework
range_header:
git:
url: https://github.com/dukefirehawk/angel.git
ref: sdk-2.12.x_nnbd
path: packages/range_header
convert: ^3.0.0 convert: ^3.0.0
crypto: ^3.0.0 crypto: ^3.0.1
file: ^6.1.0 file: ^6.1.0
http_parser: ^4.0.0 http_parser: ^4.0.0
path: ^1.4.2 path: ^1.8.0
range_header: ^2.0.0
dev_dependencies: dev_dependencies:
angel_test: angel_test:
git: git:
url: https://github.com/dukefirehawk/angel.git url: https://github.com/dukefirehawk/angel.git
ref: sdk-2.12.x_nnbd ref: sdk-2.12.x_nnbd
path: packages/test path: packages/test
http: http: ^0.13.2
logging: ^1.0.0 logging: ^1.0.1
matcher: ^0.12.0 matcher: ^0.12.10
pedantic: ^1.0.0 pedantic: ^1.11.0
test: ^1.15.7 test: ^1.17.3

View file

@ -1,7 +1,6 @@
import 'package:angel_framework/angel_framework.dart'; import 'package:angel_framework/angel_framework.dart';
import 'package:angel_framework/http.dart'; import 'package:angel_framework/http.dart';
import 'package:angel_static/angel_static.dart'; import 'package:angel_static/angel_static.dart';
import 'package:file/file.dart';
import 'package:file/local.dart'; import 'package:file/local.dart';
import 'package:http/http.dart' show Client; import 'package:http/http.dart' show Client;
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
@ -9,9 +8,9 @@ import 'package:test/test.dart';
void main() { void main() {
Angel app; Angel app;
AngelHttp http; late AngelHttp http;
var testDir = const LocalFileSystem().directory('test'); var testDir = const LocalFileSystem().directory('test');
String url; late String url;
var client = Client(); var client = Client();
setUp(() async { setUp(() async {
@ -42,7 +41,7 @@ void main() {
}); });
tearDown(() async { tearDown(() async {
if (http.server != null) await http.server.close(force: true); if (http.server != null) await http.server!.close(force: true);
}); });
test('can serve files, with correct Content-Type', () async { test('can serve files, with correct Content-Type', () async {

View file

@ -1,13 +1,12 @@
import 'package:angel_framework/angel_framework.dart'; import 'package:angel_framework/angel_framework.dart';
import 'package:angel_framework/http.dart'; import 'package:angel_framework/http.dart';
import 'package:angel_static/angel_static.dart'; import 'package:angel_static/angel_static.dart';
import 'package:file/file.dart';
import 'package:file/local.dart'; import 'package:file/local.dart';
main() async { void main() async {
Angel app; Angel app;
AngelHttp http; AngelHttp http;
Directory testDir = const LocalFileSystem().directory('test'); var testDir = const LocalFileSystem().directory('test');
app = Angel(); app = Angel();
http = AngelHttp(app); http = AngelHttp(app);

View file

@ -10,9 +10,9 @@ import 'package:test/test.dart';
void main() { void main() {
Angel app; Angel app;
AngelHttp http; late AngelHttp http;
var testDir = const LocalFileSystem().directory('test'); var testDir = const LocalFileSystem().directory('test');
String url; late String url;
var client = Client(); var client = Client();
setUp(() async { setUp(() async {
@ -42,7 +42,7 @@ void main() {
}); });
tearDown(() async { tearDown(() async {
if (http.server != null) await http.server.close(force: true); if (http.server != null) await http.server!.close(force: true);
}); });
test('sets etag, cache-control, expires, last-modified', () async { test('sets etag, cache-control, expires, last-modified', () async {

View file

@ -9,9 +9,9 @@ import 'package:test/test.dart';
final Directory swaggerUiDistDir = final Directory swaggerUiDistDir =
const LocalFileSystem().directory('test/node_modules/swagger-ui-dist'); const LocalFileSystem().directory('test/node_modules/swagger-ui-dist');
main() async { void main() async {
TestClient client; late TestClient client;
String swaggerUiCssContents, swaggerTestJsContents; late String swaggerUiCssContents, swaggerTestJsContents;
setUp(() async { setUp(() async {
// Load file contents // Load file contents

View file

@ -8,7 +8,7 @@ import 'package:test/test.dart';
void main() { void main() {
Angel app; Angel app;
MemoryFileSystem fileSystem; MemoryFileSystem fileSystem;
TestClient client; late TestClient client;
setUp(() async { setUp(() async {
fileSystem = MemoryFileSystem(); fileSystem = MemoryFileSystem();