From 47f9cbad4d6b634298cca0eba561f64715801daf Mon Sep 17 00:00:00 2001 From: Harpreet Sangar Date: Thu, 19 Aug 2021 14:21:22 +0530 Subject: [PATCH 01/10] Update tests --- lib/src/services/base_api_call.dart | 5 +- lib/src/services/request_cache.dart | 10 ++- lib/src/services/typedefs.dart | 6 ++ pubspec.yaml | 1 + test/configuration_test.dart | 31 ++++++++- test/services/request_cache_test.dart | 94 +++++++++++++++------------ 6 files changed, 98 insertions(+), 49 deletions(-) create mode 100644 lib/src/services/typedefs.dart diff --git a/lib/src/services/base_api_call.dart b/lib/src/services/base_api_call.dart index f44af64..ffcf6b8 100644 --- a/lib/src/services/base_api_call.dart +++ b/lib/src/services/base_api_call.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'package:http/http.dart' as http; +import 'typedefs.dart'; import 'node_pool.dart'; import '../configuration.dart'; import '../models/node.dart'; @@ -46,12 +47,12 @@ abstract class BaseApiCall { Map get defaultQueryParameters => Map.from(_defaultQueryParameters); - /// Retries the [request] untill a node responds or [Configuration.numRetries] + /// Retries the [request] untill a node functionresponds or [Configuration.numRetries] /// run out. /// /// Also sets the health status of nodes after each request so it can be put /// in/out of [NodePool]'s circulation. - Future send(Future Function(Node) request) async { + Future send(Request request) async { http.Response response; Node node; for (var triesLeft = config.numRetries;;) { diff --git a/lib/src/services/request_cache.dart b/lib/src/services/request_cache.dart index 65f835f..fb70388 100644 --- a/lib/src/services/request_cache.dart +++ b/lib/src/services/request_cache.dart @@ -1,20 +1,18 @@ import 'dart:collection'; -import 'package:http/http.dart' as http; - -import '../models/node.dart'; +import 'typedefs.dart'; /// Cache store which uses a [HashMap] internally to serve requests. class RequestCache { final _cachedResponses = HashMap(); + // TODO(harisarang): rename this function to getResponse /// Caches the response of the [request], identified by [key]. The cached /// response is valid till [cacheTTL]. Future> cache( int key, - Future> Function(Future Function(Node)) - send, - Future Function(Node) request, + Send> send, // Only being used by ApiCall for now. + Request request, Duration cacheTTL, ) async { if (_cachedResponses.containsKey(key)) { diff --git a/lib/src/services/typedefs.dart b/lib/src/services/typedefs.dart new file mode 100644 index 0000000..50b9095 --- /dev/null +++ b/lib/src/services/typedefs.dart @@ -0,0 +1,6 @@ +import 'package:http/http.dart' as http; + +import '../models/node.dart'; + +typedef Request = Future Function(Node); +typedef Send = Future Function(Request); diff --git a/pubspec.yaml b/pubspec.yaml index 5aafc7a..4585efc 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -11,6 +11,7 @@ dependencies: http: ^0.13.3 crypto: ^3.0.1 equatable: ^2.0.2 + dcache: ^0.4.0 dev_dependencies: test: ^1.17.7 diff --git a/test/configuration_test.dart b/test/configuration_test.dart index 9d04788..f3ce36a 100644 --- a/test/configuration_test.dart +++ b/test/configuration_test.dart @@ -27,6 +27,7 @@ void main() { retryInterval: Duration(seconds: 3), sendApiKeyAsQueryParam: true, cachedSearchResultsTTL: Duration(seconds: 30), + cacheCapacity: 101, ); group('Configuration', () { @@ -61,9 +62,12 @@ void main() { test('has a sendApiKeyAsQueryParam field', () { expect(config.sendApiKeyAsQueryParam, isTrue); }); - test('has a cacheSearchResults field', () { + test('has a cacheSearchResultsTTL field', () { expect(config.cachedSearchResultsTTL, equals(Duration(seconds: 30))); }); + test('has a cacheCapacity field', () { + expect(config.cacheCapacity, equals(101)); + }); }); group('Configuration initialization', () { @@ -180,6 +184,31 @@ void main() { ); expect(config.retryInterval, equals(Duration(milliseconds: 100))); }); + test('with missing cacheCapacity, sets cacheCapacity to 100', () { + final config = Configuration( + apiKey: 'abc123', + connectionTimeout: Duration(seconds: 10), + healthcheckInterval: Duration(seconds: 5), + nearestNode: Node( + protocol: 'http', + host: 'localhost', + path: '/path/to/service', + ), + nodes: { + Node( + protocol: 'https', + host: 'localhost', + path: '/path/to/service', + ), + }, + numRetries: 5, + retryInterval: Duration(seconds: 3), + sendApiKeyAsQueryParam: true, + cachedSearchResultsTTL: Duration(seconds: 30), + ); + + expect(config.cacheCapacity, equals(100)); + }); test( 'with missing sendApiKeyAsQueryParam, sets sendApiKeyAsQueryParam to false', () { diff --git a/test/services/request_cache_test.dart b/test/services/request_cache_test.dart index 8a50ea5..4367338 100644 --- a/test/services/request_cache_test.dart +++ b/test/services/request_cache_test.dart @@ -6,89 +6,103 @@ import 'package:http/http.dart' as http; import 'package:typesense/src/services/request_cache.dart'; import 'package:typesense/src/models/node.dart'; +import 'package:typesense/src/services/typedefs.dart'; import '../test_utils.dart'; class MockResponse extends Mock implements http.Response {} void main() { - group('RequestCache', () { - RequestCache requestCache; - MockResponse mockResponse; - int requestNumber; - Future> Function(Future Function(Node)) - send; - Future Function(Node) request; - final cacheTTL = Duration(seconds: 1); - setUp(() { - requestCache = RequestCache(); - mockResponse = MockResponse(); - requestNumber = 1; + RequestCache requestCache; + MockResponse mockResponse; + int requestNumber; + Send> send; + Request request; + + setUp(() { + requestCache = RequestCache(5, Duration(seconds: 1)); + mockResponse = MockResponse(); + requestNumber = 1; - when(mockResponse.body).thenAnswer((invocation) { - switch (requestNumber++) { - case 1: - return json.encode({'value': 'initial'}); + when(mockResponse.body).thenAnswer((invocation) { + switch (requestNumber++) { + case 1: + return json.encode({'value': 'initial'}); - case 2: - return json.encode({'value': 'updated'}); + case 2: + return json.encode({'value': 'updated'}); + + default: + return json.encode({}); + } + }); - default: - return json.encode({}); - } - }); + send = (request) async { + final response = await request( + Node(protocol: protocol, host: host, path: pathToService)); + return json.decode(response.body); + }; + request = (node) => Future.value(mockResponse); + }); + group('RequestCache', () { + final requestCache = RequestCache(5, Duration(seconds: 1)); - send = (request) async { - final response = await request( - Node(protocol: protocol, host: host, path: pathToService)); - return json.decode(response.body); - }; - request = (node) => Future.value(mockResponse); + test('has a size field', () { + expect(requestCache.size, equals(5)); }); + test('has a timeToUse field', () { + expect(requestCache.timeToUse, equals(Duration(seconds: 1))); + }); + test('has a getResponse method', () async { + expect( + await requestCache.getResponse( + '/value'.hashCode, + send, + request, + ), + equals({'value': 'initial'})); + }); + }); - test('caches the response', () async { + group('RequestCache.getResponse', () { + test('returns cached response', () async { expect( - await requestCache.cache( + await requestCache.getResponse( '/value'.hashCode, send, request, - cacheTTL, ), equals({'value': 'initial'})); expect( - await requestCache.cache( + await requestCache.getResponse( '/value'.hashCode, send, request, - cacheTTL, ), equals({'value': 'initial'})); }); - test('refreshes the cache after TTL duration', () async { + test('refreshes the cache after timeToUse duration', () async { expect( - await requestCache.cache( + await requestCache.getResponse( '/value'.hashCode, send, request, - cacheTTL, ), equals({'value': 'initial'})); expect( - await requestCache.cache( + await requestCache.getResponse( '/value'.hashCode, send, request, - cacheTTL, ), equals({'value': 'initial'})); await Future.delayed(Duration(seconds: 1, milliseconds: 100)); expect( - await requestCache.cache( + await requestCache.getResponse( '/value'.hashCode, send, request, - cacheTTL, ), equals({'value': 'updated'})); }); From 886f995bfda7571b36f51c60b58a798f77271098 Mon Sep 17 00:00:00 2001 From: Harpreet Sangar Date: Thu, 19 Aug 2021 15:57:00 +0530 Subject: [PATCH 02/10] revert typo --- lib/src/services/base_api_call.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/services/base_api_call.dart b/lib/src/services/base_api_call.dart index ffcf6b8..6944df2 100644 --- a/lib/src/services/base_api_call.dart +++ b/lib/src/services/base_api_call.dart @@ -47,7 +47,7 @@ abstract class BaseApiCall { Map get defaultQueryParameters => Map.from(_defaultQueryParameters); - /// Retries the [request] untill a node functionresponds or [Configuration.numRetries] + /// Retries the [request] untill a node responds or [Configuration.numRetries] /// run out. /// /// Also sets the health status of nodes after each request so it can be put From a85a83d0a6d405d2ef0ddc842064d8eceb9ce3bd Mon Sep 17 00:00:00 2001 From: Harpreet Sangar Date: Fri, 20 Aug 2021 14:17:49 +0530 Subject: [PATCH 03/10] Add test for LRU eviction. [skip ci] --- test/services/request_cache_test.dart | 88 +++++++++++++++++++++------ 1 file changed, 71 insertions(+), 17 deletions(-) diff --git a/test/services/request_cache_test.dart b/test/services/request_cache_test.dart index 4367338..844886d 100644 --- a/test/services/request_cache_test.dart +++ b/test/services/request_cache_test.dart @@ -12,18 +12,28 @@ import '../test_utils.dart'; class MockResponse extends Mock implements http.Response {} +Future> send(request) async { + final response = await request( + Node( + protocol: protocol, + host: host, + path: pathToService, + ), + ); + return json.decode(response.body); +} + void main() { RequestCache requestCache; MockResponse mockResponse; int requestNumber; - Send> send; Request request; setUp(() { - requestCache = RequestCache(5, Duration(seconds: 1)); + requestCache = RequestCache(5, Duration(seconds: 1), send); + mockResponse = MockResponse(); requestNumber = 1; - when(mockResponse.body).thenAnswer((invocation) { switch (requestNumber++) { case 1: @@ -36,17 +46,9 @@ void main() { return json.encode({}); } }); - - send = (request) async { - final response = await request( - Node(protocol: protocol, host: host, path: pathToService)); - return json.decode(response.body); - }; request = (node) => Future.value(mockResponse); }); group('RequestCache', () { - final requestCache = RequestCache(5, Duration(seconds: 1)); - test('has a size field', () { expect(requestCache.size, equals(5)); }); @@ -57,11 +59,13 @@ void main() { expect( await requestCache.getResponse( '/value'.hashCode, - send, request, ), equals({'value': 'initial'})); }); + test('has a send method', () async { + expect(await requestCache.send(request), equals({'value': 'initial'})); + }); }); group('RequestCache.getResponse', () { @@ -69,14 +73,12 @@ void main() { expect( await requestCache.getResponse( '/value'.hashCode, - send, request, ), equals({'value': 'initial'})); expect( await requestCache.getResponse( '/value'.hashCode, - send, request, ), equals({'value': 'initial'})); @@ -85,14 +87,12 @@ void main() { expect( await requestCache.getResponse( '/value'.hashCode, - send, request, ), equals({'value': 'initial'})); expect( await requestCache.getResponse( '/value'.hashCode, - send, request, ), equals({'value': 'initial'})); @@ -101,10 +101,64 @@ void main() { expect( await requestCache.getResponse( '/value'.hashCode, - send, request, ), equals({'value': 'updated'})); }); + test('evicts the least recently used response', () async { + requestCache = RequestCache(5, Duration(seconds: 10), send); + + final mockResponses = List.generate(6, (_) => MockResponse()), + callCounters = List.filled(6, 0); + var i = 0; + + for (final mockResponse in mockResponses) { + when(mockResponse.body).thenAnswer((invocation) { + return json.encode({'$i': '${++callCounters[i]}'}); + }); + } + + // Cache size is 5, filling up the cache with different responses. + for (; i < 5; i++) { + expect( + await requestCache.getResponse( + i, + (node) => Future.value(mockResponses[i]), + ), + equals({'$i': '1'})); + } + + // The responses should still be 1 since they're cached. + i = 0; + for (; i < 5; i++) { + expect( + await requestCache.getResponse( + i, + (node) => Future.value(mockResponses[i]), + ), + equals({'$i': '1'})); + } + + // Least recently used response at this moment should be index 0 and hence + // should be evicted by the following call. + expect( + await requestCache.getResponse( + 5, + (node) => Future.value(mockResponses[5]), + ), + equals({'5': '1'})); + + // The responses should now be 2 since each response gets evicted before + // being called again. + i = 0; + for (; i < 5; i++) { + expect( + await requestCache.getResponse( + i, + (node) => Future.value(mockResponses[i]), + ), + equals({'$i': '2'})); + } + }); }); } From 4eaef8ac50bb7f2a1c4e9662161a87d9ce9a36c4 Mon Sep 17 00:00:00 2001 From: Harisaran Date: Sun, 22 Aug 2021 15:32:20 +0530 Subject: [PATCH 04/10] add: lru cache --- lib/src/services/request_cache.dart | 43 ++++++++++++----------------- 1 file changed, 17 insertions(+), 26 deletions(-) diff --git a/lib/src/services/request_cache.dart b/lib/src/services/request_cache.dart index fb70388..ece9083 100644 --- a/lib/src/services/request_cache.dart +++ b/lib/src/services/request_cache.dart @@ -1,42 +1,33 @@ import 'dart:collection'; +import 'package:dcache/dcache.dart'; +import 'package:http/http.dart'; -import 'typedefs.dart'; +import 'typedefs.dart' as defs; /// Cache store which uses a [HashMap] internally to serve requests. class RequestCache { - final _cachedResponses = HashMap(); + Cache _cachedResponses; + final Duration timeToUse; + final int size; + final defs.Send> send; + + RequestCache(this.size, this.timeToUse, this.send) { + _cachedResponses = LruCache(storage: InMemoryStorage(size)); + } // TODO(harisarang): rename this function to getResponse /// Caches the response of the [request], identified by [key]. The cached /// response is valid till [cacheTTL]. - Future> cache( + Future> getResponse( int key, - Send> send, // Only being used by ApiCall for now. - Request request, - Duration cacheTTL, + defs.Request request, ) async { if (_cachedResponses.containsKey(key)) { - if (_isCacheValid(_cachedResponses[key], cacheTTL)) { - // Cache entry is still valid, return it - return Future.value(_cachedResponses[key].data); - } else { - // Cache entry has expired, so delete it explicitly - _cachedResponses.remove(key); - } + return send(_cachedResponses.get(key)); } - - final response = await send(request); - _cachedResponses[key] = _Cache(response, DateTime.now()); + + var response = await send(request); + _cachedResponses.set(key, response); return response; } - - bool _isCacheValid(_Cache cache, Duration cacheTTL) => - DateTime.now().difference(cache.creationTime) < cacheTTL; -} - -class _Cache { - final DateTime creationTime; - final Map data; - - const _Cache(this.data, this.creationTime); } From ec4aecd75120b60cf72b1acf6d5fe285f184fe00 Mon Sep 17 00:00:00 2001 From: Harisaran Date: Sun, 22 Aug 2021 16:03:02 +0530 Subject: [PATCH 05/10] update: type `dynamic` to `String` --- lib/src/services/request_cache.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/src/services/request_cache.dart b/lib/src/services/request_cache.dart index ece9083..4603b00 100644 --- a/lib/src/services/request_cache.dart +++ b/lib/src/services/request_cache.dart @@ -12,14 +12,14 @@ class RequestCache { final defs.Send> send; RequestCache(this.size, this.timeToUse, this.send) { - _cachedResponses = LruCache(storage: InMemoryStorage(size)); + _cachedResponses = LruCache(storage: InMemoryStorage(size)); } // TODO(harisarang): rename this function to getResponse /// Caches the response of the [request], identified by [key]. The cached /// response is valid till [cacheTTL]. Future> getResponse( - int key, + String key, defs.Request request, ) async { if (_cachedResponses.containsKey(key)) { From ce4176ccf88573e5f3698dd802c0b2b6aa4ac06d Mon Sep 17 00:00:00 2001 From: Harisaran Date: Sun, 22 Aug 2021 16:35:14 +0530 Subject: [PATCH 06/10] update: `Cache` types --- lib/src/services/request_cache.dart | 14 +++++++------- test/services/request_cache_test.dart | 20 ++++++++++---------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/lib/src/services/request_cache.dart b/lib/src/services/request_cache.dart index 4603b00..8bca308 100644 --- a/lib/src/services/request_cache.dart +++ b/lib/src/services/request_cache.dart @@ -1,18 +1,18 @@ import 'dart:collection'; import 'package:dcache/dcache.dart'; -import 'package:http/http.dart'; +import 'package:http/http.dart' as http; -import 'typedefs.dart' as defs; +import 'typedefs.dart'; /// Cache store which uses a [HashMap] internally to serve requests. class RequestCache { - Cache _cachedResponses; + Cache> _cachedResponses; final Duration timeToUse; final int size; - final defs.Send> send; + final Send> send; RequestCache(this.size, this.timeToUse, this.send) { - _cachedResponses = LruCache(storage: InMemoryStorage(size)); + _cachedResponses = LruCache>(storage: InMemoryStorage(size)); } // TODO(harisarang): rename this function to getResponse @@ -20,10 +20,10 @@ class RequestCache { /// response is valid till [cacheTTL]. Future> getResponse( String key, - defs.Request request, + Request request, ) async { if (_cachedResponses.containsKey(key)) { - return send(_cachedResponses.get(key)); + return Future>.value(_cachedResponses.get(key)); } var response = await send(request); diff --git a/test/services/request_cache_test.dart b/test/services/request_cache_test.dart index 844886d..6c1a92a 100644 --- a/test/services/request_cache_test.dart +++ b/test/services/request_cache_test.dart @@ -58,7 +58,7 @@ void main() { test('has a getResponse method', () async { expect( await requestCache.getResponse( - '/value'.hashCode, + '/value', request, ), equals({'value': 'initial'})); @@ -72,13 +72,13 @@ void main() { test('returns cached response', () async { expect( await requestCache.getResponse( - '/value'.hashCode, + '/value', request, ), equals({'value': 'initial'})); expect( await requestCache.getResponse( - '/value'.hashCode, + '/value', request, ), equals({'value': 'initial'})); @@ -86,13 +86,13 @@ void main() { test('refreshes the cache after timeToUse duration', () async { expect( await requestCache.getResponse( - '/value'.hashCode, + '/value', request, ), equals({'value': 'initial'})); expect( await requestCache.getResponse( - '/value'.hashCode, + '/value', request, ), equals({'value': 'initial'})); @@ -100,7 +100,7 @@ void main() { await Future.delayed(Duration(seconds: 1, milliseconds: 100)); expect( await requestCache.getResponse( - '/value'.hashCode, + '/value', request, ), equals({'value': 'updated'})); @@ -122,7 +122,7 @@ void main() { for (; i < 5; i++) { expect( await requestCache.getResponse( - i, + i.toString(), (node) => Future.value(mockResponses[i]), ), equals({'$i': '1'})); @@ -133,7 +133,7 @@ void main() { for (; i < 5; i++) { expect( await requestCache.getResponse( - i, + i.toString(), (node) => Future.value(mockResponses[i]), ), equals({'$i': '1'})); @@ -143,7 +143,7 @@ void main() { // should be evicted by the following call. expect( await requestCache.getResponse( - 5, + 5.toString(), (node) => Future.value(mockResponses[5]), ), equals({'5': '1'})); @@ -154,7 +154,7 @@ void main() { for (; i < 5; i++) { expect( await requestCache.getResponse( - i, + i.toString(), (node) => Future.value(mockResponses[i]), ), equals({'$i': '2'})); From 37f7563237cb972a64f32816f7a46ce31e541d09 Mon Sep 17 00:00:00 2001 From: Harpreet Sangar Date: Sun, 22 Aug 2021 18:12:58 +0530 Subject: [PATCH 07/10] Update ApiCall to match getResponse --- lib/src/services/api_call.dart | 11 ++++------- lib/src/services/request_cache.dart | 7 +++---- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/lib/src/services/api_call.dart b/lib/src/services/api_call.dart index 057d32f..bf5d6b9 100644 --- a/lib/src/services/api_call.dart +++ b/lib/src/services/api_call.dart @@ -30,17 +30,16 @@ class ApiCall extends BaseApiCall> { bool shouldCacheResult = false, }) => shouldCacheResult && config.cachedSearchResultsTTL != Duration.zero - ? _requestCache.cache( + ? _requestCache.getResponse( // SplayTreeMap ensures order of the parameters is maintained so // cache key won't differ because of different ordering of // parameters. - '$endpoint${SplayTreeMap.from(queryParams)}'.hashCode, + '$endpoint${SplayTreeMap.from(queryParams)}', send, (node) => node.client.get( requestUri(node, endpoint, queryParams), headers: defaultHeaders, ), - config.cachedSearchResultsTTL, ) : send((node) => node.client.get( requestUri(node, endpoint, queryParams), @@ -80,19 +79,17 @@ class ApiCall extends BaseApiCall> { bool shouldCacheResult = false, }) => shouldCacheResult && config.cachedSearchResultsTTL != Duration.zero - ? _requestCache.cache( + ? _requestCache.getResponse( // SplayTreeMap ensures order of the parameters is maintained so // cache key won't differ because of different ordering of // parameters. - '$endpoint${SplayTreeMap.from(queryParams)}${SplayTreeMap.from(additionalHeaders)}${json.encode(bodyParameters)}' - .hashCode, + '$endpoint${SplayTreeMap.from(queryParams)}${SplayTreeMap.from(additionalHeaders)}${json.encode(bodyParameters)}', send, (node) => node.client.post( requestUri(node, endpoint, queryParams), headers: {...defaultHeaders, ...additionalHeaders}, body: json.encode(bodyParameters), ), - config.cachedSearchResultsTTL, ) : send((node) => node.client.post( requestUri(node, endpoint, queryParams), diff --git a/lib/src/services/request_cache.dart b/lib/src/services/request_cache.dart index 8bca308..f55dbe7 100644 --- a/lib/src/services/request_cache.dart +++ b/lib/src/services/request_cache.dart @@ -1,6 +1,5 @@ import 'dart:collection'; import 'package:dcache/dcache.dart'; -import 'package:http/http.dart' as http; import 'typedefs.dart'; @@ -12,10 +11,10 @@ class RequestCache { final Send> send; RequestCache(this.size, this.timeToUse, this.send) { - _cachedResponses = LruCache>(storage: InMemoryStorage(size)); + _cachedResponses = + LruCache>(storage: InMemoryStorage(size)); } - // TODO(harisarang): rename this function to getResponse /// Caches the response of the [request], identified by [key]. The cached /// response is valid till [cacheTTL]. Future> getResponse( @@ -25,7 +24,7 @@ class RequestCache { if (_cachedResponses.containsKey(key)) { return Future>.value(_cachedResponses.get(key)); } - + var response = await send(request); _cachedResponses.set(key, response); return response; From 7995247ae640d1ef2e5ac27de35c6047280b94f0 Mon Sep 17 00:00:00 2001 From: Harisaran Date: Sun, 22 Aug 2021 19:06:52 +0530 Subject: [PATCH 08/10] refactor: `send` inside `getResponse()` --- lib/src/services/request_cache.dart | 5 ++--- test/services/request_cache_test.dart | 17 ++++++++++++----- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/lib/src/services/request_cache.dart b/lib/src/services/request_cache.dart index 8bca308..aac9be0 100644 --- a/lib/src/services/request_cache.dart +++ b/lib/src/services/request_cache.dart @@ -1,6 +1,5 @@ import 'dart:collection'; import 'package:dcache/dcache.dart'; -import 'package:http/http.dart' as http; import 'typedefs.dart'; @@ -9,9 +8,8 @@ class RequestCache { Cache> _cachedResponses; final Duration timeToUse; final int size; - final Send> send; - RequestCache(this.size, this.timeToUse, this.send) { + RequestCache(this.size, this.timeToUse) { _cachedResponses = LruCache>(storage: InMemoryStorage(size)); } @@ -21,6 +19,7 @@ class RequestCache { Future> getResponse( String key, Request request, + Send> send ) async { if (_cachedResponses.containsKey(key)) { return Future>.value(_cachedResponses.get(key)); diff --git a/test/services/request_cache_test.dart b/test/services/request_cache_test.dart index 6c1a92a..bbbaadb 100644 --- a/test/services/request_cache_test.dart +++ b/test/services/request_cache_test.dart @@ -30,7 +30,7 @@ void main() { Request request; setUp(() { - requestCache = RequestCache(5, Duration(seconds: 1), send); + requestCache = RequestCache(5, Duration(seconds: 1)); mockResponse = MockResponse(); requestNumber = 1; @@ -60,12 +60,10 @@ void main() { await requestCache.getResponse( '/value', request, + send ), equals({'value': 'initial'})); }); - test('has a send method', () async { - expect(await requestCache.send(request), equals({'value': 'initial'})); - }); }); group('RequestCache.getResponse', () { @@ -74,12 +72,14 @@ void main() { await requestCache.getResponse( '/value', request, + send ), equals({'value': 'initial'})); expect( await requestCache.getResponse( '/value', request, + send ), equals({'value': 'initial'})); }); @@ -88,12 +88,14 @@ void main() { await requestCache.getResponse( '/value', request, + send ), equals({'value': 'initial'})); expect( await requestCache.getResponse( '/value', request, + send ), equals({'value': 'initial'})); @@ -102,11 +104,12 @@ void main() { await requestCache.getResponse( '/value', request, + send ), equals({'value': 'updated'})); }); test('evicts the least recently used response', () async { - requestCache = RequestCache(5, Duration(seconds: 10), send); + requestCache = RequestCache(5, Duration(seconds: 10)); final mockResponses = List.generate(6, (_) => MockResponse()), callCounters = List.filled(6, 0); @@ -124,6 +127,7 @@ void main() { await requestCache.getResponse( i.toString(), (node) => Future.value(mockResponses[i]), + send ), equals({'$i': '1'})); } @@ -135,6 +139,7 @@ void main() { await requestCache.getResponse( i.toString(), (node) => Future.value(mockResponses[i]), + send ), equals({'$i': '1'})); } @@ -145,6 +150,7 @@ void main() { await requestCache.getResponse( 5.toString(), (node) => Future.value(mockResponses[5]), + send ), equals({'5': '1'})); @@ -156,6 +162,7 @@ void main() { await requestCache.getResponse( i.toString(), (node) => Future.value(mockResponses[i]), + send ), equals({'$i': '2'})); } From ea8bd063e870cf3133eebcce1489820505a0abff Mon Sep 17 00:00:00 2001 From: Harisaran Date: Sun, 22 Aug 2021 19:09:39 +0530 Subject: [PATCH 09/10] update: apicall --- lib/src/services/api_call.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/src/services/api_call.dart b/lib/src/services/api_call.dart index bf5d6b9..7c4e56d 100644 --- a/lib/src/services/api_call.dart +++ b/lib/src/services/api_call.dart @@ -35,11 +35,11 @@ class ApiCall extends BaseApiCall> { // cache key won't differ because of different ordering of // parameters. '$endpoint${SplayTreeMap.from(queryParams)}', - send, (node) => node.client.get( requestUri(node, endpoint, queryParams), headers: defaultHeaders, ), + send, ) : send((node) => node.client.get( requestUri(node, endpoint, queryParams), @@ -84,12 +84,12 @@ class ApiCall extends BaseApiCall> { // cache key won't differ because of different ordering of // parameters. '$endpoint${SplayTreeMap.from(queryParams)}${SplayTreeMap.from(additionalHeaders)}${json.encode(bodyParameters)}', - send, (node) => node.client.post( requestUri(node, endpoint, queryParams), headers: {...defaultHeaders, ...additionalHeaders}, body: json.encode(bodyParameters), ), + send, ) : send((node) => node.client.post( requestUri(node, endpoint, queryParams), From c7717256b84fc8b185fb3c812de29d67b406ba09 Mon Sep 17 00:00:00 2001 From: Harisaran Date: Sun, 22 Aug 2021 19:42:28 +0530 Subject: [PATCH 10/10] add: `_cacheTimestamp` [ci skip] --- lib/src/services/request_cache.dart | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/src/services/request_cache.dart b/lib/src/services/request_cache.dart index 950f259..e8e8b6f 100644 --- a/lib/src/services/request_cache.dart +++ b/lib/src/services/request_cache.dart @@ -6,6 +6,7 @@ import 'typedefs.dart'; /// Cache store which uses a [HashMap] internally to serve requests. class RequestCache { Cache> _cachedResponses; + final _cachedTimestamp = HashMap(); final Duration timeToUse; final int size; @@ -20,12 +21,16 @@ class RequestCache { Request request, Send> send ) async { - if (_cachedResponses.containsKey(key)) { + if (_cachedResponses.containsKey(key) && _isCacheValid(key)) { return Future>.value(_cachedResponses.get(key)); } var response = await send(request); _cachedResponses.set(key, response); + _cachedTimestamp[key] = DateTime.now(); return response; } + + bool _isCacheValid(String key) => + DateTime.now().difference(_cachedTimestamp[key]) < timeToUse; }