From aab292c3535e3588a6d965a7d22933fb194e8708 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20Sundf=C3=B8r?= Date: Wed, 1 Oct 2025 09:49:11 +0200 Subject: [PATCH] Workaround for broken cache expiry handling Deno's cache API does not respect expiry dates. This meant that feeds were cached much longer than intended. This PR implments a manual cache handling instead of relying on Deno's implementation. Relevant issue: https://github.com/denoland/deno/issues/25795 --- lib/utils.ts | 1 + routes/api/feeds/[seriesId].ts | 23 +++++++++++++++++++++-- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/lib/utils.ts b/lib/utils.ts index a31a213..2427e6c 100644 --- a/lib/utils.ts +++ b/lib/utils.ts @@ -27,6 +27,7 @@ export function responseXML(body: string, status: Status) { export const withExpiry = (response: Response, ttlInSeconds: number) => { const clonedResponse = response.clone(); clonedResponse.headers.set("Cache-Control", `max-age=${ttlInSeconds}`); + clonedResponse.headers.set("Expires", new Date(Date.now() + ttlInSeconds * 1000).toUTCString()); return clonedResponse; }; diff --git a/routes/api/feeds/[seriesId].ts b/routes/api/feeds/[seriesId].ts index 358822c..1b22fc9 100644 --- a/routes/api/feeds/[seriesId].ts +++ b/routes/api/feeds/[seriesId].ts @@ -5,6 +5,23 @@ import { responseJSON, responseXML, withExpiry } from "../../../lib/utils.ts"; const FEED_CACHE_KEY = "feeds-cache"; +// Deno's cache API does not respect these headers. +// Until it does, manual expiry handling is needed. +// When it's working, this function would not be needed. +// See: https://github.com/denoland/deno/issues/25795 +function isExpired(headers: Headers) { + const expiresHeader = headers.get("Expires"); + if (!expiresHeader) { + console.log("No Expires header found, treating as expired"); + return true; + } + + const now = new Date(); + const expiryDate = new Date(expiresHeader); + const expired = now > expiryDate; + return expired; +} + export const handler = async ( request: Request, ctx: FreshContext, @@ -13,11 +30,13 @@ export const handler = async ( const cache = await caches.open(FEED_CACHE_KEY); const cachedResponse = await cache.match(request); - if (cachedResponse) { + + if (cachedResponse && !isExpired(cachedResponse.headers)) { console.log(`Cache hit for feed ${seriesId}`); return cachedResponse; } + console.log(`Fetching feed for ${seriesId}`); const series = await caching.getSeries({ id: seriesId }); if (!series) { return responseJSON({ message: "Series not found" }, STATUS_CODE.NotFound); @@ -28,7 +47,7 @@ export const handler = async ( const response = withExpiry( responseXML(feed, STATUS_CODE.OK), // 2 hours - 60 * 60 * 2, + 2 * 60 * 60, ); await cache.put(request, response.clone()); -- 2.40.1