diff --git a/lib/utils/network_utils.dart b/lib/utils/network_utils.dart index cbfa09c..cf3d2a6 100644 --- a/lib/utils/network_utils.dart +++ b/lib/utils/network_utils.dart @@ -12,6 +12,133 @@ final _logger = Logger('NetworkUtils'); http.Response requestTimeout() => http.Response('Client side timeout', 408); +enum _RequestType { + get, +} + +const _expireDuration = Duration(seconds: 2); + +class _CachedResponse { + final _RequestType requestType; + final Uri requestUri; + final Map requestBody; + final Map headers; + final http.Response response; + final DateTime requestTime; + + _CachedResponse( + {required this.requestType, + required this.requestUri, + required this.requestBody, + required this.headers, + required this.response, + required this.requestTime}); + + factory _CachedResponse.requestStub(_RequestType type, Uri uri, + Map? headers, Map? body) => + _CachedResponse( + requestType: type, + requestUri: uri, + requestBody: body ?? {}, + headers: headers ?? {}, + response: http.Response('', 555), + requestTime: DateTime(0), + ); + + factory _CachedResponse.response( + _RequestType type, + Uri uri, + Map? headers, + Map? body, + http.Response response, + ) => + _CachedResponse( + requestType: type, + requestUri: uri, + requestBody: body ?? {}, + headers: headers ?? {}, + response: response, + requestTime: DateTime.now(), + ); + + @override + bool operator ==(Object other) => + identical(this, other) || + other is _CachedResponse && + runtimeType == other.runtimeType && + requestType == other.requestType && + requestUri == other.requestUri; + + @override + int get hashCode => requestType.hashCode ^ requestUri.hashCode; +} + +class _ExpiringRequestCache { + final _responses = <_CachedResponse, _CachedResponse>{}; + + void _cleanupCache() { + final expireTime = DateTime.now().subtract(_expireDuration); + _logger + .finest('Cleaning up request cache with ${_responses.length} entries'); + _responses.removeWhere((key, value) { + final expired = key.requestTime.isBefore(expireTime); + if (expired) { + _logger.finest( + 'Expiring request: ${value.requestType} => ${value.requestUri}'); + } + return expired; + }); + _logger.finest('Cleaned up request cache has ${_responses.length} entries'); + } + + Future getRequestOrExecute({ + required Uri url, + Map? headers, + Map? body, + Duration? timeout, + }) async { + _cleanupCache(); + const type = _RequestType.get; + final requestStub = _CachedResponse.requestStub( + type, + url, + headers, + null, + ); + + late final http.Response response; + if (_responses.containsKey(requestStub)) { + print('Returning cached response for $type => $url'); + response = _responses[requestStub]?.response ?? http.Response('', 555); + } else { + final request = http.get( + url, + headers: headers, + ); + + response = await request.timeout( + timeout ?? apiCallTimeout, + onTimeout: requestTimeout, + ); + + final cacheEntry = _CachedResponse.response( + type, + url, + headers ?? {}, + body ?? {}, + response, + ); + + print('Adding cached response for $type => $url'); + _responses[cacheEntry] = cacheEntry; + } + + return response; + } +} + +final _cache = _ExpiringRequestCache(); + FutureResult, ExecError> getUrl( Uri url, { Map? headers, @@ -23,14 +150,10 @@ FutureResult, ExecError> getUrl( requestHeaders['Cookie'] = 'XDEBUG_SESSION=PHPSTORM;path=/'; } try { - final request = http.get( - url, - headers: requestHeaders, - ); - - final response = await request.timeout( - timeout ?? apiCallTimeout, - onTimeout: requestTimeout, + final response = await _cache.getRequestOrExecute( + url: url, + headers: headers, + timeout: timeout ?? apiCallTimeout, ); if (response.statusCode != 200) { @@ -87,6 +210,7 @@ FutureResult putUrl( Uri url, Map body, { Map? headers, + Duration? timeout, }) async { _logger.fine('PUT: $url \n Body: $body'); try { @@ -97,7 +221,7 @@ FutureResult putUrl( ); final response = await request.timeout( - apiCallTimeout, + timeout ?? apiCallTimeout, onTimeout: requestTimeout, ); @@ -117,6 +241,7 @@ FutureResult deleteUrl( Uri url, Map body, { Map? headers, + Duration? timeout, }) async { _logger.fine('DELETE: $url'); try { @@ -127,7 +252,7 @@ FutureResult deleteUrl( ); final response = await request.timeout( - apiCallTimeout, + timeout ?? apiCallTimeout, onTimeout: requestTimeout, );