Updated range_header

This commit is contained in:
thomashii 2021-05-18 19:58:51 +08:00
parent 27d739864c
commit bb6fa4bec1
13 changed files with 99 additions and 77 deletions

View file

@ -1,3 +1,6 @@
# 3.0.1
* Resolve static analysis warnings
# 3.0.0
* Migrated to work with Dart SDK 2.12.x NNBD

View file

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

View file

@ -1,3 +1,4 @@
include: package:pedantic/analysis_options.yaml
analyzer:
strong-mode:
implicit-casts: false

View file

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

View file

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

View file

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

View file

@ -1,4 +1,5 @@
class RangeHeaderParseException extends FormatException {
@override
final String message;
RangeHeaderParseException(this.message);

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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