Updated range_header
This commit is contained in:
parent
27d739864c
commit
bb6fa4bec1
13 changed files with 99 additions and 77 deletions
|
@ -1,3 +1,6 @@
|
|||
# 3.0.1
|
||||
* Resolve static analysis warnings
|
||||
|
||||
# 3.0.0
|
||||
* Migrated to work with Dart SDK 2.12.x NNBD
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# angel3_range_header
|
||||
[![version](https://img.shields.io/badge/pub-v3.0.0-brightgreen)](https://pub.dartlang.org/packages/angel3_range_header)
|
||||
[![version](https://img.shields.io/badge/pub-v3.0.1-brightgreen)](https://pub.dartlang.org/packages/angel3_range_header)
|
||||
[![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)
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
include: package:pedantic/analysis_options.yaml
|
||||
analyzer:
|
||||
strong-mode:
|
||||
implicit-casts: false
|
|
@ -3,7 +3,7 @@ import 'package:angel3_range_header/angel3_range_header.dart';
|
|||
|
||||
var file = File('some_video.mp4');
|
||||
|
||||
handleRequest(HttpRequest request) async {
|
||||
void handleRequest(HttpRequest request) async {
|
||||
// Parse the header
|
||||
var header =
|
||||
RangeHeader.parse(request.headers.value(HttpHeaders.rangeHeader)!);
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
//import 'package:angel_framework/http.dart';
|
||||
//import 'package:angel_static/angel_static.dart';
|
||||
|
||||
main() async {
|
||||
void main() async {
|
||||
/*
|
||||
var app = new Angel();
|
||||
var http = new AngelHttp(app);
|
||||
|
|
|
@ -17,15 +17,15 @@ class RangeHeaderTransformer
|
|||
|
||||
RangeHeaderTransformer(this.header, this.mimeType, this.totalLength,
|
||||
{String? boundary})
|
||||
: this.boundary = boundary ?? _randomString() {
|
||||
: boundary = boundary ?? _randomString() {
|
||||
if (header.items.isEmpty) {
|
||||
throw new ArgumentError('`header` cannot be null or empty.');
|
||||
throw ArgumentError('`header` cannot be null or empty.');
|
||||
}
|
||||
}
|
||||
|
||||
/// Computes the content length that will be written to a response, given a stream of the given [totalFileSize].
|
||||
int computeContentLength(int totalFileSize) {
|
||||
int len = 0;
|
||||
var len = 0;
|
||||
|
||||
for (var item in header.items) {
|
||||
if (item.start == -1) {
|
||||
|
@ -59,15 +59,15 @@ class RangeHeaderTransformer
|
|||
|
||||
@override
|
||||
Stream<List<int>> bind(Stream<List<int>> stream) {
|
||||
var ctrl = new StreamController<List<int>>();
|
||||
var ctrl = StreamController<List<int>>();
|
||||
|
||||
new Future(() async {
|
||||
Future(() async {
|
||||
var index = 0;
|
||||
var enqueued = new Queue<List<int>>();
|
||||
var q = new StreamQueue(stream);
|
||||
var enqueued = Queue<List<int>>();
|
||||
var q = StreamQueue(stream);
|
||||
|
||||
Future<List<int>> absorb(int length) async {
|
||||
var out = new BytesBuilder();
|
||||
var out = BytesBuilder();
|
||||
|
||||
while (out.length < length) {
|
||||
var remaining = length - out.length;
|
||||
|
@ -101,7 +101,7 @@ class RangeHeaderTransformer
|
|||
// If we get this far, and the stream is EMPTY, the user requested
|
||||
// too many bytes.
|
||||
if (out.length < length && enqueued.isEmpty && !(await q.hasNext)) {
|
||||
throw new StateError(
|
||||
throw StateError(
|
||||
'The range denoted is bigger than the size of the input stream.');
|
||||
}
|
||||
}
|
||||
|
@ -110,7 +110,7 @@ class RangeHeaderTransformer
|
|||
}
|
||||
|
||||
for (var item in header.items) {
|
||||
var chunk = new BytesBuilder();
|
||||
var chunk = BytesBuilder();
|
||||
|
||||
// Skip until we reach the start index.
|
||||
while (index < item.start) {
|
||||
|
@ -120,8 +120,12 @@ class RangeHeaderTransformer
|
|||
|
||||
// Next, absorb until we reach the end.
|
||||
if (item.end == -1) {
|
||||
while (enqueued.isNotEmpty) chunk.add(enqueued.removeFirst());
|
||||
while (await q.hasNext) chunk.add(await q.next);
|
||||
while (enqueued.isNotEmpty) {
|
||||
chunk.add(enqueued.removeFirst());
|
||||
}
|
||||
while (await q.hasNext) {
|
||||
chunk.add(await q.next);
|
||||
}
|
||||
} else {
|
||||
var remaining = item.end - index;
|
||||
chunk.add(await absorb(remaining));
|
||||
|
@ -141,23 +145,27 @@ class RangeHeaderTransformer
|
|||
|
||||
ctrl.add(utf8.encode('--$boundary--\r\n'));
|
||||
|
||||
ctrl.close();
|
||||
}).catchError(ctrl.addError);
|
||||
await ctrl.close();
|
||||
}).catchError((e) {
|
||||
ctrl.addError(e as Object);
|
||||
return null;
|
||||
});
|
||||
|
||||
return ctrl.stream;
|
||||
}
|
||||
}
|
||||
|
||||
var _rnd = new Random();
|
||||
var _rnd = Random();
|
||||
String _randomString(
|
||||
{int length: 32,
|
||||
String validChars:
|
||||
{int length = 32,
|
||||
String validChars =
|
||||
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'}) {
|
||||
var len = _rnd.nextInt((length - 10)) + 10;
|
||||
var buf = new StringBuffer();
|
||||
var buf = StringBuffer();
|
||||
|
||||
while (buf.length < len)
|
||||
while (buf.length < len) {
|
||||
buf.writeCharCode(validChars.codeUnitAt(_rnd.nextInt(validChars.length)));
|
||||
}
|
||||
|
||||
return buf.toString();
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
class RangeHeaderParseException extends FormatException {
|
||||
@override
|
||||
final String message;
|
||||
|
||||
RangeHeaderParseException(this.message);
|
||||
|
|
|
@ -6,8 +6,8 @@ import 'range_header.dart';
|
|||
import 'range_header_impl.dart';
|
||||
import 'range_header_item.dart';
|
||||
|
||||
final RegExp _rgxInt = new RegExp(r'[0-9]+');
|
||||
final RegExp _rgxWs = new RegExp(r'[ \n\r\t]');
|
||||
final RegExp _rgxInt = RegExp(r'[0-9]+');
|
||||
final RegExp _rgxWs = RegExp(r'[ \n\r\t]');
|
||||
|
||||
enum TokenType { RANGE_UNIT, COMMA, INT, DASH, EQUALS }
|
||||
|
||||
|
@ -19,27 +19,27 @@ class Token {
|
|||
}
|
||||
|
||||
List<Token> scan(String text, List<String> allowedRangeUnits) {
|
||||
List<Token> tokens = [];
|
||||
var scanner = new SpanScanner(text);
|
||||
var tokens = <Token>[];
|
||||
var scanner = SpanScanner(text);
|
||||
|
||||
while (!scanner.isDone) {
|
||||
// Skip whitespace
|
||||
scanner.scan(_rgxWs);
|
||||
|
||||
if (scanner.scanChar($comma))
|
||||
tokens.add(new Token(TokenType.COMMA, scanner.lastSpan));
|
||||
else if (scanner.scanChar($dash))
|
||||
tokens.add(new Token(TokenType.DASH, scanner.lastSpan));
|
||||
else if (scanner.scan(_rgxInt))
|
||||
tokens.add(new Token(TokenType.INT, scanner.lastSpan));
|
||||
else if (scanner.scanChar($equal))
|
||||
tokens.add(new Token(TokenType.EQUALS, scanner.lastSpan));
|
||||
else {
|
||||
bool matched = false;
|
||||
if (scanner.scanChar($comma)) {
|
||||
tokens.add(Token(TokenType.COMMA, scanner.lastSpan));
|
||||
} else if (scanner.scanChar($dash)) {
|
||||
tokens.add(Token(TokenType.DASH, scanner.lastSpan));
|
||||
} else if (scanner.scan(_rgxInt)) {
|
||||
tokens.add(Token(TokenType.INT, scanner.lastSpan));
|
||||
} else if (scanner.scanChar($equal)) {
|
||||
tokens.add(Token(TokenType.EQUALS, scanner.lastSpan));
|
||||
} else {
|
||||
var matched = false;
|
||||
|
||||
for (var unit in allowedRangeUnits) {
|
||||
if (scanner.scan(unit)) {
|
||||
tokens.add(new Token(TokenType.RANGE_UNIT, scanner.lastSpan));
|
||||
tokens.add(Token(TokenType.RANGE_UNIT, scanner.lastSpan));
|
||||
matched = true;
|
||||
break;
|
||||
}
|
||||
|
@ -47,8 +47,8 @@ List<Token> scan(String text, List<String> allowedRangeUnits) {
|
|||
|
||||
if (!matched) {
|
||||
var ch = scanner.readChar();
|
||||
throw new RangeHeaderParseException(
|
||||
'Unexpected character: "${new String.fromCharCode(ch)}"');
|
||||
throw RangeHeaderParseException(
|
||||
'Unexpected character: "${String.fromCharCode(ch)}"');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -68,20 +68,21 @@ class Parser {
|
|||
bool get done => _index >= tokens.length - 1;
|
||||
|
||||
RangeHeaderParseException _expected(String type) {
|
||||
int? offset = current?.span?.start.offset;
|
||||
var offset = current?.span?.start.offset;
|
||||
|
||||
if (offset == null) return new RangeHeaderParseException('Expected $type.');
|
||||
if (offset == null) return RangeHeaderParseException('Expected $type.');
|
||||
|
||||
Token? peek;
|
||||
|
||||
if (_index < tokens.length - 1) peek = tokens[_index + 1];
|
||||
|
||||
if (peek != null && peek.span != null) {
|
||||
return new RangeHeaderParseException(
|
||||
return RangeHeaderParseException(
|
||||
'Expected $type at offset $offset, found "${peek.span!.text}" instead. \nSource:\n${peek.span?.highlight() ?? peek.type}');
|
||||
} else
|
||||
return new RangeHeaderParseException(
|
||||
} else {
|
||||
return RangeHeaderParseException(
|
||||
'Expected $type at offset $offset, but the header string ended without one.\nSource:\n${current!.span?.highlight() ?? current!.type}');
|
||||
}
|
||||
}
|
||||
|
||||
bool next(TokenType type) {
|
||||
|
@ -91,8 +92,9 @@ class Parser {
|
|||
_index++;
|
||||
_current = tok;
|
||||
return true;
|
||||
} else
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
RangeHeader? parseRangeHeader() {
|
||||
|
@ -100,24 +102,27 @@ class Parser {
|
|||
var unit = current!.span!.text;
|
||||
next(TokenType.EQUALS); // Consume =, if any.
|
||||
|
||||
List<RangeHeaderItem> items = [];
|
||||
RangeHeaderItem? item = parseHeaderItem();
|
||||
var items = <RangeHeaderItem>[];
|
||||
var item = parseHeaderItem();
|
||||
|
||||
while (item != null) {
|
||||
items.add(item);
|
||||
// Parse comma
|
||||
if (next(TokenType.COMMA)) {
|
||||
item = parseHeaderItem();
|
||||
} else
|
||||
} else {
|
||||
item = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (items.isEmpty)
|
||||
if (items.isEmpty) {
|
||||
throw _expected('range');
|
||||
else
|
||||
return new RangeHeaderImpl(unit, items);
|
||||
} else
|
||||
} else {
|
||||
return RangeHeaderImpl(unit, items);
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
RangeHeaderItem? parseHeaderItem() {
|
||||
|
@ -126,18 +131,22 @@ class Parser {
|
|||
var start = int.parse(current!.span!.text);
|
||||
if (next(TokenType.DASH)) {
|
||||
if (next(TokenType.INT)) {
|
||||
return new RangeHeaderItem(start, int.parse(current!.span!.text));
|
||||
} else
|
||||
return new RangeHeaderItem(start);
|
||||
} else
|
||||
return RangeHeaderItem(start, int.parse(current!.span!.text));
|
||||
} else {
|
||||
return RangeHeaderItem(start);
|
||||
}
|
||||
} else {
|
||||
throw _expected('"-"');
|
||||
}
|
||||
} else if (next(TokenType.DASH)) {
|
||||
// i.e. -599
|
||||
if (next(TokenType.INT)) {
|
||||
return new RangeHeaderItem(-1, int.parse(current!.span!.text));
|
||||
} else
|
||||
return RangeHeaderItem(-1, int.parse(current!.span!.text));
|
||||
} else {
|
||||
throw _expected('integer');
|
||||
} else
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@ abstract class RangeHeader {
|
|||
/// 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 = Set<RangeHeaderItem>();
|
||||
var out = <RangeHeaderItem>{};
|
||||
|
||||
for (var item in items) {
|
||||
// Remove any overlapping items, consolidate them.
|
||||
|
@ -28,7 +28,7 @@ abstract class RangeHeader {
|
|||
out.add(item);
|
||||
}
|
||||
|
||||
return new UnmodifiableListView(out.toList()..sort());
|
||||
return UnmodifiableListView(out.toList()..sort());
|
||||
}
|
||||
|
||||
/// Attempts to parse a [RangeHeader] from its [text] representation.
|
||||
|
@ -40,9 +40,9 @@ abstract class RangeHeader {
|
|||
/// 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 parser = Parser(tokens);
|
||||
var header = parser.parseRangeHeader();
|
||||
if (header == null) {
|
||||
throw InvalidRangeHeaderException('Header is null');
|
||||
|
@ -57,9 +57,10 @@ abstract class RangeHeader {
|
|||
|
||||
class _ConstantRangeHeader implements RangeHeader {
|
||||
final Iterable<RangeHeaderItem> items_;
|
||||
@override
|
||||
final String? rangeUnit;
|
||||
|
||||
const _ConstantRangeHeader(this.items_, {this.rangeUnit: 'bytes'});
|
||||
const _ConstantRangeHeader(this.items_, {this.rangeUnit = 'bytes'});
|
||||
|
||||
@override
|
||||
UnmodifiableListView<RangeHeaderItem> get items =>
|
||||
|
|
|
@ -8,12 +8,12 @@ class RangeHeaderImpl implements RangeHeader {
|
|||
final List<RangeHeaderItem> _items = [];
|
||||
|
||||
RangeHeaderImpl(this.rangeUnit, [List<RangeHeaderItem> items = const []]) {
|
||||
this._items.addAll(items);
|
||||
_items.addAll(items);
|
||||
}
|
||||
|
||||
@override
|
||||
UnmodifiableListView<RangeHeaderItem> get items =>
|
||||
_cached ??= new UnmodifiableListView<RangeHeaderItem>(_items);
|
||||
_cached ??= UnmodifiableListView<RangeHeaderItem>(_items);
|
||||
|
||||
@override
|
||||
final String? rangeUnit;
|
||||
|
|
|
@ -14,8 +14,9 @@ class RangeHeaderItem implements Comparable<RangeHeaderItem> {
|
|||
|
||||
/// Joins two items together into the largest possible range.
|
||||
RangeHeaderItem consolidate(RangeHeaderItem other) {
|
||||
if (!(other.overlaps(this)))
|
||||
if (!(other.overlaps(this))) {
|
||||
throw ArgumentError('The two ranges do not overlap.');
|
||||
}
|
||||
return RangeHeaderItem(min(start, other.start), max(end, other.end));
|
||||
}
|
||||
|
||||
|
@ -56,12 +57,13 @@ class RangeHeaderItem implements Comparable<RangeHeaderItem> {
|
|||
|
||||
@override
|
||||
String toString() {
|
||||
if (start > -1 && end > -1)
|
||||
if (start > -1 && end > -1) {
|
||||
return '$start-$end';
|
||||
else if (start > -1)
|
||||
} else if (start > -1) {
|
||||
return '$start-';
|
||||
else
|
||||
} else {
|
||||
return '-$end';
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a representation of this instance suitable for a `Content-Range` header.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
name: angel3_range_header
|
||||
version: 3.0.0
|
||||
version: 3.0.1
|
||||
description: Range header parser for Dart. Beyond parsing, a stream transformer is included.
|
||||
homepage: https://github.com/dukefirehawk/angel/tree/angel3/packages/range_header
|
||||
environment:
|
||||
|
@ -11,9 +11,8 @@ dependencies:
|
|||
source_span: ^1.8.1
|
||||
string_scanner: ^1.1.0
|
||||
dev_dependencies:
|
||||
#angel_framework:
|
||||
#angel_static: ^2.0.0
|
||||
file: ^6.1.0
|
||||
http_parser: ^4.0.0
|
||||
logging: ^1.0.1
|
||||
test: ^1.17.4
|
||||
test: ^1.17.4
|
||||
pedantic: ^1.11.0
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
import 'package:angel3_range_header/angel3_range_header.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
import '../lib/angel3_range_header.dart';
|
||||
import '../lib/src/range_header.dart';
|
||||
|
||||
final Matcher throwsRangeParseException =
|
||||
throwsA(const TypeMatcher<RangeHeaderParseException>());
|
||||
|
||||
|
|
Loading…
Reference in a new issue