Updated static
This commit is contained in:
parent
89d710fe28
commit
cadbc1c4f7
6 changed files with 160 additions and 83 deletions
|
@ -1,80 +1,110 @@
|
||||||
# 4.0.0
|
# Change Log
|
||||||
|
|
||||||
|
## 4.0.1
|
||||||
|
|
||||||
|
* Fixed `push_state_test` unit test failure on Windows
|
||||||
|
* Fixed NNBD related issues
|
||||||
|
* Added logging to `VirtualDirectory` and `CachingVirtualDirectory` to log exception
|
||||||
|
|
||||||
|
## 4.0.0
|
||||||
|
|
||||||
* Migrated to support Dart SDK 2.12.x NNBD
|
* Migrated to support Dart SDK 2.12.x NNBD
|
||||||
|
|
||||||
# 3.0.0
|
## 3.0.0
|
||||||
|
|
||||||
* Migrated to work with Dart SDK 2.12.x Non NNBD
|
* Migrated to work with Dart SDK 2.12.x Non NNBD
|
||||||
|
|
||||||
# 2.1.3+2
|
## 2.1.3+2
|
||||||
|
|
||||||
* Prepare for upcoming change to File.openRead()
|
* Prepare for upcoming change to File.openRead()
|
||||||
|
|
||||||
# 2.1.3+1
|
## 2.1.3+1
|
||||||
|
|
||||||
* Apply control flow lints.
|
* Apply control flow lints.
|
||||||
|
|
||||||
# 2.1.3
|
## 2.1.3
|
||||||
|
|
||||||
* Apply lints.
|
* Apply lints.
|
||||||
* Pin to Dart `>=2.0.0 <3.0.0`.
|
* Pin to Dart `>=2.0.0 <3.0.0`.
|
||||||
* Use at least version `2.0.0-rc.0` of `angel_framework`.
|
* Use at least version `2.0.0-rc.0` of `angel_framework`.
|
||||||
|
|
||||||
# 2.1.2+1
|
## 2.1.2+1
|
||||||
|
|
||||||
* Fix a typo that prevented `Range` requests from working.
|
* Fix a typo that prevented `Range` requests from working.
|
||||||
|
|
||||||
# 2.1.2
|
## 2.1.2
|
||||||
|
|
||||||
* Patch support for range+streaming in Caching server.
|
* Patch support for range+streaming in Caching server.
|
||||||
|
|
||||||
# 2.1.1
|
## 2.1.1
|
||||||
|
|
||||||
* URI-encode paths in directory listing. This produces correct URL's, always.
|
* URI-encode paths in directory listing. This produces correct URL's, always.
|
||||||
|
|
||||||
# 2.1.0
|
## 2.1.0
|
||||||
|
|
||||||
* Include support for the `Range` header.
|
* Include support for the `Range` header.
|
||||||
* Use MD5 for etags, instead of a weak ETag.
|
* Use MD5 for etags, instead of a weak ETag.
|
||||||
|
|
||||||
# 2.0.2
|
## 2.0.2
|
||||||
|
|
||||||
* Fixed invalid HTML for directory listings.
|
* Fixed invalid HTML for directory listings.
|
||||||
|
|
||||||
# 2.0.1
|
## 2.0.1
|
||||||
|
|
||||||
* Remove use of `sendFile`.
|
* Remove use of `sendFile`.
|
||||||
* Add a `p.isWithin` check to ensure that paths do not escape the `source` directory.
|
* Add a `p.isWithin` check to ensure that paths do not escape the `source` directory.
|
||||||
* Handle `HEAD` requests.
|
* Handle `HEAD` requests.
|
||||||
|
|
||||||
# 2.0.0
|
## 2.0.0
|
||||||
|
|
||||||
* Upgrade dependencies to Angel 2 + file@5.
|
* Upgrade dependencies to Angel 2 + file@5.
|
||||||
* Replace `useStream` with `useBuffer`.
|
* Replace `useStream` with `useBuffer`.
|
||||||
* Remove `package:intl`, just use `HttpDate` instead.
|
* Remove `package:intl`, just use `HttpDate` instead.
|
||||||
|
|
||||||
# 1.3.0+1
|
## 1.3.0+1
|
||||||
|
|
||||||
* Dart 2 fixes.
|
* Dart 2 fixes.
|
||||||
* Enable optionally writing responses to the buffer instead of streaming.
|
* Enable optionally writing responses to the buffer instead of streaming.
|
||||||
|
|
||||||
# 1.3.0
|
## 1.3.0
|
||||||
|
|
||||||
* `pushState` uses `strict` mode when `accepts` is passed.
|
* `pushState` uses `strict` mode when `accepts` is passed.
|
||||||
|
|
||||||
# 1.3.0-alpha+2
|
## 1.3.0-alpha+2
|
||||||
|
|
||||||
* Added an `accepts` option to `pushState`.
|
* Added an `accepts` option to `pushState`.
|
||||||
* Added optional directory listings.
|
* Added optional directory listings.
|
||||||
|
|
||||||
# 1.3.0-alpha+1
|
## 1.3.0-alpha+1
|
||||||
|
|
||||||
* ETags once again only encode the first 50 bytes of files. Resolves [#27](https://github.com/angel-dart/static/issues/27).
|
* ETags once again only encode the first 50 bytes of files. Resolves [#27](https://github.com/angel-dart/static/issues/27).
|
||||||
|
|
||||||
# 1.3.0-alpha
|
## 1.3.0-alpha
|
||||||
|
|
||||||
* Removed file transformers.
|
* Removed file transformers.
|
||||||
* `VirtualDirectory` is no longer an `AngelPlugin`, and instead exposes a `handleRequest` middleware.
|
* `VirtualDirectory` is no longer an `AngelPlugin`, and instead exposes a `handleRequest` middleware.
|
||||||
* Added `pushState` to `VirtualDirectory`.
|
* Added `pushState` to `VirtualDirectory`.
|
||||||
|
|
||||||
# 1.2.5
|
## 1.2.5
|
||||||
|
|
||||||
* Fixed a bug where `onlyInProduction` was not properly adhered to.
|
* Fixed a bug where `onlyInProduction` was not properly adhered to.
|
||||||
* Fixed another bug where `Accept-Encoding` was not properly adhered to.
|
* Fixed another bug where `Accept-Encoding` was not properly adhered to.
|
||||||
* Setting `maxAge` to `null` will now prevent a `CachingVirtualDirectory` from sending an `Expires` header.
|
* Setting `maxAge` to `null` will now prevent a `CachingVirtualDirectory` from sending an `Expires` header.
|
||||||
* Pre-built assets can now be mass-deleted with `VirtualDirectory.cleanFromDisk()`.
|
* Pre-built assets can now be mass-deleted with `VirtualDirectory.cleanFromDisk()`.
|
||||||
Resolves [#22](https://github.com/angel-dart/static/issues/22).
|
Resolves [#22](https://github.com/angel-dart/static/issues/22).
|
||||||
|
|
||||||
# 1.2.4+1
|
## 1.2.4+1
|
||||||
|
|
||||||
Fixed a bug where `Accept-Encoding` was not properly adhered to.
|
Fixed a bug where `Accept-Encoding` was not properly adhered to.
|
||||||
|
|
||||||
# 1.2.4
|
## 1.2.4
|
||||||
Fixes https://github.com/angel-dart/angel/issues/44.
|
|
||||||
|
Fixes <https://github.com/angel-dart/angel/issues/44>.
|
||||||
|
|
||||||
* MIME types will now default to `application/octet-stream`.
|
* MIME types will now default to `application/octet-stream`.
|
||||||
* When `streamToIO` is `true`, the body will only be sent gzipped if the request explicitly allows it.
|
* When `streamToIO` is `true`, the body will only be sent gzipped if the request explicitly allows it.
|
||||||
|
|
||||||
# 1.2.3
|
## 1.2.3
|
||||||
|
|
||||||
Fixed #40 and #41, which dealt with paths being improperly served when using a
|
Fixed #40 and #41, which dealt with paths being improperly served when using a
|
||||||
`publicPath`.
|
`publicPath`.
|
||||||
|
|
|
@ -1,16 +1,17 @@
|
||||||
# angel3_static
|
# Angel3 Static Files Service
|
||||||
|
|
||||||
[![version](https://img.shields.io/badge/pub-v4.0.1-brightgreen)](https://pub.dartlang.org/packages/angel3_static)
|
[![version](https://img.shields.io/badge/pub-v4.0.1-brightgreen)](https://pub.dartlang.org/packages/angel3_static)
|
||||||
[![Null Safety](https://img.shields.io/badge/null-safety-brightgreen)](https://dart.dev/null-safety)
|
[![Null Safety](https://img.shields.io/badge/null-safety-brightgreen)](https://dart.dev/null-safety)
|
||||||
[![Gitter](https://img.shields.io/gitter/room/angel_dart/discussion)](https://gitter.im/angel_dart/discussion)
|
[![Gitter](https://img.shields.io/gitter/room/angel_dart/discussion)](https://gitter.im/angel_dart/discussion)
|
||||||
|
|
||||||
[![License](https://img.shields.io/github/license/dukefirehawk/angel)](https://github.com/dukefirehawk/angel/tree/angel3/packages/static/LICENSE)
|
[![License](https://img.shields.io/github/license/dukefirehawk/angel)](https://github.com/dukefirehawk/angel/tree/angel3/packages/static/LICENSE)
|
||||||
|
|
||||||
|
This package supports serving static files such as html, css and js for [Angel3 framework](https://pub.dartlang.org/packages/angel3).
|
||||||
Static server infrastructure for Angel.
|
|
||||||
|
|
||||||
*Can also handle `Range` requests now, making it suitable for media streaming, ex. music, video, etc.*
|
*Can also handle `Range` requests now, making it suitable for media streaming, ex. music, video, etc.*
|
||||||
|
|
||||||
# Installation
|
## Installation
|
||||||
|
|
||||||
In `pubspec.yaml`:
|
In `pubspec.yaml`:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
|
@ -18,9 +19,9 @@ dependencies:
|
||||||
angel3_static: ^4.0.0
|
angel3_static: ^4.0.0
|
||||||
```
|
```
|
||||||
|
|
||||||
# Usage
|
## Usage
|
||||||
To serve files from a directory, you need to create a `VirtualDirectory`.
|
|
||||||
Keep in mind that `angel3_static` uses `package:file` instead of `dart:io`.
|
To serve files from a directory, you need to create a `VirtualDirectory`. Keep in mind that `angel3_static` uses `package:file` instead of `dart:io`.
|
||||||
|
|
||||||
```dart
|
```dart
|
||||||
import 'package:angel3_framework/angel3_framework.dart';
|
import 'package:angel3_framework/angel3_framework.dart';
|
||||||
|
@ -46,10 +47,9 @@ void main() async {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
# Push State
|
## Push State
|
||||||
`VirtualDirectory` also exposes a `pushState` method that returns a
|
|
||||||
request handler that serves the file at a given path as a fallback, unless
|
`VirtualDirectory` also exposes a `pushState` method that returns a request handler that serves the file at a given path as a fallback, unless the user is requesting that file. This can be very useful for SPA's.
|
||||||
the user is requesting that file. This can be very useful for SPA's.
|
|
||||||
|
|
||||||
```dart
|
```dart
|
||||||
// Create VirtualDirectory as well
|
// Create VirtualDirectory as well
|
||||||
|
@ -62,8 +62,10 @@ app.fallback(vDir.handleRequest);
|
||||||
app.fallback(vDir.pushState('index.html'));
|
app.fallback(vDir.pushState('index.html'));
|
||||||
```
|
```
|
||||||
|
|
||||||
# Options
|
## Options
|
||||||
|
|
||||||
The `VirtualDirectory` API accepts a few named parameters:
|
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
|
- **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`.
|
`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']`.
|
- **indexFileNames**: A `List<String>` of filenames that should be served as index pages. Default is `['index.html']`.
|
||||||
|
@ -71,4 +73,4 @@ The `VirtualDirectory` API accepts a few named parameters:
|
||||||
angel_static is serving your files. If you are not serving static files at the site root,
|
angel_static is serving your files. If you are not serving static files at the site root,
|
||||||
please include this.
|
please include this.
|
||||||
- **callback**: Runs before sending a file to a client. Use this to set headers, etc. If it returns anything other than `null` or `true`,
|
- **callback**: Runs before sending a file to a client. Use this to set headers, etc. If it returns anything other than `null` or `true`,
|
||||||
then the callback's result will be sent to the user, instead of the file contents.
|
then the callback's result will be sent to the user, instead of the file contents.
|
||||||
|
|
|
@ -2,6 +2,7 @@ import 'dart:async';
|
||||||
import 'dart:io' show HttpDate;
|
import 'dart:io' show HttpDate;
|
||||||
import 'package:angel3_framework/angel3_framework.dart';
|
import 'package:angel3_framework/angel3_framework.dart';
|
||||||
import 'package:file/file.dart';
|
import 'package:file/file.dart';
|
||||||
|
import 'package:logging/logging.dart';
|
||||||
import 'virtual_directory.dart';
|
import 'virtual_directory.dart';
|
||||||
|
|
||||||
/// Returns a string representation of the given [CacheAccessLevel].
|
/// Returns a string representation of the given [CacheAccessLevel].
|
||||||
|
@ -18,6 +19,8 @@ String accessLevelToString(CacheAccessLevel accessLevel) {
|
||||||
|
|
||||||
/// A `VirtualDirectory` that also sets `Cache-Control` headers.
|
/// A `VirtualDirectory` that also sets `Cache-Control` headers.
|
||||||
class CachingVirtualDirectory extends VirtualDirectory {
|
class CachingVirtualDirectory extends VirtualDirectory {
|
||||||
|
final _log = Logger('CachingVirtualDirectory');
|
||||||
|
|
||||||
final Map<String, String> _etags = {};
|
final Map<String, String> _etags = {};
|
||||||
|
|
||||||
/// Either `PUBLIC` or `PRIVATE`.
|
/// Either `PUBLIC` or `PRIVATE`.
|
||||||
|
@ -40,20 +43,20 @@ 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 = false,
|
||||||
Iterable<String>? indexFileNames,
|
Iterable<String> indexFileNames = const ['index.html'],
|
||||||
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 = false,
|
||||||
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,
|
||||||
publicPath: publicPath ?? '/',
|
publicPath: publicPath,
|
||||||
callback: callback,
|
callback: callback,
|
||||||
allowDirectoryListing: allowDirectoryListing,
|
allowDirectoryListing: allowDirectoryListing,
|
||||||
useBuffer: useBuffer);
|
useBuffer: useBuffer);
|
||||||
|
@ -63,27 +66,35 @@ 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (req.headers == null) {
|
||||||
|
_log.severe('Missing headers in the RequestContext');
|
||||||
|
throw ArgumentError('Missing headers in the RequestContext');
|
||||||
|
}
|
||||||
|
var reqHeaders = req.headers!;
|
||||||
|
|
||||||
var shouldNotCache = noCache == true;
|
var shouldNotCache = noCache == true;
|
||||||
|
|
||||||
if (!shouldNotCache) {
|
if (!shouldNotCache) {
|
||||||
shouldNotCache = req.headers!.value('cache-control') == 'no-cache' ||
|
shouldNotCache = reqHeaders.value('cache-control') == 'no-cache' ||
|
||||||
req.headers!.value('pragma') == 'no-cache';
|
reqHeaders.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 = reqHeaders.ifModifiedSince;
|
||||||
var ifRange = false;
|
var ifRange = false;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
ifModified = HttpDate.parse(req.headers!.value('if-range')!);
|
if (reqHeaders.value('if-range') != null) {
|
||||||
ifRange = true;
|
ifModified = HttpDate.parse(reqHeaders.value('if-range')!);
|
||||||
|
ifRange = true;
|
||||||
|
}
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
// Fail silently...
|
// Fail silently...
|
||||||
}
|
}
|
||||||
|
@ -97,7 +108,9 @@ 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]!;
|
if (_etags[file.absolute.path] != null) {
|
||||||
|
res.headers['ETag'] = _etags[file.absolute.path]!;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ifRange) {
|
if (ifRange) {
|
||||||
|
@ -111,6 +124,8 @@ class CachingVirtualDirectory extends VirtualDirectory {
|
||||||
return super.serveFile(file, stat, req, res);
|
return super.serveFile(file, stat, req, res);
|
||||||
}
|
}
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
|
_log.severe(
|
||||||
|
'Invalid date for ${ifRange ? 'if-range' : 'if-not-modified-since'} header.');
|
||||||
throw AngelHttpException.badRequest(
|
throw AngelHttpException.badRequest(
|
||||||
message:
|
message:
|
||||||
'Invalid date for ${ifRange ? 'if-range' : 'if-not-modified-since'} header.');
|
'Invalid date for ${ifRange ? 'if-range' : 'if-not-modified-since'} header.');
|
||||||
|
@ -120,18 +135,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 = reqHeaders['if-none-match'] ?? [];
|
||||||
ifRange = false;
|
ifRange = false;
|
||||||
|
|
||||||
if (etagsToMatchAgainst?.isNotEmpty != true) {
|
if (etagsToMatchAgainst.isEmpty) {
|
||||||
etagsToMatchAgainst = req.headers!['if-range'];
|
etagsToMatchAgainst = reqHeaders['if-range'] ?? [];
|
||||||
ifRange = etagsToMatchAgainst?.isNotEmpty == true;
|
ifRange = etagsToMatchAgainst.isNotEmpty;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (etagsToMatchAgainst?.isNotEmpty == true) {
|
if (etagsToMatchAgainst.isNotEmpty) {
|
||||||
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 {
|
||||||
|
|
|
@ -3,6 +3,7 @@ import 'package:angel3_framework/angel3_framework.dart';
|
||||||
import 'package:file/file.dart';
|
import 'package:file/file.dart';
|
||||||
import 'package:http_parser/http_parser.dart';
|
import 'package:http_parser/http_parser.dart';
|
||||||
import 'package:path/path.dart' as p;
|
import 'package:path/path.dart' as p;
|
||||||
|
import 'package:logging/logging.dart';
|
||||||
import 'package:angel3_range_header/angel3_range_header.dart';
|
import 'package:angel3_range_header/angel3_range_header.dart';
|
||||||
|
|
||||||
final RegExp _param = RegExp(r':([A-Za-z0-9_]+)(\((.+)\))?');
|
final RegExp _param = RegExp(r':([A-Za-z0-9_]+)(\((.+)\))?');
|
||||||
|
@ -28,11 +29,13 @@ String _pathify(String path) {
|
||||||
|
|
||||||
/// A static server plug-in.
|
/// A static server plug-in.
|
||||||
class VirtualDirectory {
|
class VirtualDirectory {
|
||||||
String? _prefix;
|
final _log = Logger('VirtualDirectory');
|
||||||
Directory? _source;
|
|
||||||
|
late String _prefix;
|
||||||
|
late 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;
|
||||||
|
@ -47,7 +50,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.
|
||||||
///
|
///
|
||||||
|
@ -77,7 +80,7 @@ class VirtualDirectory {
|
||||||
}
|
}
|
||||||
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -91,7 +94,7 @@ class VirtualDirectory {
|
||||||
/// 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';
|
||||||
|
|
||||||
return (RequestContext req, ResponseContext res) {
|
return (RequestContext req, ResponseContext res) {
|
||||||
var path = req.path.replaceAll(_straySlashes, '');
|
var path = req.path.replaceAll(_straySlashes, '');
|
||||||
|
@ -110,22 +113,28 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Replace the separator when running on Windows with file system
|
||||||
|
// detected as Linux
|
||||||
|
if (absolute.contains('\\') && fileSystem.path.separator == '/') {
|
||||||
|
absolute = absolute.replaceAll('\\', '/');
|
||||||
|
}
|
||||||
|
|
||||||
var stat = await fileSystem.stat(absolute);
|
var stat = await fileSystem.stat(absolute);
|
||||||
return await serveStat(absolute, path, stat, req, res);
|
return await serveStat(absolute, path, stat, req, res);
|
||||||
}
|
}
|
||||||
|
@ -199,8 +208,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]';
|
||||||
|
@ -213,23 +222,24 @@ class VirtualDirectory {
|
||||||
stub = p.basename(entity.path);
|
stub = p.basename(entity.path);
|
||||||
} else {
|
} else {
|
||||||
//TODO: Handle unknown type
|
//TODO: Handle unknown type
|
||||||
|
_log.severe('Unknown file entity. Not a file, directory or link.');
|
||||||
|
type = '[]';
|
||||||
|
stub = '';
|
||||||
}
|
}
|
||||||
var href = stub;
|
var href = stub;
|
||||||
|
|
||||||
if (relative.isNotEmpty) {
|
if (relative.isNotEmpty) {
|
||||||
stub ??= '';
|
|
||||||
href = '/' + relative + '/' + stub;
|
href = '/' + relative + '/' + stub;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (entity is Directory) {
|
if (entity is Directory) {
|
||||||
if (href == null) {
|
if (href == '') {
|
||||||
href = '/';
|
href = '/';
|
||||||
} else {
|
} else {
|
||||||
href += '/';
|
href += '/';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
href = Uri.encodeFull(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>');
|
||||||
}
|
}
|
||||||
|
@ -242,12 +252,13 @@ 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) {
|
||||||
|
_log.severe('Mime type [$value] is not supported');
|
||||||
throw AngelHttpException(
|
throw AngelHttpException(
|
||||||
UnsupportedError(
|
UnsupportedError(
|
||||||
'Client requested $value, but server wanted to send $mimeType.'),
|
'Client requested $value, but server wanted to send $mimeType.'),
|
||||||
|
@ -262,11 +273,12 @@ 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) =>
|
(RequestContext req, ResponseContext res) =>
|
||||||
callback!(file, req, res),
|
callback!(file, req, res),
|
||||||
req,
|
req,
|
||||||
res);
|
res) ??
|
||||||
|
true;
|
||||||
}
|
}
|
||||||
|
|
||||||
var type =
|
var type =
|
||||||
|
@ -277,14 +289,21 @@ 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 == null) {
|
||||||
|
_log.severe('Missing headers in the RequestContext');
|
||||||
|
throw ArgumentError('Missing headers in the RequestContext');
|
||||||
|
}
|
||||||
|
var reqHeaders = req.headers!;
|
||||||
|
|
||||||
|
if (reqHeaders.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(reqHeaders.value('range')!);
|
||||||
var items = RangeHeader.foldItems(header.items);
|
var items = RangeHeader.foldItems(header.items);
|
||||||
var totalFileSize = await file.length();
|
|
||||||
header = RangeHeader(items);
|
header = RangeHeader(items);
|
||||||
|
|
||||||
|
var totalFileSize = await file.length();
|
||||||
|
|
||||||
for (var item in header.items) {
|
for (var item in header.items) {
|
||||||
var invalid = false;
|
var invalid = false;
|
||||||
|
|
||||||
|
|
|
@ -1,21 +1,22 @@
|
||||||
name: angel3_static
|
name: angel3_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: 4.0.0
|
version: 4.0.1
|
||||||
homepage: https://github.com/dukefirehawk/angel/tree/angel3/packages/static
|
homepage: https://github.com/dukefirehawk/angel
|
||||||
|
repository: https://github.com/dukefirehawk/angel/tree/angel3/packages/static
|
||||||
environment:
|
environment:
|
||||||
sdk: '>=2.12.0 <3.0.0'
|
sdk: '>=2.12.0 <3.0.0'
|
||||||
dependencies:
|
dependencies:
|
||||||
angel3_framework: ^4.0.0
|
angel3_framework: ^4.1.0
|
||||||
angel3_range_header: ^3.0.0
|
angel3_range_header: ^3.0.0
|
||||||
convert: ^3.0.0
|
convert: ^3.0.0
|
||||||
crypto: ^3.0.1
|
crypto: ^3.0.1
|
||||||
file: ^6.1.0
|
file: ^6.1.0
|
||||||
http_parser: ^4.0.0
|
http_parser: ^4.0.0
|
||||||
path: ^1.8.0
|
path: ^1.8.0
|
||||||
|
logging: ^1.0.1
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
angel3_test: ^4.0.0
|
angel3_test: ^4.0.0
|
||||||
http: ^0.13.2
|
http: ^0.13.2
|
||||||
logging: ^1.0.1
|
|
||||||
matcher: ^0.12.10
|
matcher: ^0.12.10
|
||||||
pedantic: ^1.11.0
|
pedantic: ^1.11.0
|
||||||
test: ^1.17.4
|
test: ^1.17.4
|
||||||
|
|
10
packages/static/test/web/index.html
Normal file
10
packages/static/test/web/index.html
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Push Test</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Hello!</h1>
|
||||||
|
<i>Hooray for testing...</i>
|
||||||
|
</body>
|
||||||
|
</html>
|
Loading…
Reference in a new issue