diff --git a/CHANGELOG.md b/CHANGELOG.md index 40e089c..2dcf890 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 2.2.0 +- feat: Cache-Control: max-age as cache trigger added. +- core: update README.md. + ## 2.1.1 - fix: refreshForceCache policy added. Get parity with refresh/request and refreshForceCache/forceCache. diff --git a/README.md b/README.md index e26d491..4e108c4 100644 --- a/README.md +++ b/README.md @@ -3,11 +3,17 @@ Dio HTTP cache interceptor with multiple stores respecting HTTP directives (or not). ## HTTP directives: -- ETag -- Last-Modified -- Cache-Control -- Date -- Expires +| | | +|-------------------|--------------------------------| +| Cache triggers | ETag | +| | Last-Modified | +| | max-age (Cache-Control) | +| Cache freshness | Date (request date otherwise) | +| | Expires | +| | max-age (Cache-Control) | +| Cache commutators | no-cache (Cache-Control) | +| | no-store (Cache-Control) | +| | | ## Stores - BackupCacheStore: Combined store with primary and secondary. diff --git a/lib/src/dio_cache_interceptor.dart b/lib/src/dio_cache_interceptor.dart index 6fc96ae..93c7422 100644 --- a/lib/src/dio_cache_interceptor.dart +++ b/lib/src/dio_cache_interceptor.dart @@ -39,13 +39,13 @@ class DioCacheInterceptor extends Interceptor { options.policy != CachePolicy.refreshForceCache) { final cacheResp = await _getCacheResponse(request); if (cacheResp != null) { - if (_shouldReturnCache(options, cacheResp)) { + if (_isCacheValid(options, cacheResp)) { handler.resolve(cacheResp.toResponse(request, fromNetwork: false)); return; } // Update request with cache directives - _addCacheDirectives(request, cacheResp); + _addCacheValidationHeaders(request, cacheResp); } } @@ -71,7 +71,7 @@ class DioCacheInterceptor extends Interceptor { await _getCacheStore(options).delete( options.keyBuilder(response.requestOptions), ); - } else if (_hasCacheDirectives(response, policy: policy)) { + } else if (_shouldStoreResponse(response, policy: policy)) { // Cache response into store final cacheResp = await _buildCacheResponse( options.keyBuilder(response.requestOptions), @@ -132,7 +132,10 @@ class DioCacheInterceptor extends Interceptor { handler.next(err); } - void _addCacheDirectives(RequestOptions request, CacheResponse response) { + void _addCacheValidationHeaders( + RequestOptions request, + CacheResponse response, + ) { if (response.eTag != null) { request.headers[ifNoneMatchHeader] = response.eTag; } @@ -142,7 +145,7 @@ class DioCacheInterceptor extends Interceptor { } } - bool _hasCacheDirectives(Response response, {CachePolicy? policy}) { + bool _shouldStoreResponse(Response response, {CachePolicy? policy}) { if (policy == CachePolicy.forceCache || policy == CachePolicy.refreshForceCache) { return true; @@ -156,13 +159,15 @@ class DioCacheInterceptor extends Interceptor { ); if (cacheControl != null) { + final checkedMaxAge = cacheControl.maxAge; + result |= checkedMaxAge != null && checkedMaxAge > 0; result &= !(cacheControl.noStore ?? false); } return result; } - bool _shouldReturnCache(CacheOptions options, CacheResponse cacheResp) { + bool _isCacheValid(CacheOptions options, CacheResponse cacheResp) { // Forced cache response if (options.policy == CachePolicy.forceCache) { return true; diff --git a/pubspec.yaml b/pubspec.yaml index defe6e0..5460064 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -3,7 +3,7 @@ description: Dio HTTP cache interceptor with multiple stores respecting HTTP dir repository: https://github.com/llfbandit/dio_cache_interceptor issue_tracker: https://github.com/llfbandit/dio_cache_interceptor/issues -version: 2.1.1 +version: 2.2.0 environment: sdk: ">=2.12.0 <3.0.0" diff --git a/test/cache_interceptor_test.dart b/test/cache_interceptor_test.dart index b680015..d97259f 100644 --- a/test/cache_interceptor_test.dart +++ b/test/cache_interceptor_test.dart @@ -275,4 +275,17 @@ void main() { final cacheKey = resp.extra[CacheResponse.cacheKey]; expect(cacheKey, isNull); }); + + test('Fetch max-age', () async { + final resp = await _dio.get('${MockHttpClientAdapter.mockBase}/max-age'); + final cacheKey = resp.extra[CacheResponse.cacheKey]; + final cacheResp = await store.get(cacheKey); + expect(cacheResp, isNotNull); + + // We're before max-age: 1 + expect(cacheResp!.isExpired(), isFalse); + // We're after max-age: 1 + await Future.delayed(const Duration(seconds: 1)); + expect(cacheResp.isExpired(), isTrue); + }); } diff --git a/test/mock_httpclient_adapter.dart b/test/mock_httpclient_adapter.dart index 58cb585..a831f0b 100644 --- a/test/mock_httpclient_adapter.dart +++ b/test/mock_httpclient_adapter.dart @@ -145,6 +145,17 @@ class MockHttpClientAdapter extends HttpClientAdapter { }, ); } + case '/max-age': + { + return ResponseBody.fromBytes( + utf8.encode(jsonEncode({'path': uri.path})), + 200, + headers: { + Headers.contentTypeHeader: [Headers.jsonContentType], + 'cache-control': ['public', 'max-age=1'], + }, + ); + } default: return ResponseBody.fromString('', 404); }