Fix an issue with range support, prepare for release (#62)
Removed content-length header when sending a 4xx response
Added corresponding test
Also cleaned up the implementation by DRYing up access to regex matches
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 60be005..a885d2a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,7 @@
-## 1.1.0-dev
+## 1.1.0
* Correctly handle `HEAD` requests.
+* Support HTTP range requests.
## 1.0.0
diff --git a/lib/src/static_handler.dart b/lib/src/static_handler.dart
index 533fe93..c290d95 100644
--- a/lib/src/static_handler.dart
+++ b/lib/src/static_handler.dart
@@ -189,7 +189,6 @@
}
final headers = {
- HttpHeaders.contentLengthHeader: stat.size.toString(),
HttpHeaders.lastModifiedHeader: formatHttpDate(stat.modified),
HttpHeaders.acceptRangesHeader: 'bytes',
};
@@ -202,52 +201,59 @@
// We only support one range, where the standard support several.
final matches = RegExp(r'^bytes=(\d*)\-(\d*)$').firstMatch(range);
// If the range header have the right format, handle it.
- if (matches != null && (matches[1]!.isNotEmpty || matches[2]!.isNotEmpty)) {
- // Serve sub-range.
- int start; // First byte position - inclusive.
- int end; // Last byte position - inclusive.
- if (matches[1]!.isEmpty) {
- start = length - int.parse(matches[2]!);
- if (start < 0) start = 0;
- end = length - 1;
- } else {
- start = int.parse(matches[1]!);
- end = matches[2]!.isEmpty ? length - 1 : int.parse(matches[2]!);
- }
- // If the range is syntactically invalid the Range header
- // MUST be ignored (RFC 2616 section 14.35.1).
- if (start <= end) {
- if (end >= length) {
+ if (matches != null) {
+ final startMatch = matches[1]!;
+ final endMatch = matches[2]!;
+ if (startMatch.isNotEmpty || endMatch.isNotEmpty) {
+ // Serve sub-range.
+ int start; // First byte position - inclusive.
+ int end; // Last byte position - inclusive.
+ if (startMatch.isEmpty) {
+ start = length - int.parse(endMatch);
+ if (start < 0) start = 0;
end = length - 1;
- }
- if (start >= length) {
- return Response(
- HttpStatus.requestedRangeNotSatisfiable,
- headers: headers,
- );
- }
-
- // Override Content-Length with the actual bytes sent.
- headers[HttpHeaders.contentLengthHeader] = (end - start + 1).toString();
-
- // Set 'Partial Content' status code.
- headers[HttpHeaders.contentRangeHeader] = 'bytes $start-$end/$length';
- // Pipe the 'range' of the file.
- if (request.method == 'HEAD') {
- return Response(
- HttpStatus.partialContent,
- headers: headers,
- );
} else {
- return Response(
- HttpStatus.partialContent,
- body: file.openRead(start, end + 1),
- headers: headers,
- );
+ start = int.parse(startMatch);
+ end = endMatch.isEmpty ? length - 1 : int.parse(endMatch);
+ }
+ // If the range is syntactically invalid the Range header
+ // MUST be ignored (RFC 2616 section 14.35.1).
+ if (start <= end) {
+ if (end >= length) {
+ end = length - 1;
+ }
+ if (start >= length) {
+ return Response(
+ HttpStatus.requestedRangeNotSatisfiable,
+ headers: headers,
+ );
+ }
+
+ // Override Content-Length with the actual bytes sent.
+ headers[HttpHeaders.contentLengthHeader] =
+ (end - start + 1).toString();
+
+ // Set 'Partial Content' status code.
+ headers[HttpHeaders.contentRangeHeader] = 'bytes $start-$end/$length';
+ // Pipe the 'range' of the file.
+ if (request.method == 'HEAD') {
+ return Response(
+ HttpStatus.partialContent,
+ headers: headers,
+ );
+ } else {
+ return Response(
+ HttpStatus.partialContent,
+ body: file.openRead(start, end + 1),
+ headers: headers,
+ );
+ }
}
}
}
}
+ headers[HttpHeaders.contentLengthHeader] = stat.size.toString();
+
return Response.ok(
request.method == 'HEAD' ? null : file.openRead(),
headers: headers,
diff --git a/pubspec.yaml b/pubspec.yaml
index a8fdc56..824b2d6 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,5 +1,5 @@
name: shelf_static
-version: 1.1.0-dev
+version: 1.1.0
description: Static file server support for the shelf package and ecosystem
repository: https://github.com/dart-lang/shelf_static
diff --git a/test/create_file_handler_test.dart b/test/create_file_handler_test.dart
index 8544bb8..907e3b4 100644
--- a/test/create_file_handler_test.dart
+++ b/test/create_file_handler_test.dart
@@ -81,13 +81,14 @@
);
expect(response.statusCode, equals(HttpStatus.partialContent));
expect(
- response.headers[HttpHeaders.acceptRangesHeader],
- 'bytes',
+ response.headers,
+ containsPair(HttpHeaders.acceptRangesHeader, 'bytes'),
);
expect(
- response.headers[HttpHeaders.contentRangeHeader],
- 'bytes 0-4/8',
+ response.headers,
+ containsPair(HttpHeaders.contentRangeHeader, 'bytes 0-4/8'),
);
+ expect(response.headers, containsPair('content-length', '5'));
});
test('at the end of has overflow from 0 to 9', () async {
final handler = createFileHandler(p.join(d.sandbox, 'file.txt'));
@@ -101,13 +102,14 @@
equals(HttpStatus.partialContent),
);
expect(
- response.headers[HttpHeaders.acceptRangesHeader],
- 'bytes',
+ response.headers,
+ containsPair(HttpHeaders.acceptRangesHeader, 'bytes'),
);
expect(
- response.headers[HttpHeaders.contentRangeHeader],
- 'bytes 0-7/8',
+ response.headers,
+ containsPair(HttpHeaders.contentRangeHeader, 'bytes 0-7/8'),
);
+ expect(response.headers, containsPair('content-length', '8'));
});
test('at the start of has overflow from 8 to 9', () async {
final handler = createFileHandler(p.join(d.sandbox, 'file.txt'));
@@ -116,13 +118,14 @@
'/file.txt',
headers: {'range': 'bytes=8-9'},
);
+ expect(response.headers, containsPair('content-length', '0'));
expect(
- response.headers[HttpHeaders.acceptRangesHeader],
- 'bytes',
+ response.headers,
+ containsPair(HttpHeaders.acceptRangesHeader, 'bytes'),
);
expect(
response.statusCode,
- equals(HttpStatus.requestedRangeNotSatisfiable),
+ HttpStatus.requestedRangeNotSatisfiable,
);
});
});