diff --git a/lib/util/http/cache/abstract-http-cache-provider.ts b/lib/util/http/cache/abstract-http-cache-provider.ts index 12ed412a154f58eb5c556c3dad29d34073335330..46ab61e258466ddc9cb4a558f845ed5aacb375fb 100644 --- a/lib/util/http/cache/abstract-http-cache-provider.ts +++ b/lib/util/http/cache/abstract-http-cache-provider.ts @@ -39,7 +39,10 @@ export abstract class AbstractHttpCacheProvider implements HttpCacheProvider { } } - bypassServerResponse<T>(_url: string): Promise<HttpResponse<T> | null> { + bypassServer<T>( + _url: string, + _ignoreSoftTtl: boolean, + ): Promise<HttpResponse<T> | null> { return Promise.resolve(null); } diff --git a/lib/util/http/cache/package-http-cache-provider.spec.ts b/lib/util/http/cache/package-http-cache-provider.spec.ts index 3b01971528e9950ae5f8abcdc193c7f8d04cce09..e5951b2422c104a1ce1608ccbcf6045b31e33d6b 100644 --- a/lib/util/http/cache/package-http-cache-provider.spec.ts +++ b/lib/util/http/cache/package-http-cache-provider.spec.ts @@ -109,4 +109,23 @@ describe('util/http/cache/package-http-cache-provider', () => { }, }); }); + + it('serves stale response during revalidation error', async () => { + mockTime('2024-06-15T00:15:00.000Z'); + cache[url] = { + etag: 'etag-value', + lastModified: 'Fri, 15 Jun 2024 00:00:00 GMT', + httpResponse: { statusCode: 200, body: 'cached response' }, + timestamp: '2024-06-15T00:00:00.000Z', + }; + const cacheProvider = new PackageHttpCacheProvider({ + namespace: '_test-namespace', + }); + httpMock.scope(url).get('').reply(500); + + const res = await http.get(url, { cacheProvider }); + + expect(res.body).toBe('cached response'); + expect(packageCache.set).not.toHaveBeenCalled(); + }); }); diff --git a/lib/util/http/cache/package-http-cache-provider.ts b/lib/util/http/cache/package-http-cache-provider.ts index 6b79e9ce42869e60fe76e36b2f253ef9bac67963..0aa1607ea598257a38132d74589735e0b5d64390 100644 --- a/lib/util/http/cache/package-http-cache-provider.ts +++ b/lib/util/http/cache/package-http-cache-provider.ts @@ -37,14 +37,19 @@ export class PackageHttpCacheProvider extends AbstractHttpCacheProvider { await set(this.namespace, url, data, this.hardTtlMinutes); } - override async bypassServerResponse<T>( + override async bypassServer<T>( url: string, + ignoreSoftTtl = false, ): Promise<HttpResponse<T> | null> { const cached = await this.get(url); if (!cached) { return null; } + if (ignoreSoftTtl) { + return cached.httpResponse as HttpResponse<T>; + } + const cachedAt = DateTime.fromISO(cached.timestamp); const deadline = cachedAt.plus({ minutes: this.softTtlMinutes }); const now = DateTime.now(); diff --git a/lib/util/http/cache/types.ts b/lib/util/http/cache/types.ts index 885e105cec1477e2734deb7b2643bbb22ad432d8..7a763fd6317f686eb30ed6d2452f0526159fbd40 100644 --- a/lib/util/http/cache/types.ts +++ b/lib/util/http/cache/types.ts @@ -6,7 +6,10 @@ export interface HttpCacheProvider { opts: T, ): Promise<void>; - bypassServerResponse<T>(url: string): Promise<HttpResponse<T> | null>; + bypassServer<T>( + url: string, + ignoreSoftTtl?: boolean, + ): Promise<HttpResponse<T> | null>; wrapServerResponse<T>( url: string, diff --git a/lib/util/http/index.ts b/lib/util/http/index.ts index bee51858a44c3626242bddaa728a014bf7415a9a..e05e745cae6a4fcbbf686cd7fd8bd34cb4e4a93d 100644 --- a/lib/util/http/index.ts +++ b/lib/util/http/index.ts @@ -181,8 +181,7 @@ export class Http<Opts extends HttpOptions = HttpOptions> { options.timeout ??= 60000; const { cacheProvider } = options; - const cachedResponse = await cacheProvider?.bypassServerResponse<T>(url); - // istanbul ignore if + const cachedResponse = await cacheProvider?.bypassServer<T>(url); if (cachedResponse) { return cachedResponse; } @@ -252,6 +251,16 @@ export class Http<Opts extends HttpOptions = HttpOptions> { if (abortOnError && !abortIgnoreStatusCodes?.includes(err.statusCode)) { throw new ExternalHostError(err); } + + const staleResponse = await cacheProvider?.bypassServer<T>(url, true); + if (staleResponse) { + logger.debug( + { err }, + `Request error: returning stale cache instead for ${url}`, + ); + return staleResponse; + } + throw err; } }