Skip to content

Commit

Permalink
fix: etag caching HTTP headers (#2504)
Browse files Browse the repository at this point in the history
  • Loading branch information
marvinhagemeister committed Jun 25, 2024
1 parent 0f6b4f6 commit 72811da
Show file tree
Hide file tree
Showing 4 changed files with 25 additions and 10 deletions.
2 changes: 2 additions & 0 deletions src/build_cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export interface StaticFile {
hash: string | null;
size: number;
readable: ReadableStream<Uint8Array> | Uint8Array;
close(): void;
}

export interface BuildCache {
Expand Down Expand Up @@ -101,6 +102,7 @@ export class ProdBuildCache implements BuildCache {
hash: info.hash,
size: stat.size,
readable: file.readable,
close: () => file.close(),
};
}

Expand Down
2 changes: 2 additions & 0 deletions src/dev/dev_build_cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ export class MemoryBuildCache implements DevBuildCache {
hash: processed.hash,
readable: processed.content,
size: processed.content.byteLength,
close: () => {},
};
}

Expand All @@ -65,6 +66,7 @@ export class MemoryBuildCache implements DevBuildCache {
hash: null,
size: stat.size,
readable: file.readable,
close: () => file.close(),
};
} catch (_err) {
return null;
Expand Down
10 changes: 8 additions & 2 deletions src/middlewares/static_files.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,15 @@ export function staticFiles<T>(): MiddlewareFn<T> {
}

if (req.method !== "GET" && req.method !== "HEAD") {
file.close();
return new Response("Method Not Allowed", { status: 405 });
}

const cacheKey = url.searchParams.get(ASSET_CACHE_BUST_KEY);
if (cacheKey !== null && BUILD_ID !== cacheKey) {
url.searchParams.delete(ASSET_CACHE_BUST_KEY);
const location = url.pathname + url.search;
file.close();
return new Response(null, {
status: 307,
headers: {
Expand All @@ -67,15 +69,19 @@ export function staticFiles<T>(): MiddlewareFn<T> {
} else {
const ifNoneMatch = req.headers.get("If-None-Match");
if (
etag !== null &&
(ifNoneMatch === etag || ifNoneMatch === "W/" + etag)
ifNoneMatch !== null &&
(ifNoneMatch === etag || ifNoneMatch === `W/"${etag}"`)
) {
file.close();
return new Response(null, { status: 304, headers });
} else if (etag !== null) {
headers.set("Etag", `W/"${etag}"`);
}
}

headers.set("Content-Length", String(file.size));
if (req.method === "HEAD") {
file.close();
return new Response(null, { status: 200, headers });
}

Expand Down
21 changes: 13 additions & 8 deletions src/middlewares/static_files_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ class MockBuildCache implements BuildCache {
hash: info.hash,
size: text.byteLength,
readable: text,
close: () => {},
});
}
}
Expand Down Expand Up @@ -79,18 +80,22 @@ Deno.test("static files - etag", async () => {
{ buildCache },
);

const headers = new Headers();
headers.append("If-None-Match", "123");
const cacheUrl = `/foo.css?${ASSET_CACHE_BUST_KEY}=${BUILD_ID}`;
const res = await server.get(cacheUrl, { headers });
let res = await server.get(cacheUrl);
await res.body?.cancel();
expect(res.headers.get("Etag")).toEqual('W/"123"');

let headers = new Headers();
headers.append("If-None-Match", "123");
res = await server.get(cacheUrl, { headers });
await res.body?.cancel();
expect(res.status).toEqual(304);

const headers2 = new Headers();
headers2.append("If-None-Match", "W/123");
const res2 = await server.get(cacheUrl, { headers });
await res2.body?.cancel();
expect(res2.status).toEqual(304);
headers = new Headers();
headers.append("If-None-Match", 'W/"123"');
res = await server.get(cacheUrl, { headers });
await res.body?.cancel();
expect(res.status).toEqual(304);
});

Deno.test("static files - 404 on missing favicon.ico", async () => {
Expand Down

0 comments on commit 72811da

Please sign in to comment.