Skip to content

Commit 2d40ade

Browse files
flakey5ronagmetcoder95Uzlopakmcollina
authored
feat: http caching (#3562)
* 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]>
1 parent e8c3aba commit 2d40ade

20 files changed

+2597
-4
lines changed

docs/docs/api/CacheStore.md

+116
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
# Cache Store
2+
3+
A Cache Store is responsible for storing and retrieving cached responses.
4+
It is also responsible for deciding which specific response to use based off of
5+
a response's `Vary` header (if present).
6+
7+
## Pre-built Cache Stores
8+
9+
### `MemoryCacheStore`
10+
11+
The `MemoryCacheStore` stores the responses in-memory.
12+
13+
**Options**
14+
15+
- `maxEntries` - The maximum amount of responses to store. Default `Infinity`.
16+
- `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.
17+
18+
## Defining a Custom Cache Store
19+
20+
The store must implement the following functions:
21+
22+
### Getter: `isFull`
23+
24+
This tells the cache interceptor if the store is full or not. If this is true,
25+
the cache interceptor will not attempt to cache the response.
26+
27+
### Function: `createReadStream`
28+
29+
Parameters:
30+
31+
* **req** `Dispatcher.RequestOptions` - Incoming request
32+
33+
Returns: `CacheStoreReadable | Promise<CacheStoreReadable | undefined> | undefined` - If the request is cached, a readable for the body is returned. Otherwise, `undefined` is returned.
34+
35+
### Function: `createWriteStream`
36+
37+
Parameters:
38+
39+
* **req** `Dispatcher.RequestOptions` - Incoming request
40+
* **value** `CacheStoreValue` - Response to store
41+
42+
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.
43+
44+
## `CacheStoreValue`
45+
46+
This is an interface containing the majority of a response's data (minus the body).
47+
48+
### Property `statusCode`
49+
50+
`number` - The response's HTTP status code.
51+
52+
### Property `statusMessage`
53+
54+
`string` - The response's HTTP status message.
55+
56+
### Property `rawHeaders`
57+
58+
`(Buffer | Buffer[])[]` - The response's headers.
59+
60+
### Property `rawTrailers`
61+
62+
`string[] | undefined` - The response's trailers.
63+
64+
### Property `vary`
65+
66+
`Record<string, string> | undefined` - The headers defined by the response's `Vary` header
67+
and their respective values for later comparison
68+
69+
For example, for a response like
70+
```
71+
Vary: content-encoding, accepts
72+
content-encoding: utf8
73+
accepts: application/json
74+
```
75+
76+
This would be
77+
```js
78+
{
79+
'content-encoding': 'utf8',
80+
accepts: 'application/json'
81+
}
82+
```
83+
84+
### Property `cachedAt`
85+
86+
`number` - Time in millis that this value was cached.
87+
88+
### Property `staleAt`
89+
90+
`number` - Time in millis that this value is considered stale.
91+
92+
### Property `deleteAt`
93+
94+
`number` - Time in millis that this value is to be deleted from the cache. This
95+
is either the same sa staleAt or the `max-stale` caching directive.
96+
97+
The store must not return a response after the time defined in this property.
98+
99+
## `CacheStoreReadable`
100+
101+
This extends Node's [`Readable`](https://nodejs.org/api/stream.html#class-streamreadable)
102+
and defines extra properties relevant to the cache interceptor.
103+
104+
### Getter: `value`
105+
106+
The response's [`CacheStoreValue`](#cachestorevalue)
107+
108+
## `CacheStoreWriteable`
109+
110+
This extends Node's [`Writable`](https://nodejs.org/api/stream.html#class-streamwritable)
111+
and defines extra properties relevant to the cache interceptor.
112+
113+
### Setter: `rawTrailers`
114+
115+
If the response has trailers, the cache interceptor will pass them to the cache
116+
interceptor through this method.

docs/docs/api/Dispatcher.md

+10
Original file line numberDiff line numberDiff line change
@@ -1233,6 +1233,16 @@ test('should not error if request status code is not in the specified error code
12331233
12341234
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.
12351235
1236+
##### `Cache Interceptor`
1237+
1238+
The `cache` interceptor implements client-side response caching as described in
1239+
[RFC9111](https://www.rfc-editor.org/rfc/rfc9111.html).
1240+
1241+
**Options**
1242+
1243+
- `store` - The [`CacheStore`](./CacheStore.md) to store and retrieve responses from. Default is [`MemoryCacheStore`](./CacheStore.md#memorycachestore).
1244+
- `methods` - The [**safe** HTTP methods](https://www.rfc-editor.org/rfc/rfc9110#section-9.2.1) to cache the response of.
1245+
12361246
## Instance Events
12371247
12381248
### Event: `'connect'`

index.js

+6-1
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,12 @@ module.exports.interceptors = {
4040
redirect: require('./lib/interceptor/redirect'),
4141
retry: require('./lib/interceptor/retry'),
4242
dump: require('./lib/interceptor/dump'),
43-
dns: require('./lib/interceptor/dns')
43+
dns: require('./lib/interceptor/dns'),
44+
cache: require('./lib/interceptor/cache')
45+
}
46+
47+
module.exports.cacheStores = {
48+
MemoryCacheStore: require('./lib/cache/memory-cache-store')
4449
}
4550

4651
module.exports.buildConnector = buildConnector

0 commit comments

Comments
 (0)