Handle sub-second resolution in DateTime vs second-resolution in HTTP date
diff --git a/lib/shelf_static.dart b/lib/shelf_static.dart
index ebdea75..a2eba04 100644
--- a/lib/shelf_static.dart
+++ b/lib/shelf_static.dart
@@ -7,6 +7,8 @@
import 'package:path/path.dart' as p;
import 'package:shelf/shelf.dart';
+import 'src/util.dart';
+
// directory listing
// hidden files
@@ -87,8 +89,12 @@
var ifModifiedSince = request.ifModifiedSince;
- if (ifModifiedSince != null && !fileStat.changed.isAfter(ifModifiedSince)) {
- return new Response.notModified();
+
+ if (ifModifiedSince != null) {
+ var fileChangeAtSecResolution = toSecondResolution(fileStat.changed);
+ if (!fileChangeAtSecResolution.isAfter(ifModifiedSince)) {
+ return new Response.notModified();
+ }
}
var headers = <String, String>{
diff --git a/lib/src/util.dart b/lib/src/util.dart
index 7a305da..62dbf47 100644
--- a/lib/src/util.dart
+++ b/lib/src/util.dart
@@ -10,3 +10,8 @@
/// Like [Future.sync], but wraps the Future in [Chain.track] as well.
Future syncFuture(callback()) => Chain.track(new Future.sync(callback));
+
+DateTime toSecondResolution(DateTime dt) {
+ if (dt.millisecond == 0) return dt;
+ return dt.subtract(new Duration(milliseconds: dt.millisecond));
+}
diff --git a/test/basic_file_test.dart b/test/basic_file_test.dart
index 7d8c953..c128202 100644
--- a/test/basic_file_test.dart
+++ b/test/basic_file_test.dart
@@ -97,7 +97,7 @@
var modified = new File(rootPath).statSync().changed.toUtc();
return makeRequest(handler, '/root.txt').then((response) {
- expect(response.lastModified, modified);
+ expect(response.lastModified, atSameTimeToSecond(modified));
});
});
});
@@ -137,7 +137,7 @@
return makeRequest(handler, '/root.txt', headers: headers)
.then((response) {
expect(response.statusCode, HttpStatus.OK);
- expect(response.lastModified, modified);
+ expect(response.lastModified, atSameTimeToSecond(modified));
});
});
});
diff --git a/test/sample_test.dart b/test/sample_test.dart
index c939471..0f26a5a 100644
--- a/test/sample_test.dart
+++ b/test/sample_test.dart
@@ -2,6 +2,7 @@
import 'dart:async';
import 'dart:io';
+
import 'package:path/path.dart' as p;
import 'package:scheduled_test/scheduled_test.dart';
@@ -9,6 +10,8 @@
import 'package:shelf_static/shelf_static.dart';
import 'package:shelf_static/src/util.dart';
+import 'test_util.dart';
+
void main() {
group('/index.html', () {
test('body is correct', () {
@@ -61,7 +64,7 @@
return _requestFile(filename).then((response) {
expect(response.contentLength, fileStat.size);
- expect(response.lastModified, fileStat.changed.toUtc());
+ expect(response.lastModified, atSameTimeToSecond(fileStat.changed.toUtc()));
return _expectCompletesWithBytes(response, fileContents);
});
}
diff --git a/test/test_util.dart b/test/test_util.dart
index 58fa18a..cbd7544 100644
--- a/test/test_util.dart
+++ b/test/test_util.dart
@@ -6,6 +6,7 @@
import 'dart:async';
+import 'package:matcher/matcher.dart';
import 'package:path/path.dart' as p;
import 'package:shelf/shelf.dart';
import 'package:shelf_static/src/util.dart';
@@ -42,3 +43,27 @@
return handler(relativeRequest);
};
}
+
+Matcher atSameTimeToSecond(value) =>
+ new _SecondResolutionDateTimeMatcher(value);
+
+class _SecondResolutionDateTimeMatcher extends Matcher {
+ final DateTime _target;
+
+ _SecondResolutionDateTimeMatcher(DateTime target) :
+ this._target = toSecondResolution(target);
+
+ bool matches(item, Map matchState) {
+ if (item is! DateTime) return false;
+
+ return datesEqualToSecond(_target, item);
+ }
+
+ Description describe(Description descirption) =>
+ descirption.add('Must be at the same moment as $_target with resolution '
+ 'to the second.');
+}
+
+bool datesEqualToSecond(DateTime d1, DateTime d2) {
+ return toSecondResolution(d1).isAtSameMomentAs(toSecondResolution(d2));
+}