Compare commits

...

1 Commits

Author SHA1 Message Date
Olav Sundfør aab292c353 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
2025-10-01 09:49:11 +02:00
2 changed files with 22 additions and 2 deletions

View File

@ -27,6 +27,7 @@ export function responseXML(body: string, status: Status) {
export const withExpiry = <T>(response: Response, ttlInSeconds: number) => { export const withExpiry = <T>(response: Response, ttlInSeconds: number) => {
const clonedResponse = response.clone(); const clonedResponse = response.clone();
clonedResponse.headers.set("Cache-Control", `max-age=${ttlInSeconds}`); clonedResponse.headers.set("Cache-Control", `max-age=${ttlInSeconds}`);
clonedResponse.headers.set("Expires", new Date(Date.now() + ttlInSeconds * 1000).toUTCString());
return clonedResponse; return clonedResponse;
}; };

View File

@ -5,6 +5,23 @@ import { responseJSON, responseXML, withExpiry } from "../../../lib/utils.ts";
const FEED_CACHE_KEY = "feeds-cache"; 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 ( export const handler = async (
request: Request, request: Request,
ctx: FreshContext, ctx: FreshContext,
@ -13,11 +30,13 @@ export const handler = async (
const cache = await caches.open(FEED_CACHE_KEY); const cache = await caches.open(FEED_CACHE_KEY);
const cachedResponse = await cache.match(request); const cachedResponse = await cache.match(request);
if (cachedResponse) {
if (cachedResponse && !isExpired(cachedResponse.headers)) {
console.log(`Cache hit for feed ${seriesId}`); console.log(`Cache hit for feed ${seriesId}`);
return cachedResponse; return cachedResponse;
} }
console.log(`Fetching feed for ${seriesId}`);
const series = await caching.getSeries({ id: seriesId }); const series = await caching.getSeries({ id: seriesId });
if (!series) { if (!series) {
return responseJSON({ message: "Series not found" }, STATUS_CODE.NotFound); return responseJSON({ message: "Series not found" }, STATUS_CODE.NotFound);
@ -28,7 +47,7 @@ export const handler = async (
const response = withExpiry( const response = withExpiry(
responseXML(feed, STATUS_CODE.OK), responseXML(feed, STATUS_CODE.OK),
// 2 hours // 2 hours
60 * 60 * 2, 2 * 60 * 60,
); );
await cache.put(request, response.clone()); await cache.put(request, response.clone());