Skip to content

cache: allow caching heuristically cacheable error status codes #4318

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jul 4, 2025

Conversation

mcollina
Copy link
Member

@mcollina mcollina commented Jul 3, 2025

Summary

Enable caching for error responses with heuristically cacheable status codes (204, 404, 410, etc.) when they include explicit cache directives, following RFC 9111 HTTP caching standards.

Changes

  • Modified canCacheResponse function in lib/handler/cache-handler.js to allow caching responses with status codes from HEURISTICALLY_CACHEABLE_STATUS_CODES array when they have explicit cache directives
  • Added comprehensive tests for caching 404, 204, and 410 responses with cache headers
  • Added test to verify non-heuristically cacheable status codes (418) are not cached

Background

Previously, only 200 and 307 responses could be cached regardless of cache headers. This was overly restrictive compared to HTTP caching standards, which allow many error status codes to be cached when they have appropriate cache directives.

The existing HEURISTICALLY_CACHEABLE_STATUS_CODES array already contained the appropriate status codes (200, 203, 204, 206, 300, 301, 308, 404, 405, 410, 414, 501) but they weren't being used in the main caching decision logic.

Test plan

  • All existing cache tests pass
  • New tests verify 404 responses with Cache-Control: public, max-age=60 are cached
  • New tests verify 204 and 410 responses with cache headers are cached
  • New test verifies non-heuristically cacheable status codes (418) are not cached
  • Linting passes

Fixes #4293

🤖 Generated with Claude Code

Enable caching for error responses with heuristically cacheable status codes
(204, 404, 410, etc.) when they include explicit cache directives, following
RFC 9111 HTTP caching standards.

Previously, only 200 and 307 responses could be cached regardless of cache
headers. Now responses with status codes in HEURISTICALLY_CACHEABLE_STATUS_CODES
can be cached when they have Cache-Control, Expires, or other caching directives.

Fixes: #4293

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Matteo Collina <hello@matteocollina.com>
Copy link
Collaborator

@Ethan-Arrowood Ethan-Arrowood left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd like to see the 3 cache test cases reduced to re-use the logic since its basically the same exact assertions aside from the specific code/message. Maybe just an array of the codes we want to test, and then iterate through with the same test case.

But I wont block on that. LGTM 😄

Refactor three identical test cases for status codes 204, 404, and 410
into a single parameterized test to reduce code duplication and improve
maintainability.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
@mcollina mcollina merged commit c6a8b23 into main Jul 4, 2025
51 of 52 checks passed
@fredericDelaporte
Copy link
Contributor

fredericDelaporte commented Jul 4, 2025

Sorry to be late on this, but just to let you know if that matters to you:

allow caching responses with status codes from HEURISTICALLY_CACHEABLE_STATUS_CODES array when they have explicit cache directives

Is still more restrictive than the RFC.

Storing Responses in Caches
A cache MUST NOT store a response to a request unless:
...

  • the response status code is final (see Section 15 of [HTTP]);
  • if the response status code is 206 or 304, or the must-understand cache directive (see Section 5.2.2.3) is present: the cache understands the response status code;
    ...
  • the response contains at least one of the following:

The status code being heuristically cacheable is not required for caching, unless none of the previous alternatives are met. Which means, a response containing a cache directive may be cached event if its status code is not heuristically cacheable.

Other paragraphs add further restrictions but also clarify this.

5.2.2.9. public
The public response directive indicates that a cache MAY store the response even if it would otherwise be prohibited, subject to the constraints defined in Section 3. In other words, public explicitly marks the response as cacheable. For example, public permits a shared cache to reuse a response to a request containing an Authorization header field (Section 3.5).

Note that it is unnecessary to add the public directive to a response that is already cacheable according to Section 3.

If a response with the public directive has no explicit freshness information, it is heuristically cacheable (Section 4.2.2).

According to the last sentence the heuristically cacheable test has to be done in the case no expiry is provided in the cache directive. Nothing mandates the response to be heuristically cacheable for explicitly cacheable responses (with expiry for the shared cache case, the private one does not have that constraint of having an explicit expiry).

For example it means 302 and 307 are cacheable too, if there is an explicit caching directive.

But implementing the RFC in order to cache in all circumstances allowed by the RFC looks like it would requires a convoluted logic, which seems undesirable to me. Especially implementing heuristic caching (caching of responses without explicit caching directive) looks cumbersome.

Anyway, your implementation is compliant and aligned with the RFC, just not as permissive as the RFC. It does not mandate caching, but states instead "may cache".

So, why not leaving it as it is currently.

Edit: I think I have miss-understood the sentence:

If a response with the public directive has no explicit freshness information, it is heuristically cacheable (Section 4.2.2).

I have read it as meaning it should have an heuristically cacheable status code. But I now think it just means an heuristic cache duration can be applied to cache response with public directive but no expiry.

Comment on lines +245 to +247
// Also allow caching for other status codes that are heuristically cacheable
// when they have explicit cache directives
if (statusCode !== 200 && statusCode !== 307 && !HEURISTICALLY_CACHEABLE_STATUS_CODES.includes(statusCode)) {
Copy link
Contributor

@fredericDelaporte fredericDelaporte Jul 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment seems to imply an intent to cache heuristically cacheable status only if an explicit caching directive is provided, but I have not seen any code checking there is an explicit caching directive. The following code only checks there is not a caching directive forbidding to cache.

So, here, by still allowing 307 caching without checking there is actually an explicit caching directive, undue caching of some cases of 307 responses may happen (307 not being heuristically cacheable, but being cacheable if a directive tells to cache it).

It looks to me the test here should be changed to only check the status is final, and if not, shortcircuit returning false.

Then test if either the status code is not heuristically cacheable and there is neither a public nor a private directive, and in such case, return false.

All that is a bit nit-picking, I do not think it matters much due to theses cases being infrequent, but I prefer let you know in case you think otherwise.

@mcollina
Copy link
Member Author

mcollina commented Jul 4, 2025

@fredericDelaporte further PRs that improve things are always very welcome! If you want to open a tracking issue.. go ahead!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Cache cacheable response with an error status code
4 participants