Skip to content

Commit 16e324f

Browse files
committed
Cache public pages by canonical revision
1 parent 55fba5c commit 16e324f

3 files changed

Lines changed: 68 additions & 5 deletions

File tree

packages/runtime-cloudflare/src/worker.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,12 @@ async function runCoordinatedWordPressRequest(request: Request, env: Env, coordi
212212
if (!lease.pointer || lease.pointer.revision !== bootstrapped.pointer.revision) throw new Error("Canonical bootstrap promotion was not observed by its next lease.")
213213
cacheRuntime(lease.pointer, bootstrapped)
214214
}
215+
const cachedPage = route === "wordpress" ? await matchWordPressPageCache(request, lease.pointer) : null
216+
if (cachedPage) {
217+
await releaseLease(coordinator, request.url, lease)
218+
finalized = true
219+
return cachedPage
220+
}
215221
runtime = await getRuntime(env, lease.pointer, new URL(request.url).origin)
216222
const mutatesCanonicalState = isMutation(request, route)
217223
let response: Response
@@ -236,6 +242,7 @@ async function runCoordinatedWordPressRequest(request: Request, env: Env, coordi
236242
}
237243
finalized = true
238244
if (mutatesCanonicalState) await discardRuntime(runtime)
245+
else if (route === "wordpress") response = await cacheWordPressPage(request, lease.pointer, response)
239246
return response
240247
} catch (error) {
241248
if (!finalized) await abortLease(coordinator, request.url, lease)
@@ -244,6 +251,54 @@ async function runCoordinatedWordPressRequest(request: Request, env: Env, coordi
244251
}
245252
}
246253

254+
function isCacheableWordPressPageRequest(request: Request): boolean {
255+
if (request.method !== "GET" && request.method !== "HEAD") return false
256+
if (request.headers.has("authorization") || request.headers.has("cookie")) return false
257+
const url = new URL(request.url)
258+
if (["/wp-admin", "/wp-login.php", "/wp-json"].some((path) => url.pathname === path || url.pathname.startsWith(`${path}/`))) return false
259+
return !url.searchParams.has("preview") && !url.searchParams.has("rest_route")
260+
}
261+
262+
async function matchWordPressPageCache(request: Request, pointer: MarkdownPointer): Promise<Response | null> {
263+
if (!isCacheableWordPressPageRequest(request)) return null
264+
const cache = typeof caches === "undefined" ? undefined : (caches as CacheStorage & { default?: Cache }).default
265+
if (!cache) return null
266+
try {
267+
const cached = await cache.match(wordPressPageCacheKey(request, pointer))
268+
return cached ? pageCacheResponse(cached, request.method === "HEAD", "hit") : null
269+
} catch {
270+
return null
271+
}
272+
}
273+
274+
async function cacheWordPressPage(request: Request, pointer: MarkdownPointer, response: Response): Promise<Response> {
275+
if (!isCacheableWordPressPageRequest(request) || response.status !== 200 || !response.headers.get("content-type")?.includes("text/html") || response.headers.has("set-cookie")) return response
276+
if (request.method === "HEAD") return pageCacheResponse(response, true, "miss")
277+
const cache = typeof caches === "undefined" ? undefined : (caches as CacheStorage & { default?: Cache }).default
278+
const cacheable = pageCacheResponse(response, false, "miss")
279+
if (cache) {
280+
try {
281+
await cache.put(wordPressPageCacheKey(request, pointer), cacheable.clone())
282+
} catch {
283+
// Page caching is an optimization; canonical rendering remains authoritative.
284+
}
285+
}
286+
return cacheable
287+
}
288+
289+
function wordPressPageCacheKey(request: Request, pointer: MarkdownPointer): Request {
290+
const url = new URL(request.url)
291+
url.searchParams.set("__wp_codebox_revision", pointer.revision)
292+
return new Request(url, { method: "GET" })
293+
}
294+
295+
function pageCacheResponse(response: Response, head: boolean, status: "hit" | "miss"): Response {
296+
const headers = new Headers(response.headers)
297+
headers.set("cache-control", "public, max-age=60, s-maxage=31536000")
298+
headers.set("x-wp-codebox-page-cache", status)
299+
return new Response(head ? null : response.body, { status: response.status, statusText: response.statusText, headers })
300+
}
301+
247302
function isMutation(request: Request, route: "wordpress" | "health" | "r2-mutate"): boolean {
248303
return route === "r2-mutate" || !["GET", "HEAD", "OPTIONS"].includes(request.method)
249304
}

scripts/cloudflare-local-gate.mjs

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ try {
2121
const coldHome = await timedWordPressPage(origin, "cold explanatory homepage")
2222
const warmHome = await timedWordPressPage(origin, "warm explanatory homepage")
2323
await assertExplanatoryHomepage(warmHome.body)
24-
if (warmHome.elapsedMs >= coldHome.elapsedMs || warmHome.elapsedMs > 2_000) {
25-
throw new Error(`Warm explanatory homepage did not reuse the canonical runtime: cold=${coldHome.elapsedMs}ms warm=${warmHome.elapsedMs}ms.`)
24+
if (coldHome.cacheStatus !== "miss" || warmHome.cacheStatus !== "hit" || warmHome.elapsedMs >= coldHome.elapsedMs || warmHome.elapsedMs > 500) {
25+
throw new Error(`Warm explanatory homepage did not use its revision cache: cold=${coldHome.elapsedMs}ms/${coldHome.cacheStatus} warm=${warmHome.elapsedMs}ms/${warmHome.cacheStatus}.`)
2626
}
2727
console.log(`Explanatory homepage timing: cold=${coldHome.elapsedMs}ms warm=${warmHome.elapsedMs}ms.`)
2828
const adminHtml = await login()
@@ -44,7 +44,7 @@ try {
4444
await assertLinkedAssets(restartedAdmin, "admin after cold restart")
4545
cookies.length = 0
4646
await login()
47-
console.log("Cloudflare local runtime gate passed: explanatory homepage, complete block styles, warm runtime reuse, login, dashboard, post editor, concurrent canonical mutations, authenticated REST post creation, frontend/admin/editor assets, and cold-restart session persistence.")
47+
console.log("Cloudflare local runtime gate passed: explanatory homepage, complete block styles, revision page cache, login, dashboard, post editor, concurrent canonical mutations, authenticated REST post creation, frontend/admin/editor assets, and cold-restart session persistence.")
4848
} finally {
4949
await stopWorker()
5050
await rm(stateDirectory, { recursive: true, force: true })
@@ -164,8 +164,11 @@ async function assertWordPressPage(target, label) {
164164

165165
async function timedWordPressPage(target, label) {
166166
const startedAt = performance.now()
167-
const body = await assertWordPressPage(target, label)
168-
return { body, elapsedMs: Math.round(performance.now() - startedAt) }
167+
const response = await request(target)
168+
const body = await response.text()
169+
assertNoPhpDiagnostics(body, label)
170+
if (response.status !== 200 || !response.headers.get("content-type")?.includes("text/html") || !/<html[\s>]/i.test(body)) throw new Error(`Expected an HTML ${label}, received ${response.status}: ${body}`)
171+
return { body, elapsedMs: Math.round(performance.now() - startedAt), cacheStatus: response.headers.get("x-wp-codebox-page-cache") }
169172
}
170173

171174
async function assertExplanatoryHomepage(html) {

tests/cloudflare-runtime.test.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,11 @@ test("Cloudflare keeps PHP-WASM in the entry Worker and uses the Durable Object
250250
assert.match(worker, /"x-wp-codebox-static": "wordpress-archive"/)
251251
assert.match(worker, /cache\.match\(request\)/)
252252
assert.match(worker, /cache\.put\(request, response\.clone\(\)\)/)
253+
assert.match(worker, /matchWordPressPageCache\(request, lease\.pointer\)/)
254+
assert.match(worker, /cacheWordPressPage\(request, lease\.pointer, response\)/)
255+
assert.match(worker, /__wp_codebox_revision/)
256+
assert.match(worker, /"x-wp-codebox-page-cache"/)
257+
assert.match(worker, /"public, max-age=60, s-maxage=31536000"/)
253258
assert.match(materializer, /materializeWordPressRuntimeArtifact\(php, bucket, wordpressRuntimeArtifactManifest/)
254259
assert.doesNotMatch(materializer, /decodeRemoteZip\(WORDPRESS_ARCHIVE_URL/)
255260
assert.match(corpus, /path\.endsWith\("\.map"\)/)

0 commit comments

Comments
 (0)