import 'dart:convert'; import 'dart:math'; import 'package:http/http.dart'; import 'constants.dart'; import 'request_headers.dart'; import 'date_parser.dart'; import 'store/base.dart'; class HttpGetCache extends BaseClient { final HttpCacheStore store; final Client inner; HttpGetCache(this.inner, this.store); static Future networkError(Object? error) async { return StreamedResponse( Stream.value(utf8.encode('$error')), 500, ); } static Future offlineError(Object? error) async { return StreamedResponse( Stream.value(utf8.encode('$error')), 503, ); } static Future gatewayTimeout() async { return StreamedResponse( Stream.value(utf8.encode('Gateway Timeout')), 504, ); } Future save(BaseRequest request) async { final reqCC = request.cacheControl; final res = await inner.send(request); final resCC = res.cacheControl; if (res.statusCode == 200) { if (reqCC.noStore || resCC.noStore || resCC.noCache) { return res; } else { return await store.set(request, res); } } else { final cached = await store.get(request); if (cached != null) { final cachedCC = cached.cacheControl; final now = getDateInt(); final date = cached.requestHeaders.created; if (reqCC.staleIfError.$2 || resCC.staleIfError.$2) { return cached; } else if (date != null && now < (date + cachedCC.maxAge + max( cachedCC.staleIfError.$1, reqCC.staleIfError.$1, ))) { return cached; } } } return res; } Stream stream(BaseRequest request) async* { final method = request.method.toLowerCase(); final reqCC = request.cacheControl; if (kIsWeb || method != 'get') { yield await inner.send(request); return; } // Check for early fetch var cached = await store.get(request); bool needsUpdate = true; if (cached == null && reqCC.onlyIfCached) { yield await gatewayTimeout(); return; } // Check for cache if (cached != null) { final cachedCC = cached.cacheControl; if (cachedCC.mustRevalidate || cachedCC.noCache || reqCC.noCache) { needsUpdate = true; } else { final now = getDateInt(); final date = cached.requestHeaders.created; if (date != null && now < (date + cachedCC.maxAge)) { needsUpdate = false; yield cached; } else if (date != null && now < (date + cachedCC.maxAge + cachedCC.staleWhileRevalidate.$1)) { yield cached; } else if (cachedCC.staleWhileRevalidate.$2) { yield cached; } if (!cachedCC.immutable && needsUpdate) { // Check for new eTag, Last-Modified change final headRes = await inner.head(request.url); final headHeaders = headRes.requestHeaders; if (headRes.statusCode == 200) { // Check eTag if ((cached.requestHeaders.eTag + headHeaders.eTag).isNotEmpty && cached.requestHeaders.eTag == headHeaders.eTag) { needsUpdate = false; } if ((max( cached.requestHeaders.lastModified ?? 0, headHeaders.lastModified ?? 0, ) > 0) && cached.requestHeaders.lastModified == headHeaders.lastModified) { needsUpdate = false; } } else if (headRes.statusCode == 304) { // Or 304 cache is still good to reuse needsUpdate = false; if (cachedCC.mustRevalidate || cachedCC.noCache || reqCC.noCache) { yield cached; } } } } } if (!needsUpdate) return; yield await save(request); } @override Future send(BaseRequest request) { return stream(request).last; } }