Migrated range_header

This commit is contained in:
thomashii@dukefirehawk.com 2021-05-01 11:39:09 +08:00
parent 1e92db46a6
commit 3b2fc97da1
11 changed files with 82 additions and 73 deletions

View file

@ -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)

View file

@ -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);

View file

@ -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 {
}
}
}
*/

View file

@ -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.');
}
}

View file

@ -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';
}

View file

@ -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

View file

@ -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_);
}

View file

@ -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;
}

View file

@ -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;

View file

@ -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

View file

@ -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');
});
}