Add caching to feed endpoint #45
|
|
@ -1,5 +1,5 @@
|
||||||
import { assertEquals, assertNotEquals } from "$std/assert/mod.ts";
|
import { assertEquals, assertNotEquals } from "$std/assert/mod.ts";
|
||||||
import { responseJSON, responseXML } from "./utils.ts";
|
import { responseJSON, responseXML, withExpiry } from "./utils.ts";
|
||||||
|
|
||||||
Deno.test("JSON response is stringified", async () => {
|
Deno.test("JSON response is stringified", async () => {
|
||||||
const body = { message: "Hello, World!" };
|
const body = { message: "Hello, World!" };
|
||||||
|
|
@ -14,3 +14,41 @@ Deno.test("XML resopnse is _not_ stringified", async () => {
|
||||||
assertEquals(responseBody, body);
|
assertEquals(responseBody, body);
|
||||||
assertNotEquals(responseBody, JSON.stringify(body));
|
assertNotEquals(responseBody, JSON.stringify(body));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Deno.test(
|
||||||
|
"Response with cache control returns a response with the correct header",
|
||||||
|
() => {
|
||||||
|
const response = responseJSON({ message: "Hello, World!" }, 200);
|
||||||
|
const cachedResponse = withExpiry(response, 3600);
|
||||||
|
assertEquals(cachedResponse.headers.get("Cache-Control"), "max-age=3600");
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
Deno.test(
|
||||||
|
"Response with cache control returns the same body as the original",
|
||||||
|
async () => {
|
||||||
|
const body = { message: "Hello, World!" };
|
||||||
|
const response = responseJSON(body, 200);
|
||||||
|
const cachedResponse = withExpiry(response, 3600);
|
||||||
|
assertEquals(await cachedResponse.text(), await response.text());
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
Deno.test("Response with cache control does not modify the original", () => {
|
||||||
|
const response = responseJSON({ message: "Hello, World!" }, 200);
|
||||||
|
withExpiry(response, 3600);
|
||||||
|
assertEquals(response.headers.get("Cache-Control"), null);
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test(
|
||||||
|
"Response with cache control returns the correct number of seconds",
|
||||||
|
() => {
|
||||||
|
const response = responseJSON({ message: "Hello, World!" }, 200);
|
||||||
|
const ttlInSeconds = 1234;
|
||||||
|
const cachedResponse = withExpiry(response, ttlInSeconds);
|
||||||
|
assertEquals(
|
||||||
|
cachedResponse.headers.get("Cache-Control"),
|
||||||
|
`max-age=${ttlInSeconds}`,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,12 @@ export function responseXML(body: string, status: Status) {
|
||||||
return response(body, status, "xml");
|
return response(body, status, "xml");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const withExpiry = <T>(response: Response, ttlInSeconds: number) => {
|
||||||
|
const clonedResponse = response.clone();
|
||||||
|
clonedResponse.headers.set("Cache-Control", `max-age=${ttlInSeconds}`);
|
||||||
|
return clonedResponse;
|
||||||
|
};
|
||||||
|
|
||||||
function response(body: string, status: number, type: "json" | "xml") {
|
function response(body: string, status: number, type: "json" | "xml") {
|
||||||
return new Response(body, {
|
return new Response(body, {
|
||||||
status,
|
status,
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,36 @@
|
||||||
import { FreshContext, STATUS_CODE } from "$fresh/server.ts";
|
import { FreshContext, STATUS_CODE } from "$fresh/server.ts";
|
||||||
import { caching } from "../../../lib/caching.ts";
|
import { caching } from "../../../lib/caching.ts";
|
||||||
import { rss } from "../../../lib/rss.ts";
|
import { rss } from "../../../lib/rss.ts";
|
||||||
import { responseJSON, responseXML } from "../../../lib/utils.ts";
|
import { responseJSON, responseXML, withExpiry } from "../../../lib/utils.ts";
|
||||||
|
|
||||||
export const handler = async (_req: Request, ctx: FreshContext): Promise<Response> => {
|
const FEED_CACHE_KEY = "feeds-cache";
|
||||||
|
|
||||||
|
export const handler = async (
|
||||||
|
request: Request,
|
||||||
|
ctx: FreshContext,
|
||||||
|
): Promise<Response> => {
|
||||||
const seriesId = ctx.params.seriesId;
|
const seriesId = ctx.params.seriesId;
|
||||||
|
|
||||||
|
const cache = await caches.open(FEED_CACHE_KEY);
|
||||||
|
const cachedResponse = await cache.match(request);
|
||||||
|
if (cachedResponse) {
|
||||||
|
console.log(`Cache hit for feed ${seriesId}`);
|
||||||
|
return cachedResponse;
|
||||||
|
}
|
||||||
|
|
||||||
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
const feed = rss.assembleFeed(series);
|
const feed = rss.assembleFeed(series);
|
||||||
|
|
||||||
const xml = responseXML(feed, STATUS_CODE.OK);
|
const response = withExpiry(
|
||||||
|
responseXML(feed, STATUS_CODE.OK),
|
||||||
|
// 2 hours
|
||||||
|
60 * 60 * 2,
|
||||||
|
);
|
||||||
|
|
||||||
return xml;
|
await cache.put(request, response.clone());
|
||||||
|
return response;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue