Migrated range_header
This commit is contained in:
parent
1e92db46a6
commit
3b2fc97da1
11 changed files with 82 additions and 73 deletions
|
@ -26,7 +26,7 @@
|
|||
* Migrated production to 3.0.0 (0/0 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)
|
||||
* Added range_header and migrated to 2.0.0 (16/16 tests passed)
|
||||
* Added range_header and migrated to 3.0.0 (12/12 tests passed)
|
||||
* Updated static to 3.0.0 (in progress)
|
||||
* Update basic-sdk-2.12.x boilerplate (in progress)
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ var file = new File('some_video.mp4');
|
|||
handleRequest(HttpRequest request) async {
|
||||
// Parse the header
|
||||
var header =
|
||||
new RangeHeader.parse(request.headers.value(HttpHeaders.rangeHeader));
|
||||
new RangeHeader.parse(request.headers.value(HttpHeaders.rangeHeader)!);
|
||||
|
||||
// Optimize/canonicalize it
|
||||
var items = RangeHeader.foldItems(header.items);
|
||||
|
|
|
@ -1,16 +1,9 @@
|
|||
import 'dart:async';
|
||||
import 'dart:io' show HttpHeaders, HttpStatus;
|
||||
import 'dart:typed_data';
|
||||
import 'package:angel_framework/angel_framework.dart';
|
||||
import 'package:angel_framework/http.dart';
|
||||
import 'package:angel_static/angel_static.dart';
|
||||
import 'package:file/file.dart';
|
||||
import 'package:file/local.dart';
|
||||
import 'package:http_parser/http_parser.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:range_header/range_header.dart';
|
||||
//import 'package:angel_framework/angel_framework.dart';
|
||||
//import 'package:angel_framework/http.dart';
|
||||
//import 'package:angel_static/angel_static.dart';
|
||||
|
||||
main() async {
|
||||
/*
|
||||
var app = new Angel();
|
||||
var http = new AngelHttp(app);
|
||||
var fs = const LocalFileSystem();
|
||||
|
@ -32,8 +25,9 @@ main() async {
|
|||
app.fallback((req, res) => throw new AngelHttpException.notFound());
|
||||
await http.startServer('127.0.0.1', 3000);
|
||||
print('Listening at ${http.uri}');
|
||||
*/
|
||||
}
|
||||
|
||||
/*
|
||||
class _RangingVirtualDirectory extends VirtualDirectory {
|
||||
_RangingVirtualDirectory(Angel app, Directory source)
|
||||
: super(app, source.fileSystem,
|
||||
|
@ -106,3 +100,4 @@ class _RangingVirtualDirectory extends VirtualDirectory {
|
|||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
|
|
@ -16,9 +16,9 @@ class RangeHeaderTransformer
|
|||
final int totalLength;
|
||||
|
||||
RangeHeaderTransformer(this.header, this.mimeType, this.totalLength,
|
||||
{String boundary})
|
||||
{String? boundary})
|
||||
: this.boundary = boundary ?? _randomString() {
|
||||
if (header == null || header.items.isEmpty) {
|
||||
if (header.items.isEmpty) {
|
||||
throw new ArgumentError('`header` cannot be null or empty.');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,3 +6,12 @@ class RangeHeaderParseException extends FormatException {
|
|||
@override
|
||||
String toString() => 'Range header parse exception: $message';
|
||||
}
|
||||
|
||||
class InvalidRangeHeaderException implements Exception {
|
||||
final String message;
|
||||
|
||||
InvalidRangeHeaderException(this.message);
|
||||
|
||||
@override
|
||||
String toString() => 'Range header parse exception: $message';
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ enum TokenType { RANGE_UNIT, COMMA, INT, DASH, EQUALS }
|
|||
|
||||
class Token {
|
||||
final TokenType type;
|
||||
final SourceSpan span;
|
||||
final SourceSpan? span;
|
||||
|
||||
Token(this.type, this.span);
|
||||
}
|
||||
|
@ -57,31 +57,31 @@ List<Token> scan(String text, List<String> allowedRangeUnits) {
|
|||
}
|
||||
|
||||
class Parser {
|
||||
Token _current;
|
||||
Token? _current;
|
||||
int _index = -1;
|
||||
final List<Token> tokens;
|
||||
|
||||
Parser(this.tokens);
|
||||
|
||||
Token get current => _current;
|
||||
Token? get current => _current;
|
||||
|
||||
bool get done => _index >= tokens.length - 1;
|
||||
|
||||
RangeHeaderParseException _expected(String type) {
|
||||
int offset = current?.span?.start?.offset;
|
||||
int? offset = current?.span?.start.offset;
|
||||
|
||||
if (offset == null) return new RangeHeaderParseException('Expected $type.');
|
||||
|
||||
Token peek;
|
||||
Token? peek;
|
||||
|
||||
if (_index < tokens.length - 1) peek = tokens[_index + 1];
|
||||
|
||||
if (peek != null && peek.span != null) {
|
||||
return new RangeHeaderParseException(
|
||||
'Expected $type at offset $offset, found "${peek.span.text}" instead. \nSource:\n${peek.span?.highlight() ?? peek.type}');
|
||||
'Expected $type at offset $offset, found "${peek.span!.text}" instead. \nSource:\n${peek.span?.highlight() ?? peek.type}');
|
||||
} else
|
||||
return new RangeHeaderParseException(
|
||||
'Expected $type at offset $offset, but the header string ended without one.\nSource:\n${current.span?.highlight() ?? current.type}');
|
||||
'Expected $type at offset $offset, but the header string ended without one.\nSource:\n${current!.span?.highlight() ?? current!.type}');
|
||||
}
|
||||
|
||||
bool next(TokenType type) {
|
||||
|
@ -95,13 +95,13 @@ class Parser {
|
|||
return false;
|
||||
}
|
||||
|
||||
RangeHeader parseRangeHeader() {
|
||||
RangeHeader? parseRangeHeader() {
|
||||
if (next(TokenType.RANGE_UNIT)) {
|
||||
var unit = current.span.text;
|
||||
var unit = current!.span!.text;
|
||||
next(TokenType.EQUALS); // Consume =, if any.
|
||||
|
||||
List<RangeHeaderItem> items = [];
|
||||
RangeHeaderItem item = parseHeaderItem();
|
||||
RangeHeaderItem? item = parseHeaderItem();
|
||||
|
||||
while (item != null) {
|
||||
items.add(item);
|
||||
|
@ -120,13 +120,13 @@ class Parser {
|
|||
return null;
|
||||
}
|
||||
|
||||
RangeHeaderItem parseHeaderItem() {
|
||||
RangeHeaderItem? parseHeaderItem() {
|
||||
if (next(TokenType.INT)) {
|
||||
// i.e 500-544, or 600-
|
||||
var start = int.parse(current.span.text);
|
||||
var start = int.parse(current!.span!.text);
|
||||
if (next(TokenType.DASH)) {
|
||||
if (next(TokenType.INT)) {
|
||||
return new RangeHeaderItem(start, int.parse(current.span.text));
|
||||
return new RangeHeaderItem(start, int.parse(current!.span!.text));
|
||||
} else
|
||||
return new RangeHeaderItem(start);
|
||||
} else
|
||||
|
@ -134,7 +134,7 @@ class Parser {
|
|||
} else if (next(TokenType.DASH)) {
|
||||
// i.e. -599
|
||||
if (next(TokenType.INT)) {
|
||||
return new RangeHeaderItem(-1, int.parse(current.span.text));
|
||||
return new RangeHeaderItem(-1, int.parse(current!.span!.text));
|
||||
} else
|
||||
throw _expected('integer');
|
||||
} else
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import 'dart:collection';
|
||||
import 'exception.dart';
|
||||
import 'parser.dart';
|
||||
import 'range_header_item.dart';
|
||||
import 'range_header_impl.dart';
|
||||
|
@ -9,12 +10,12 @@ abstract class RangeHeader {
|
|||
UnmodifiableListView<RangeHeaderItem> get items;
|
||||
|
||||
const factory RangeHeader(Iterable<RangeHeaderItem> items,
|
||||
{String rangeUnit}) = _ConstantRangeHeader;
|
||||
{String? rangeUnit}) = _ConstantRangeHeader;
|
||||
|
||||
/// Eliminates any overlapping [items], sorts them, and folds them all into the most efficient representation possible.
|
||||
static UnmodifiableListView<RangeHeaderItem> foldItems(
|
||||
Iterable<RangeHeaderItem> items) {
|
||||
var out = new Set<RangeHeaderItem>();
|
||||
var out = Set<RangeHeaderItem>();
|
||||
|
||||
for (var item in items) {
|
||||
// Remove any overlapping items, consolidate them.
|
||||
|
@ -37,27 +38,30 @@ abstract class RangeHeader {
|
|||
///
|
||||
/// If [fold] is `true`, the items will be folded into the most compact
|
||||
/// possible representation.
|
||||
///
|
||||
factory RangeHeader.parse(String text,
|
||||
{Iterable<String> allowedRangeUnits, bool fold: true}) {
|
||||
{Iterable<String>? allowedRangeUnits, bool fold: true}) {
|
||||
var tokens = scan(text, allowedRangeUnits?.toList() ?? ['bytes']);
|
||||
var parser = new Parser(tokens);
|
||||
var header = parser.parseRangeHeader();
|
||||
if (header == null) return null;
|
||||
if (header == null) {
|
||||
throw InvalidRangeHeaderException('Header is null');
|
||||
}
|
||||
var items = foldItems(header.items);
|
||||
return RangeHeaderImpl(header.rangeUnit, items);
|
||||
}
|
||||
|
||||
/// Returns this header's range unit. Most commonly, this is `bytes`.
|
||||
String get rangeUnit;
|
||||
String? get rangeUnit;
|
||||
}
|
||||
|
||||
class _ConstantRangeHeader implements RangeHeader {
|
||||
final Iterable<RangeHeaderItem> items_;
|
||||
final String rangeUnit;
|
||||
final String? rangeUnit;
|
||||
|
||||
const _ConstantRangeHeader(this.items_, {this.rangeUnit: 'bytes'});
|
||||
|
||||
@override
|
||||
UnmodifiableListView<RangeHeaderItem> get items =>
|
||||
new UnmodifiableListView(items_);
|
||||
UnmodifiableListView(items_);
|
||||
}
|
||||
|
|
|
@ -4,11 +4,11 @@ import 'range_header_item.dart';
|
|||
|
||||
/// Represents the contents of a parsed `Range` header.
|
||||
class RangeHeaderImpl implements RangeHeader {
|
||||
UnmodifiableListView<RangeHeaderItem> _cached;
|
||||
UnmodifiableListView<RangeHeaderItem>? _cached;
|
||||
final List<RangeHeaderItem> _items = [];
|
||||
|
||||
RangeHeaderImpl(this.rangeUnit, [List<RangeHeaderItem> items = const []]) {
|
||||
this._items.addAll(items ?? []);
|
||||
this._items.addAll(items);
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -16,5 +16,5 @@ class RangeHeaderImpl implements RangeHeader {
|
|||
_cached ??= new UnmodifiableListView<RangeHeaderItem>(_items);
|
||||
|
||||
@override
|
||||
final String rangeUnit;
|
||||
final String? rangeUnit;
|
||||
}
|
||||
|
|
|
@ -71,7 +71,7 @@ class RangeHeaderItem implements Comparable<RangeHeaderItem> {
|
|||
/// Please adhere to the standard!!!
|
||||
/// http://httpwg.org/specs/rfc7233.html
|
||||
|
||||
String toContentRange([int totalSize]) {
|
||||
String toContentRange([int? totalSize]) {
|
||||
// var maxIndex = totalSize != null ? (totalSize - 1).toString() : '*';
|
||||
var s = start > -1 ? start : 0;
|
||||
|
||||
|
|
|
@ -1,20 +1,20 @@
|
|||
name: range_header
|
||||
version: 2.0.2+2
|
||||
version: 3.0.0
|
||||
description: Range header parser for Dart. Beyond parsing, a stream transformer is included.
|
||||
author: Tobe O <thosakwe@gmail.com>
|
||||
homepage: https://github.com/thosakwe/range_header
|
||||
environment:
|
||||
sdk: ">=2.0.0-dev <3.0.0"
|
||||
sdk: '>=2.12.0 <3.0.0'
|
||||
dependencies:
|
||||
async: ^2.0.0
|
||||
charcode: ^1.0.0
|
||||
quiver_hashcode: ^2.0.0
|
||||
source_span: ^1.0.0
|
||||
string_scanner: ^1.0.0
|
||||
async: ^2.6.0
|
||||
charcode: ^1.2.0
|
||||
quiver_hashcode: ^3.0.0+1
|
||||
source_span: ^1.8.1
|
||||
string_scanner: ^1.1.0
|
||||
dev_dependencies:
|
||||
angel_framework:
|
||||
angel_static: ^2.0.0
|
||||
file:
|
||||
http_parser: ^3.0.0
|
||||
logging: ^0.11.0
|
||||
test: ^1.0.0
|
||||
#angel_framework:
|
||||
#angel_static: ^2.0.0
|
||||
file: ^6.1.0
|
||||
http_parser: ^4.0.0
|
||||
logging: ^1.0.1
|
||||
test: ^1.17.3
|
|
@ -1,27 +1,32 @@
|
|||
import 'package:range_header/range_header.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
import '../lib/range_header.dart';
|
||||
import '../lib/src/range_header.dart';
|
||||
|
||||
final Matcher throwsRangeParseException =
|
||||
throwsA(const TypeMatcher<RangeHeaderParseException>());
|
||||
|
||||
final Matcher throwsInvalidRangeHeaderException =
|
||||
throwsA(const TypeMatcher<InvalidRangeHeaderException>());
|
||||
|
||||
main() {
|
||||
group('one item', () {
|
||||
test('start and end', () {
|
||||
var r = new RangeHeader.parse('bytes 1-200');
|
||||
var r = RangeHeader.parse('bytes 1-200');
|
||||
expect(r.items, hasLength(1));
|
||||
expect(r.items.first.start, 1);
|
||||
expect(r.items.first.end, 200);
|
||||
});
|
||||
|
||||
test('start only', () {
|
||||
var r = new RangeHeader.parse('bytes 1-');
|
||||
var r = RangeHeader.parse('bytes 1-');
|
||||
expect(r.items, hasLength(1));
|
||||
expect(r.items.first.start, 1);
|
||||
expect(r.items.first.end, -1);
|
||||
});
|
||||
|
||||
test('end only', () {
|
||||
var r = new RangeHeader.parse('bytes -200');
|
||||
var r = RangeHeader.parse('bytes -200');
|
||||
print(r.items);
|
||||
expect(r.items, hasLength(1));
|
||||
expect(r.items.first.start, -1);
|
||||
|
@ -31,7 +36,7 @@ main() {
|
|||
|
||||
group('multiple items', () {
|
||||
test('three items', () {
|
||||
var r = new RangeHeader.parse('bytes 1-20, 21-40, 41-60');
|
||||
var r = RangeHeader.parse('bytes 1-20, 21-40, 41-60');
|
||||
print(r.items);
|
||||
expect(r.items, hasLength(3));
|
||||
expect(r.items[0].start, 1);
|
||||
|
@ -43,7 +48,7 @@ main() {
|
|||
});
|
||||
|
||||
test('one item without end', () {
|
||||
var r = new RangeHeader.parse('bytes 1-20, 21-');
|
||||
var r = RangeHeader.parse('bytes 1-20, 21-');
|
||||
print(r.items);
|
||||
expect(r.items, hasLength(2));
|
||||
expect(r.items[0].start, 1);
|
||||
|
@ -55,42 +60,38 @@ main() {
|
|||
|
||||
group('failures', () {
|
||||
test('no start with no end', () {
|
||||
expect(new RangeHeader.parse('-'), isNull);
|
||||
expect(() => RangeHeader.parse('-'), throwsInvalidRangeHeaderException);
|
||||
});
|
||||
});
|
||||
|
||||
group('exceptions', () {
|
||||
test('invalid character', () {
|
||||
expect(() => new RangeHeader.parse('!!!'), throwsRangeParseException);
|
||||
expect(() => RangeHeader.parse('!!!'), throwsRangeParseException);
|
||||
});
|
||||
|
||||
test('no ranges', () {
|
||||
expect(() => new RangeHeader.parse('bytes'), throwsRangeParseException);
|
||||
expect(() => RangeHeader.parse('bytes'), throwsRangeParseException);
|
||||
});
|
||||
|
||||
test('no dash after int', () {
|
||||
expect(() => new RangeHeader.parse('bytes 3'), throwsRangeParseException);
|
||||
expect(
|
||||
() => new RangeHeader.parse('bytes 3,'), throwsRangeParseException);
|
||||
expect(
|
||||
() => new RangeHeader.parse('bytes 3 24'), throwsRangeParseException);
|
||||
expect(() => RangeHeader.parse('bytes 3'), throwsRangeParseException);
|
||||
expect(() => RangeHeader.parse('bytes 3,'), throwsRangeParseException);
|
||||
expect(() => RangeHeader.parse('bytes 3 24'), throwsRangeParseException);
|
||||
});
|
||||
|
||||
test('no int after dash', () {
|
||||
expect(
|
||||
() => new RangeHeader.parse('bytes -,'), throwsRangeParseException);
|
||||
expect(() => RangeHeader.parse('bytes -,'), throwsRangeParseException);
|
||||
});
|
||||
});
|
||||
|
||||
group('complete coverage', () {
|
||||
test('exception toString()', () {
|
||||
var m = new RangeHeaderParseException('hey');
|
||||
var m = RangeHeaderParseException('hey');
|
||||
expect(m.toString(), contains('hey'));
|
||||
});
|
||||
});
|
||||
|
||||
test('content-range', () {
|
||||
expect(
|
||||
new RangeHeader.parse('bytes 1-2').items[0].toContentRange(3), '1-2/3');
|
||||
expect(RangeHeader.parse('bytes 1-2').items[0].toContentRange(3), '1-2/3');
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue