-
Notifications
You must be signed in to change notification settings - Fork 5
Cache HTTP responses based on RFC 9111 cacheable status codes #407
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -443,6 +443,41 @@ describe('test REST calls', () => { | |
| req.end(body); | ||
| }); | ||
| }); | ||
| describe('HTTP response status code caching', function () { | ||
| it('caches a cacheable 404 response and returns it with 404 status', async () => { | ||
| const response = await axios.get('http://localhost:9926/CacheOfHttp/not-found', { | ||
| validateStatus: () => true, | ||
| }); | ||
| assert.equal(response.status, 404); | ||
| // second request should also return 404 (served from cache) | ||
| const response2 = await axios.get('http://localhost:9926/CacheOfHttp/not-found', { | ||
| validateStatus: () => true, | ||
| }); | ||
| assert.equal(response2.status, 404); | ||
| assert(!response2.headers['server-timing'].includes('miss')); | ||
| }); | ||
| it('does not cache a non-cacheable 500 response', async () => { | ||
| const callsBefore = tables.CacheOfHttp.serverErrorCalls; | ||
| const response = await axios.get('http://localhost:9926/CacheOfHttp/server-error', { | ||
| validateStatus: () => true, | ||
| }); | ||
| assert.equal(response.status, 500); | ||
| // second request should also hit source (not cached) | ||
| const response2 = await axios.get('http://localhost:9926/CacheOfHttp/server-error', { | ||
| validateStatus: () => true, | ||
| }); | ||
| assert.equal(response2.status, 500); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The test claims to verify non-caching but only asserts the status code, which would be 500 whether the response was cached or not. A previous version of this test (commit The it('does not cache a non-cacheable 500 response', async () => {
let callCount = 0;
const origSource = tables.CacheOfHttp._source; // or however the source is accessible
// wrap / intercept to count calls, then restore in after()
const response = await axios.get('http://localhost:9926/CacheOfHttp/server-error', { validateStatus: () => true });
assert.equal(response.status, 500);
const response2 = await axios.get('http://localhost:9926/CacheOfHttp/server-error', { validateStatus: () => true });
assert.equal(response2.status, 500);
assert.equal(callCount, 2, 'source should be called for every request — 500 must not be cached');
});Without a check like this, a regression that cached 500 responses would silently pass this test. |
||
| assert.equal(tables.CacheOfHttp.serverErrorCalls, callsBefore + 2); | ||
| }); | ||
| it('does not store status in cached record for 200 responses', async () => { | ||
| // The CacheOfHttp 'created-response' source returns a 200 with a custom header | ||
| // If status 200 were stored, it would appear as a field in the raw record | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: The test name and comment claim to verify that |
||
| // We verify that the 200 response is served correctly without redundantly caching status | ||
| const response = await axios.get('http://localhost:9926/CacheOfHttp/created-response'); | ||
| assert.equal(response.status, 200); | ||
| assert.equal(response.headers.get('x-custom-header'), 'custom value'); | ||
| }); | ||
| }); | ||
| it('post with custom response', async () => { | ||
| const response = await axios.post('http://localhost:9926/SimpleCache/35555', { | ||
| customResponse: true, | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The set correctly mirrors the RFC 9111 §4.2.2 heuristically-cacheable list, but this is a silent breaking change for source functions that currently return Response objects with 2xx codes not in the set — specifically 201, 202, 205, 207, 208, 226.
Before this PR, every
status < 300fell through to the caching branch. After it, any 2xx code absent fromCACHEABLE_STATUS_CODES(201,202,205,207, …) hits the!CACHEABLE_STATUS_CODES.has(status)branch and throwsServerError. A source returning201 Createdwould now propagate an error to the client instead of caching the response.RFC 9111 is the right authority here, but the change is undocumented in the PR body and could break integrators. Worth a note in the PR description (or a
CHANGELOGentry) so it's visible in release notes.