fix(handleCacheHeaders): correct conditional-request precedence and Cache-Control default#1454
Conversation
…default `handleCacheHeaders` had three issues (#1442, #1453): - Forced `public` onto `Cache-Control` unconditionally, producing contradictory directives like `public, private` for authenticated responses. Now `public` is only the default when no explicit `cacheControls` are provided. - Evaluated `If-None-Match` and `If-Modified-Since` independently. Per RFC 9110 §13.1.3, `If-Modified-Since` must be ignored when `If-None-Match` is present; it is now only consulted when absent. - Matched ETags with strict string comparison only. Now uses RFC 9110 weak comparison (ignoring the `W/` indicator) and supports the `*` wildcard. Co-Authored-By: Claude Opus 4.8 <[email protected]>
📝 WalkthroughWalkthrough
ChangesCache validation behavior
Estimated code review effort: 3 (Moderate) | ~25 minutes Possibly related PRs
Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
…tching - Restore default `public` for shared caches, suppressing it only when the caller sets `private`/`no-store` (previously any explicit `cacheControls` dropped it, breaking CDN storage of authenticated responses). - Omit the shared-cache `s-maxage` directive for private/no-store responses. - Treat a present-but-empty `If-None-Match` as absent so `If-Modified-Since` revalidation still applies. - Extract `matchETag` into `internal/cache.ts` and reuse it in `serveStatic` so static serving gets weak-tag/list/`*` handling from one implementation. Co-Authored-By: Claude Opus 4.8 <[email protected]>
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/utils/static.ts`:
- Around line 174-175: Update the conditional-cache handling in the static
request logic around `ifNoneMatch` and the `If-Modified-Since` check so
`If-None-Match` is evaluated first whenever present, and only fall back to
`If-Modified-Since` when it is absent. Match the precedence behavior used by
`handleCacheHeaders`.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 8b79e3b4-7ef4-4c0a-89e9-851bb3102b4b
📒 Files selected for processing (5)
docs/2.utils/1.request.mdsrc/utils/cache.tssrc/utils/internal/cache.tssrc/utils/static.tstest/utils.test.ts
✅ Files skipped from review due to trivial changes (1)
- docs/2.utils/1.request.md
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/utils/static.ts`:
- Around line 171-176: Update the conditional-cache validation logic around
ifNoneMatch and matchETag to detect header presence rather than truthiness, so
an empty If-None-Match value does not fall back to If-Modified-Since. Resolve
the selected response ETag from options.headers as well as meta.etag, and pass
it to matchETag; ensure wildcard matching succeeds even when no metadata ETag
exists.
In `@test/static.test.ts`:
- Around line 325-332: Replace the invalid If-None-Match value "w/999" in the
test with the valid weak entity-tag `W/"999"`, preserving the test’s intended
nonmatching ETag behavior.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 98b0e32b-cc5f-4d50-a280-135e02d8d5bc
📒 Files selected for processing (5)
src/utils/cache.tssrc/utils/internal/cache.tssrc/utils/static.tstest/static.test.tstest/utils.test.ts
🚧 Files skipped from review as they are similar to previous changes (3)
- src/utils/internal/cache.ts
- src/utils/cache.ts
- test/utils.test.ts
Fixes #1442, fixes #1453.
handleCacheHeadershad three issues addressed here:1. Forced
publicCache-Control (#1442, #1453)publicwas unconditionally prepended, socacheControls: ["private"]produced the contradictorypublic, private— a CDN may treat that as publicly cacheable and expose personalized/authenticated responses. Nowpublicis only the default when no explicitcacheControlsare provided.2. Conditional-request precedence bug (#1453)
The old code evaluated
If-None-MatchandIf-Modified-Sinceindependently and OR'd the results. Per RFC 9110 §13.1.3, a recipient MUST ignoreIf-Modified-SincewhenIf-None-Matchis present. A resource modified twice within one second could otherwise wrongly return304.If-None-Matchnow takes strict precedence;If-Modified-Sinceis only consulted when absent.3. Weak ETag comparison + wildcard (#1453)
ETag matching used strict string comparison only. It now uses the RFC 9110 weak comparison function (ignoring the
W/indicator on either side) and supports the*wildcard.Tests
Added regression tests to
test/utils.test.ts:cacheControlsno longer forcepublicIf-Modified-Sinceignored whenIf-None-Matchis presentW/) and wildcard (*) ETag matchingExisting cache tests plus the new ones pass; lint and typecheck are clean.
Note
#1442 proposed a dedicated
cacheVisibilityoption; this follows #1453's simpler approach of skipping the implicitpublicwhen customcacheControlsare given — same outcome, backward-compatible, and keeps the API minimal.🤖 Generated with Claude Code
Summary by CodeRabbit
If-None-Matchnow overridesIf-Modified-Since.W/"...") andIf-None-Match: *handling.s-maxageonly for non-private responses.Last-Modified/ETag) and 304 behavior for static assets.