Skip to content
This repository was archived by the owner on Mar 13, 2025. It is now read-only.

Commit 38b821d

Browse files
committed
Add support for If-Modified-Since to Cache#match, ref #246
1 parent 70a7f13 commit 38b821d

File tree

2 files changed

+79
-19
lines changed

2 files changed

+79
-19
lines changed

packages/cache/src/cache.ts

Lines changed: 44 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,44 @@ function getExpirationTtl(
110110
}
111111
}
112112

113+
// https://datatracker.ietf.org/doc/html/rfc7231#section-7.1.1.1
114+
const utcDateRegexp =
115+
/^(Mon|Tue|Wed|Thu|Fri|Sat|Sun), \d\d (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) \d\d\d\d \d\d:\d\d:\d\d GMT$/;
116+
function parseUTCDate(value: string): number {
117+
return utcDateRegexp.test(value) ? Date.parse(value) : NaN;
118+
}
119+
120+
function getMatchResponse(
121+
reqHeaders: Headers,
122+
resStatus: number,
123+
resHeaders: Headers,
124+
resBody: Uint8Array
125+
): Response {
126+
// If `If-Modified-Since` is set, perform a conditional request
127+
const reqIfModifiedSinceHeader = reqHeaders.get("If-Modified-Since");
128+
const resLastModifiedHeader = resHeaders.get("Last-Modified");
129+
if (reqIfModifiedSinceHeader !== null && resLastModifiedHeader !== null) {
130+
const reqIfModifiedSince = parseUTCDate(reqIfModifiedSinceHeader);
131+
const resLastModified = parseUTCDate(resLastModifiedHeader);
132+
// Comparison of NaN's (invalid dates), will always result in `false`
133+
if (resLastModified <= reqIfModifiedSince) {
134+
return new Response(null, {
135+
status: 304, // Not Modified
136+
headers: resHeaders,
137+
});
138+
}
139+
}
140+
141+
// If `Range` is set, return a partial response
142+
const reqRangeHeader = reqHeaders.get("Range");
143+
if (reqRangeHeader !== null) {
144+
return _getRangeResponse(reqRangeHeader, resStatus, resHeaders, resBody);
145+
}
146+
147+
// Otherwise, return the full response
148+
return new Response(resBody, { status: resStatus, headers: resHeaders });
149+
}
150+
113151
export interface InternalCacheOptions {
114152
formDataFiles?: boolean;
115153
clock?: Clock;
@@ -221,24 +259,12 @@ export class Cache implements CacheInterface {
221259

222260
// Returning a @miniflare/core Response so we don't need to convert
223261
// BaseResponse to one when dispatching fetch events
224-
let res: Response;
225-
const rangeHeader = req.headers.get("Range");
226-
if (rangeHeader === null) {
227-
// If this wasn't a `Range` request, return the full response...
228-
res = new Response(cached.value, {
229-
status: cached.metadata.status,
230-
headers,
231-
});
232-
} else {
233-
// ...otherwise, return a partial response
234-
res = _getRangeResponse(
235-
rangeHeader,
236-
cached.metadata.status,
237-
headers,
238-
cached.value
239-
);
240-
}
241-
262+
let res = getMatchResponse(
263+
req.headers,
264+
cached.metadata.status,
265+
headers,
266+
cached.value
267+
);
242268
if (!this.#formDataFiles) res = withStringFormDataFiles(res);
243269
return withImmutableHeaders(res);
244270
}

packages/cache/test/cache.spec.ts

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,41 @@ test("Cache: match throws if attempting to load cached response created with Min
238238
"load cached data created with Miniflare 1 and must delete it.",
239239
});
240240
});
241-
test("Cache: match returns 206 Response with Range header", async (t) => {
241+
test("Cache: match respects If-Modified-Since header", async (t) => {
242+
const { cache } = t.context;
243+
const res = new Response("value", {
244+
headers: {
245+
"Last-Modified": "Tue, 13 Sep 2022 12:00:00 GMT",
246+
"Cache-Control": "max-age=3600",
247+
},
248+
});
249+
await cache.put("http://localhost:8787/test", res);
250+
251+
const ifModifiedSince = (value: string) =>
252+
new BaseRequest("http://localhost:8787/test", {
253+
headers: { "If-Modified-Since": value },
254+
});
255+
256+
// Check returns 200 if modified after `If-Modified-Since`
257+
let cacheRes = await cache.match(
258+
ifModifiedSince("Tue, 13 Sep 2022 11:00:00 GMT")
259+
);
260+
t.is(cacheRes?.status, 200);
261+
// Check returns 304 if modified on `If-Modified-Since`
262+
cacheRes = await cache.match(
263+
ifModifiedSince("Tue, 13 Sep 2022 12:00:00 GMT")
264+
);
265+
t.is(cacheRes?.status, 304);
266+
// Check returns 304 if modified before `If-Modified-Since`
267+
cacheRes = await cache.match(
268+
ifModifiedSince("Tue, 13 Sep 2022 13:00:00 GMT")
269+
);
270+
t.is(cacheRes?.status, 304);
271+
// Check returns 200 if `If-Modified-Since` is not a "valid" UTC date
272+
cacheRes = await cache.match(ifModifiedSince("13 Sep 2022 13:00:00 GMT"));
273+
t.is(cacheRes?.status, 200);
274+
});
275+
test("Cache: match respects Range header", async (t) => {
242276
const { cache } = t.context;
243277
await cache.put("http://localhost:8787/test", testResponse("0123456789"));
244278
const req = new BaseRequest("http://localhost:8787/test", {

0 commit comments

Comments
 (0)