Make the origin's Cache-Control headers CDN-safe
An audit of the live site ahead of the Cloudflare move found three ways a cache-everything rule would have gone wrong. First, several routes named no policy at all — `/login`, `/issues.json`, `/files/epub/*`, `/files/xtc/*`, `/robots.txt`, the error pages — so the edge would have applied its own default TTL (Cloudflare: two hours on a 200). For `/files/*` that means an authenticated download becoming a publicly cached one. Those routes now say what they mean, and `security_headers` fails closed: a response that set no `Cache-Control` gets `no-store`, so a route added later cannot silently inherit the CDN's default. Everything cookie- or Basic-auth gated (`/files/*`, `/opds*`) is `private, no-store` on every response, 401s and 404s included. Second, the public pages said `max-age=300` alone. They now say `public, max-age=300, s-maxage=86400`: five minutes for the browser, a day for the edge, which is safe because publishing purges the edge. A request carrying a `daily_session=` cookie still gets `private, no-store`. Third, `/static/favicon.svg` and `/static/speculation.json` were referenced without `?v=` while being served `immutable` for a year — editing either one could never have reached a browser again. Both are now in the `ASSET_VERSION` hash and referenced with the version, and `static_asset` only promises a year when the URL actually carries `?v=`; a bare `/static/…` URL gets an hour and revalidates against the same ETag. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Va5eMEmWEnjMXBsBob5FDW
This commit is contained in:
+17
-1
@@ -1671,7 +1671,7 @@ mod tests {
|
||||
assert_eq!(issue.status(), StatusCode::OK);
|
||||
assert_eq!(
|
||||
issue.headers().get(header::CACHE_CONTROL).unwrap(),
|
||||
"public, max-age=300"
|
||||
"public, max-age=300, s-maxage=86400"
|
||||
);
|
||||
let html = String::from_utf8(
|
||||
to_bytes(issue.into_body(), 1024 * 1024)
|
||||
@@ -1703,6 +1703,10 @@ mod tests {
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(archive.status(), StatusCode::OK);
|
||||
assert_eq!(
|
||||
archive.headers().get(header::CACHE_CONTROL).unwrap(),
|
||||
"public, max-age=300, s-maxage=86400"
|
||||
);
|
||||
|
||||
let feed = app
|
||||
.clone()
|
||||
@@ -1718,6 +1722,10 @@ mod tests {
|
||||
feed.headers().get(header::CONTENT_TYPE).unwrap(),
|
||||
"application/atom+xml; charset=utf-8"
|
||||
);
|
||||
assert_eq!(
|
||||
feed.headers().get(header::CACHE_CONTROL).unwrap(),
|
||||
"public, max-age=300, s-maxage=86400"
|
||||
);
|
||||
let feed = String::from_utf8(
|
||||
to_bytes(feed.into_body(), 1024 * 1024)
|
||||
.await
|
||||
@@ -1750,6 +1758,10 @@ mod tests {
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
robots.headers().get(header::CACHE_CONTROL).unwrap(),
|
||||
"public, max-age=86400"
|
||||
);
|
||||
let robots =
|
||||
String::from_utf8(to_bytes(robots.into_body(), 4096).await.unwrap().to_vec()).unwrap();
|
||||
assert!(robots.contains("Disallow: /dashboard"));
|
||||
@@ -1763,6 +1775,10 @@ mod tests {
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
reports.headers().get(header::CACHE_CONTROL).unwrap(),
|
||||
"public, max-age=300, s-maxage=86400"
|
||||
);
|
||||
let reports =
|
||||
String::from_utf8(to_bytes(reports.into_body(), 4096).await.unwrap().to_vec()).unwrap();
|
||||
assert!(reports.contains("\"status\": \"ok\""));
|
||||
|
||||
Reference in New Issue
Block a user