diff --git a/lib/src/headers/headers.dart b/lib/src/headers/headers.dart index 5df4ca0..fc42841 100644 --- a/lib/src/headers/headers.dart +++ b/lib/src/headers/headers.dart @@ -96,11 +96,18 @@ abstract base class Headers { /// Define header properties - /// Date-related headers - final DateTime? date; - final DateTime? expires; - final DateTime? ifModifiedSince; - final DateTime? lastModified; + /// Date-related headers` + final _LazyInit _date; + DateTime? get date => _date.value; + + final _LazyInit _expires; + DateTime? get expires => _expires.value; + + final _LazyInit _ifModifiedSince; + DateTime? get ifModifiedSince => _ifModifiedSince.value; + + final _LazyInit _lastModified; + DateTime? get lastModified => _lastModified.value; /// General Headers final Uri? origin; @@ -264,10 +271,10 @@ abstract base class Headers { Headers._({ // Date-related headers - this.date, - this.expires, - this.ifModifiedSince, - this.lastModified, + required _LazyInit date, + required _LazyInit expires, + required _LazyInit ifModifiedSince, + required _LazyInit lastModified, // General Headers this.origin, @@ -340,7 +347,11 @@ abstract base class Headers { this.crossOriginEmbedderPolicy, this.crossOriginOpenerPolicy, required this.failedHeadersToParse, - }) : custom = custom ?? CustomHeaders.empty(); + }) : _date = date, + _expires = expires, + _ifModifiedSince = ifModifiedSince, + _lastModified = lastModified, + custom = custom ?? CustomHeaders.empty(); /// Create a new request headers instance from a Dart IO request factory Headers.fromHttpRequest( @@ -368,21 +379,29 @@ abstract base class Headers { return _HeadersImpl( // Date-related headers - date: dartIOHeaders.parseSingleValue( - dateHeader, - onParse: parseDate, - ), - expires: dartIOHeaders.parseSingleValue( - expiresHeader, - onParse: parseDate, - ), - ifModifiedSince: dartIOHeaders.parseSingleValue( - ifModifiedSinceHeader, - onParse: parseDate, - ), - lastModified: dartIOHeaders.parseSingleValue( - lastModifiedHeader, - onParse: parseDate, + date: _LazyInit.lazy( + init: () => dartIOHeaders.parseSingleValue( + dateHeader, + onParse: parseDate, + ), + ), + expires: _LazyInit.lazy( + init: () => dartIOHeaders.parseSingleValue( + expiresHeader, + onParse: parseDate, + ), + ), + ifModifiedSince: _LazyInit.lazy( + init: () => dartIOHeaders.parseSingleValue( + ifModifiedSinceHeader, + onParse: parseDate, + ), + ), + lastModified: _LazyInit.lazy( + init: () => dartIOHeaders.parseSingleValue( + lastModifiedHeader, + onParse: parseDate, + ), ), // General Headers @@ -687,8 +706,10 @@ abstract base class Headers { }) { return _HeadersImpl( // Date-related headers - date: date, - ifModifiedSince: ifModifiedSince, + date: _LazyInit.value(value: date), + ifModifiedSince: _LazyInit.value(value: ifModifiedSince), + expires: _LazyInit.nullValue(), + lastModified: _LazyInit.nullValue(), // Request Headers xPoweredBy: xPoweredBy, @@ -782,9 +803,12 @@ abstract base class Headers { CrossOriginOpenerPolicyHeader? crossOriginOpenerPolicy, }) { return _HeadersImpl( - date: date ?? DateTime.now(), - expires: expires, - lastModified: lastModified, + // Date-related headers + date: _LazyInit.value(value: date ?? DateTime.now()), + expires: _LazyInit.value(value: expires), + lastModified: _LazyInit.value(value: lastModified), + ifModifiedSince: _LazyInit.nullValue(), + origin: origin, server: server, via: via, @@ -920,10 +944,10 @@ abstract base class Headers { final class _HeadersImpl extends Headers { _HeadersImpl({ /// Date-related headers - super.date, - super.expires, - super.ifModifiedSince, - super.lastModified, + required super.date, + required super.expires, + required super.ifModifiedSince, + required super.lastModified, /// General Headers super.origin, @@ -1077,12 +1101,15 @@ final class _HeadersImpl extends Headers { Object? crossOriginOpenerPolicy = _Undefined, }) { return _HeadersImpl( - date: date is DateTime? ? date : this.date, - expires: expires is DateTime? ? expires : this.expires, - ifModifiedSince: - ifModifiedSince is DateTime? ? ifModifiedSince : this.ifModifiedSince, - lastModified: - lastModified is DateTime? ? lastModified : this.lastModified, + date: date is DateTime? ? _LazyInit.value(value: date) : _date, + expires: + expires is DateTime? ? _LazyInit.value(value: expires) : _expires, + ifModifiedSince: ifModifiedSince is DateTime? + ? _LazyInit.value(value: ifModifiedSince) + : _ifModifiedSince, + lastModified: lastModified is DateTime? + ? _LazyInit.value(value: lastModified) + : _lastModified, /// General Headers origin: origin is Uri? ? origin : this.origin, @@ -1478,3 +1505,51 @@ final class _HeadersImpl extends Headers { } class _Undefined {} + +typedef _LazyInitializer = T Function(); + +class _LazyInit { + final _LazyInitializer? _init; + bool _isInitialized = false; + T? _value; + + _LazyInit._({ + _LazyInitializer? init, + T? value, + bool isInitialized = false, + }) : _init = init, + _value = value, + _isInitialized = isInitialized; + + factory _LazyInit.value({ + required T value, + }) { + return _LazyInit._( + value: value, + isInitialized: true, + ); + } + + factory _LazyInit.lazy({ + required _LazyInitializer init, + }) { + return _LazyInit._( + init: init, + isInitialized: false, + ); + } + + factory _LazyInit.nullValue() { + return _LazyInit._( + isInitialized: true, + ); + } + + T? get value { + if (!_isInitialized) { + _value = _init?.call(); + _isInitialized = true; + } + return _value; + } +} diff --git a/test/headers/basic/date_header_test.dart b/test/headers/basic/date_header_test.dart index d009ab1..6b4d238 100644 --- a/test/headers/basic/date_header_test.dart +++ b/test/headers/basic/date_header_test.dart @@ -72,6 +72,21 @@ void main() { }, ); + test( + 'when a Date header with an invalid date format is passed ' + 'then the server does not respond with a bad request if the headers ' + 'is not actually used', + () async { + Headers headers = await getServerRequestHeaders( + server: server, + headers: {'date': 'invalid-date-format'}, + echoHeaders: false, + ); + + expect(headers, isNotNull); + }, + ); + test( 'when a valid Date header is passed then it should parse the date correctly', () async { diff --git a/test/headers/basic/expires_header_test.dart b/test/headers/basic/expires_header_test.dart index fec2570..b9f6d9d 100644 --- a/test/headers/basic/expires_header_test.dart +++ b/test/headers/basic/expires_header_test.dart @@ -72,6 +72,21 @@ void main() { }, ); + test( + 'when an Expires header with an invalid date format is passed ' + 'then the server does not respond with a bad request if the headers ' + 'is not actually used', + () async { + Headers headers = await getServerRequestHeaders( + server: server, + headers: {'expires': 'invalid-date-format'}, + echoHeaders: false, + ); + + expect(headers, isNotNull); + }, + ); + test( 'when a valid Expires header is passed then it should parse the date correctly', () async { diff --git a/test/headers/basic/if_modified_since_header_test.dart b/test/headers/basic/if_modified_since_header_test.dart index d2c8eb2..caa1d57 100644 --- a/test/headers/basic/if_modified_since_header_test.dart +++ b/test/headers/basic/if_modified_since_header_test.dart @@ -72,6 +72,21 @@ void main() { }, ); + test( + 'when an If-Modified-Since header with an invalid date format is passed ' + 'then the server does not respond with a bad request if the headers ' + 'is not actually used', + () async { + Headers headers = await getServerRequestHeaders( + server: server, + headers: {'if-modified-since': 'invalid-date-format'}, + echoHeaders: false, + ); + + expect(headers, isNotNull); + }, + ); + test( 'when a valid If-Modified-Since header is passed then it should parse the ' 'date correctly', diff --git a/test/headers/basic/last_modified_header.dart b/test/headers/basic/last_modified_header.dart index bf8f285..df0af23 100644 --- a/test/headers/basic/last_modified_header.dart +++ b/test/headers/basic/last_modified_header.dart @@ -72,6 +72,21 @@ void main() { }, ); + test( + 'when a Last-Modified header with an invalid date format is passed ' + 'then the server does not respond with a bad request if the headers ' + 'is not actually used', + () async { + Headers headers = await getServerRequestHeaders( + server: server, + headers: {'last-modified': 'invalid-date-format'}, + echoHeaders: false, + ); + + expect(headers, isNotNull); + }, + ); + test( 'when a valid Last-Modified header is passed then it should parse the ' 'date correctly', diff --git a/test/headers/headers_test_utils.dart b/test/headers/headers_test_utils.dart index 5ed642e..d013b19 100644 --- a/test/headers/headers_test_utils.dart +++ b/test/headers/headers_test_utils.dart @@ -17,13 +17,17 @@ class BadRequestException implements Exception { Future getServerRequestHeaders({ required RelicServer server, required Map headers, + // Whether to echo the headers back to the client. + bool echoHeaders = true, }) async { Headers? parsedHeaders; server.mountAndStart( (Request request) { parsedHeaders = request.headers; - return Response.ok(); + return Response.ok( + headers: echoHeaders ? parsedHeaders : null, + ); }, );