Skip to content
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

feat: http caching #3562

Open
wants to merge 34 commits into
base: main
Choose a base branch
from
Open

feat: http caching #3562

wants to merge 34 commits into from

Conversation

flakey5
Copy link
Member

@flakey5 flakey5 commented Sep 7, 2024

Implements bare-bones opt-in http caching as per rfc9111. Bare-bones in this case means what's required by the spec and a few extra bits, with more coming in future prs.

Opening as a draft since there's still some more work to be done (mostly tests, but a bit more functionality-wise)

No request cache directives are supported at this time, this will come later.

Response caching directives supported:

  • public
  • private
  • s-maxage
  • max-age
  • Expires header
  • no-cache
  • no-store
  • stale-while-revalidate

This relates to...

Closes #3231
Closes #2760
Closes #2256
Closes #1146

Changes

Features

  • Opt-in http caching
  • In-memory default cache store

Bug Fixes

n/a

Breaking Changes and Deprecations

n/a

Status

lib/cache/lru-cache-store.js Outdated Show resolved Hide resolved
lib/cache/lru-cache-store.js Outdated Show resolved Hide resolved
lib/cache/lru-cache-store.js Outdated Show resolved Hide resolved
lib/handler/cache-handler.js Outdated Show resolved Hide resolved
lib/handler/cache-handler.js Outdated Show resolved Hide resolved
lib/handler/cache-handler.js Outdated Show resolved Hide resolved
@ronag
Copy link
Member

ronag commented Sep 7, 2024

Would it maybe be better to use jsdoc types rather than separate type definition files? @mcollina

index.js Outdated Show resolved Hide resolved
lib/cache/lru-cache-store.js Outdated Show resolved Hide resolved
lib/cache/lru-cache-store.js Outdated Show resolved Hide resolved
lib/cache/lru-cache-store.js Outdated Show resolved Hide resolved
lib/cache/lru-cache-store.js Outdated Show resolved Hide resolved
lib/handler/cache-handler.js Outdated Show resolved Hide resolved
lib/handler/cache-handler.js Outdated Show resolved Hide resolved
lib/handler/cache-handler.js Show resolved Hide resolved
lib/interceptor/cache.js Outdated Show resolved Hide resolved
lib/interceptor/cache.js Outdated Show resolved Hide resolved
@mcollina
Copy link
Member

mcollina commented Sep 9, 2024

Would it maybe be better to use jsdoc types rather than separate type definition files? @mcollina

I think using type definitions are in line with the rest of the codebase, and therefore the correct implementation.

If we want to migrate to a jsdoc-generated world, let's discuss in another issue!

lib/handler/cache-handler.js Outdated Show resolved Hide resolved
lib/cache/lru-cache-store.js Outdated Show resolved Hide resolved
lib/handler/cache-handler.js Outdated Show resolved Hide resolved
lib/handler/cache-handler.js Show resolved Hide resolved
lib/handler/cache-handler.js Outdated Show resolved Hide resolved
@ronag
Copy link
Member

ronag commented Sep 11, 2024

@flakey5 please make sure to add me and @IsakT as co-authors (assuming you took some of our code in nxt-undici).

lib/cache/lru-cache-store.js Outdated Show resolved Hide resolved
lib/handler/cache-handler.js Outdated Show resolved Hide resolved
@flakey5 flakey5 force-pushed the flakey5/3231 branch 2 times, most recently from e07e3ec to 938fa7a Compare September 12, 2024 03:58
Implements bare-bones http caching as per rfc9111

Closes nodejs#3231
Closes nodejs#2760
Closes nodejs#2256
Closes nodejs#1146

Co-authored-by: Carlos Fuentes <[email protected]>

Co-authored-by: Robert Nagy <[email protected]>

Co-authored-by: Isak Törnros <[email protected]>

Signed-off-by: flakey5 <[email protected]>
@flakey5 flakey5 marked this pull request as ready for review September 14, 2024 05:29
types/interceptors.d.ts Show resolved Hide resolved
test/cache-interceptor/cache-stores.js Outdated Show resolved Hide resolved
test/cache-interceptor/interceptor.js Outdated Show resolved Hide resolved
lib/cache/memory-cache-store.js Show resolved Hide resolved
lib/handler/cache-handler.js Outdated Show resolved Hide resolved
lib/cache/memory-cache-store.js Outdated Show resolved Hide resolved
lib/cache/memory-cache-store.js Outdated Show resolved Hide resolved
lib/handler/cache-handler.js Outdated Show resolved Hide resolved
lib/handler/cache-handler.js Outdated Show resolved Hide resolved
lib/handler/cache-revalidation-handler.js Outdated Show resolved Hide resolved
lib/interceptor/cache.js Outdated Show resolved Hide resolved
test/cache-interceptor/interceptor.js Show resolved Hide resolved
lib/cache/memory-cache-store.js Outdated Show resolved Hide resolved
lib/cache/memory-cache-store.js Outdated Show resolved Hide resolved
Copy link
Contributor

@Uzlopak Uzlopak left a comment

Choose a reason for hiding this comment

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

headers could be null or undefined

lib/cache/memory-cache-store.js Outdated Show resolved Hide resolved
Copy link
Contributor

@Uzlopak Uzlopak left a comment

Choose a reason for hiding this comment

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

@ronag
Should we use fastTimers?

lib/cache/memory-cache-store.js Show resolved Hide resolved
lib/handler/cache-handler.js Outdated Show resolved Hide resolved
@flakey5
Copy link
Member Author

flakey5 commented Sep 24, 2024

Hmm so with sharing an in-flight response to requests to the same resource, how do we want to handle if the body ends up exceeding the maxEntrySize?

Currently when this happens, we close the writable. However, the readables waiting for body chunks are just left hanging.

I think we could either

  1. emit an error in the readable that makes it way to handler.onError, aborting the request
  2. let the requests hanging on this just pass through to the origin

The first would be the easiest to implement but the second is what I intuitively think would happen. The second option comes with some implementation difficulties though.

When we create the read stream in the interceptor, we would need to listen for some passToOrigin event, this part is simple enough. The issue is we would also need to not send the body until the response successfully finishes. This doesn't work great with createReadStream returning a Readable, since we need to store the data for each request separately until the response finishes.

The implementation would look something like

const dataChunks = []
stream.on('data', dataChunks.push)

let passedToOrigin = false
stream.on('passToOrigin', () => {
  passedToOrigin = true
  dispatch(opts, handler)
})

stream.on('end', () => {
  if (!passedToOrigin) {
    // actually send the body chunks
  }
})

Adding to it, this doesn't apply to HEAD requests since there isn't a response body for those.

Something that might work is allowing createReadStream to return just a generic EventEmitter that would give us the body chunks so we're not keeping a separate copy for each request, i.e.

stream.on('end', chunks => {/* ... */})
stream.on('passedToOrigin', () => {/* dispatch */})

If we want to, we could go with option 1 for simplicity sakes and then later down the road add support for option 2. Wdyt?

@ronag
Copy link
Member

ronag commented Sep 24, 2024

TBH I think we should skip in-flight de-duplication in this first phase. @mcollina wdyt?

I would propose:

Step 1: in-memory cache
Step 2: sqlite cache
Step 3: in-flight de-deduplication

lib/handler/cache-handler.js Outdated Show resolved Hide resolved
lib/handler/cache-handler.js Outdated Show resolved Hide resolved
lib/interceptor/cache.js Show resolved Hide resolved
@mcollina
Copy link
Member

TBH I think we should skip in-flight de-duplication in this first phase. @mcollina wdyt?

I think this might be handled here already. Why do you want to remove it?

@flakey5
Copy link
Member Author

flakey5 commented Sep 24, 2024

I think this might be handled here already. Why do you want to remove it?

Re #3562 (comment)

Signed-off-by: flakey5 <[email protected]>
@flakey5
Copy link
Member Author

flakey5 commented Sep 24, 2024

I would propose:

Step 1: in-memory cache Step 2: sqlite cache Step 3: in-flight de-deduplication

To add to this, I'll also be opening another pr that adds support for cache control directives specified by the request (wip branch https://github.com/flakey5/undici/tree/flakey5/20240924/cli-cache-control)

@flakey5
Copy link
Member Author

flakey5 commented Sep 25, 2024

Pushed a commit removing the request de-dupe code re #3562 (comment) and #3562 (comment). Can revert it if we do want to have it, but, I agree that it should be added afterwards in a pr dedicated to it. That way we can address the issues there without tying the rest of this up.

Signed-off-by: flakey5 <[email protected]>
lib/util/cache.js Show resolved Hide resolved
lib/util/cache.js Outdated Show resolved Hide resolved
test/cache-interceptor/cache-stores.js Show resolved Hide resolved
Copy link
Member

@mcollina mcollina left a comment

Choose a reason for hiding this comment

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

lgtm

Copy link
Member

@ronag ronag left a comment

Choose a reason for hiding this comment

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

I have opinions... Will try to make time over weeekend.

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.

Implement HTTP caching fetch: caching Caching Question: Does undici.fetch support RFC 7234?
5 participants