Skip to content

Commit

Permalink
feat: http caching (#3562)
Browse files Browse the repository at this point in the history
* feat: http caching

Implements bare-bones http caching as per rfc9111

Closes #3231
Closes #2760
Closes #2256
Closes #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]>

* fixup! feat: http caching

Signed-off-by: flakey5 <[email protected]>

* fixup! fixup! feat: http caching

Signed-off-by: flakey5 <[email protected]>

* fixup! fixup! fixup! feat: http caching

Signed-off-by: flakey5 <[email protected]>

* Update lib/handler/cache-handler.js

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

* Apply suggestions from code review

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

* fixup! fixup! fixup! fixup! feat: http caching

Signed-off-by: flakey5 <[email protected]>

* fixup! fixup! fixup! fixup! fixup! feat: http caching

Signed-off-by: flakey5 <[email protected]>

* clarify type for MemoryCacheStore

Signed-off-by: flakey5 <[email protected]>

* Apply suggestions from code review

Co-authored-by: Aras Abbasi <[email protected]>

* Apply suggestions from code review

Co-authored-by: Aras Abbasi <[email protected]>

* tmp

Signed-off-by: flakey5 <[email protected]>

* fixup! tmp

Signed-off-by: flakey5 <[email protected]>

* Apply suggestions from code review

Co-authored-by: Aras Abbasi <[email protected]>

* perf things, deleteByOrigin

Signed-off-by: flakey5 <[email protected]>

* incredibly messy and broken impl of streaming idea

Signed-off-by: flakey5 <[email protected]>

* fix tests

Signed-off-by: flakey5 <[email protected]>

* check if the response is already cached again

Signed-off-by: flakey5 <[email protected]>

* backpressure patch

Signed-off-by: flakey5 <[email protected]>

* move body out of CacheStoreValue, remove size property

Signed-off-by: flakey5 <[email protected]>

* Apply suggestions from code review

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

* add some comments on createWriteStream

Signed-off-by: flakey5 <[email protected]>

* fix type tests, make staleAt and deleteAt absolute

Signed-off-by: flakey5 <[email protected]>

* empty the body when overwriting the response

Signed-off-by: flakey5 <[email protected]>

* update onError calls

Signed-off-by: flakey5 <[email protected]>

* remove request deduplication for now

Signed-off-by: flakey5 <[email protected]>

* rename value -> opts, storedValue -> value

Signed-off-by: flakey5 <[email protected]>

* fix types

Signed-off-by: flakey5 <[email protected]>

* Apply suggestions from code review

Co-authored-by: Matteo Collina <[email protected]>

* simplify parsing for qualified no-cache and private

Signed-off-by: flakey5 <[email protected]>

* fix header omission, some cleanup

Signed-off-by: flakey5 <[email protected]>

* running the tests in ci is probably a good idea

Signed-off-by: flakey5 <[email protected]>

* fix some testing values

Signed-off-by: flakey5 <[email protected]>

* fixup! running the tests in ci is probably a good idea

Signed-off-by: flakey5 <[email protected]>

* Update lib/interceptor/cache.js

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

* Update lib/util/cache.js

Co-authored-by: Aras Abbasi <[email protected]>

* update from reviews

Signed-off-by: flakey5 <[email protected]>

* Update lib/interceptor/cache.js

Co-authored-by: Aras Abbasi <[email protected]>

* Apply suggestions from code review

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

* change from reviews

Signed-off-by: flakey5 <[email protected]>

* add promise support back for createReadStream

Signed-off-by: flakey5 <[email protected]>

* Apply suggestions from code review

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

* check if onError was called

Signed-off-by: flakey5 <[email protected]>

* add docs

Signed-off-by: flakey5 <[email protected]>

* add errorCallback

Signed-off-by: flakey5 <[email protected]>

* Update types/cache-interceptor.d.ts

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

* use fake timers and cleanup client

Signed-off-by: flakey5 <[email protected]>

* lazy cache wellknown headers

Signed-off-by: flakey5 <[email protected]>

* Apply suggestions from code review

Co-authored-by: Aras Abbasi <[email protected]>

* Update lib/cache/memory-cache-store.js

Co-authored-by: Aras Abbasi <[email protected]>

* code review

Signed-off-by: flakey5 <[email protected]>

* Apply suggestions from code review

Co-authored-by: Aras Abbasi <[email protected]>

* Apply suggestions from code review

Co-authored-by: Aras Abbasi <[email protected]>

* code review pt2

Signed-off-by: flakey5 <[email protected]>

* Update lib/handler/cache-revalidation-handler.js

Co-authored-by: Aras Abbasi <[email protected]>

* Update lib/handler/cache-handler.js

Co-authored-by: Aras Abbasi <[email protected]>

* Apply suggestions from code review

* fix

---------

Signed-off-by: flakey5 <[email protected]>
Co-authored-by: Robert Nagy <[email protected]>
Co-authored-by: Carlos Fuentes <[email protected]>
Co-authored-by: Aras Abbasi <[email protected]>
Co-authored-by: Matteo Collina <[email protected]>
  • Loading branch information
5 people authored Oct 14, 2024
1 parent e8c3aba commit 2d40ade
Show file tree
Hide file tree
Showing 20 changed files with 2,597 additions and 4 deletions.
116 changes: 116 additions & 0 deletions docs/docs/api/CacheStore.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
# Cache Store

A Cache Store is responsible for storing and retrieving cached responses.
It is also responsible for deciding which specific response to use based off of
a response's `Vary` header (if present).

## Pre-built Cache Stores

### `MemoryCacheStore`

The `MemoryCacheStore` stores the responses in-memory.

**Options**

- `maxEntries` - The maximum amount of responses to store. Default `Infinity`.
- `maxEntrySize` - The maximum size in bytes that a response's body can be. If a response's body is greater than or equal to this, the response will not be cached.

## Defining a Custom Cache Store

The store must implement the following functions:

### Getter: `isFull`

This tells the cache interceptor if the store is full or not. If this is true,
the cache interceptor will not attempt to cache the response.

### Function: `createReadStream`

Parameters:

* **req** `Dispatcher.RequestOptions` - Incoming request

Returns: `CacheStoreReadable | Promise<CacheStoreReadable | undefined> | undefined` - If the request is cached, a readable for the body is returned. Otherwise, `undefined` is returned.

### Function: `createWriteStream`

Parameters:

* **req** `Dispatcher.RequestOptions` - Incoming request
* **value** `CacheStoreValue` - Response to store

Returns: `CacheStoreWriteable | undefined` - If the store is full, return `undefined`. Otherwise, return a writable so that the cache interceptor can stream the body and trailers to the store.

## `CacheStoreValue`

This is an interface containing the majority of a response's data (minus the body).

### Property `statusCode`

`number` - The response's HTTP status code.

### Property `statusMessage`

`string` - The response's HTTP status message.

### Property `rawHeaders`

`(Buffer | Buffer[])[]` - The response's headers.

### Property `rawTrailers`

`string[] | undefined` - The response's trailers.

### Property `vary`

`Record<string, string> | undefined` - The headers defined by the response's `Vary` header
and their respective values for later comparison

For example, for a response like
```
Vary: content-encoding, accepts
content-encoding: utf8
accepts: application/json
```

This would be
```js
{
'content-encoding': 'utf8',
accepts: 'application/json'
}
```

### Property `cachedAt`

`number` - Time in millis that this value was cached.

### Property `staleAt`

`number` - Time in millis that this value is considered stale.

### Property `deleteAt`

`number` - Time in millis that this value is to be deleted from the cache. This
is either the same sa staleAt or the `max-stale` caching directive.

The store must not return a response after the time defined in this property.

## `CacheStoreReadable`

This extends Node's [`Readable`](https://nodejs.org/api/stream.html#class-streamreadable)
and defines extra properties relevant to the cache interceptor.

### Getter: `value`

The response's [`CacheStoreValue`](#cachestorevalue)

## `CacheStoreWriteable`

This extends Node's [`Writable`](https://nodejs.org/api/stream.html#class-streamwritable)
and defines extra properties relevant to the cache interceptor.

### Setter: `rawTrailers`

If the response has trailers, the cache interceptor will pass them to the cache
interceptor through this method.
10 changes: 10 additions & 0 deletions docs/docs/api/Dispatcher.md
Original file line number Diff line number Diff line change
Expand Up @@ -1233,6 +1233,16 @@ test('should not error if request status code is not in the specified error code
The Response Error Interceptor provides a robust mechanism for handling HTTP response errors by capturing detailed error information and propagating it through a structured `ResponseError` class. This enhancement improves error handling and debugging capabilities in applications using the interceptor.
##### `Cache Interceptor`
The `cache` interceptor implements client-side response caching as described in
[RFC9111](https://www.rfc-editor.org/rfc/rfc9111.html).
**Options**
- `store` - The [`CacheStore`](./CacheStore.md) to store and retrieve responses from. Default is [`MemoryCacheStore`](./CacheStore.md#memorycachestore).
- `methods` - The [**safe** HTTP methods](https://www.rfc-editor.org/rfc/rfc9110#section-9.2.1) to cache the response of.
## Instance Events
### Event: `'connect'`
Expand Down
7 changes: 6 additions & 1 deletion index.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,12 @@ module.exports.interceptors = {
redirect: require('./lib/interceptor/redirect'),
retry: require('./lib/interceptor/retry'),
dump: require('./lib/interceptor/dump'),
dns: require('./lib/interceptor/dns')
dns: require('./lib/interceptor/dns'),
cache: require('./lib/interceptor/cache')
}

module.exports.cacheStores = {
MemoryCacheStore: require('./lib/cache/memory-cache-store')
}

module.exports.buildConnector = buildConnector
Expand Down
Loading

0 comments on commit 2d40ade

Please sign in to comment.