| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -66,10 +66,12 @@ Parameters: | |||
| 66 | 66 | Returns: `GetResult | Promise<GetResult | undefined> | undefined` - If the request is cached, the cached response is returned. If the request's method is anything other than HEAD, the response is also returned. | |
| 67 | 67 | If the request isn't cached, `undefined` is returned. | |
| 68 | 68 | ||
| 69 | + The `get` method may return a `Promise` for async cache stores (e.g. Redis-backed or remote stores). The cache interceptor handles both synchronous and asynchronous return values, including in revalidation paths (304 Not Modified handling and stale-while-revalidate background revalidation). | ||
| 70 | + | ||
| 69 | 71 | Response properties: | |
| 70 | 72 | ||
| 71 | 73 | * **response** `CacheValue` - The cached response data. | |
| 72 | - * **body** `Readable | undefined` - The response's body. | ||
| 74 | + * **body** `Readable | Iterable<Buffer> | undefined` - The response's body. This can be an array of `Buffer` chunks (with a `.values()` method) or a `Readable` stream. Both formats are supported in all code paths, including 304 revalidation. | ||
| 73 | 75 | ||
| 74 | 76 | ### Function: `createWriteStream` | |
| 75 | 77 | ||
@@ -98,8 +100,11 @@ This is an interface containing the majority of a response's data (minus the bod | |||
| 98 | 100 | ||
| 99 | 101 | ### Property `vary` | |
| 100 | 102 | ||
| 101 | - `Record<string, string | string[]> | undefined` - The headers defined by the response's `Vary` header | ||
| 102 | - and their respective values for later comparison | ||
| 103 | + `Record<string, string | string[] | null> | undefined` - The headers defined by the response's `Vary` header | ||
| 104 | + and their respective values for later comparison. Values are `null` when the | ||
| 105 | + header specified in `Vary` was not present in the original request. These `null` | ||
| 106 | + values are automatically filtered out during revalidation so they are not sent | ||
| 107 | + as request headers. | ||
| 103 | 108 | ||
| 104 | 109 | For example, for a response like | |
| 105 | 110 | ``` | |
@@ -116,6 +121,14 @@ This would be | |||
| 116 | 121 | } | |
| 117 | 122 | ``` | |
| 118 | 123 | ||
| 124 | + If the original request did not include the `accepts` header: | ||
| 125 | + ```js | ||
| 126 | + { | ||
| 127 | + 'content-encoding': 'utf8', | ||
| 128 | + accepts: null | ||
| 129 | + } | ||
| 130 | + ``` | ||
| 131 | + | ||
| 119 | 132 | ### Property `cachedAt` | |
| 120 | 133 | ||
| 121 | 134 | `number` - Time in millis that this value was cached. | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -15,7 +15,7 @@ Arguments: | |||
| 15 | 15 | ||
| 16 | 16 | This example will not work in browsers or other platforms that don't allow passing an object. | |
| 17 | 17 | ||
| 18 | - ```mjs | ||
| 18 | + ```js | ||
| 19 | 19 | import { WebSocket, ProxyAgent } from 'undici' | |
| 20 | 20 | ||
| 21 | 21 | const proxyAgent = new ProxyAgent('my.proxy.server') | |
@@ -28,7 +28,7 @@ const ws = new WebSocket('wss://echo.websocket.events', { | |||
| 28 | 28 | ||
| 29 | 29 | If you do not need a custom Dispatcher, it's recommended to use the following pattern: | |
| 30 | 30 | ||
| 31 | - ```mjs | ||
| 31 | + ```js | ||
| 32 | 32 | import { WebSocket } from 'undici' | |
| 33 | 33 | ||
| 34 | 34 | const ws = new WebSocket('wss://echo.websocket.events', ['echo', 'chat']) | |
@@ -44,7 +44,7 @@ const ws = new WebSocket('wss://echo.websocket.events', ['echo', 'chat']) | |||
| 44 | 44 | ||
| 45 | 45 | This example will not work in browsers or other platforms that don't allow passing an object. | |
| 46 | 46 | ||
| 47 | - ```mjs | ||
| 47 | + ```js | ||
| 48 | 48 | import { Agent } from 'undici' | |
| 49 | 49 | ||
| 50 | 50 | const agent = new Agent({ allowH2: true }) | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -95,16 +95,14 @@ class EnvHttpProxyAgent extends DispatcherBase { | |||
| 95 | 95 | if (entry.port && entry.port !== port) { | |
| 96 | 96 | continue // Skip if ports don't match. | |
| 97 | 97 | } | |
| 98 | - if (!/^[.*]/.test(entry.hostname)) { | ||
| 99 | - // No wildcards, so don't proxy only if there is not an exact match. | ||
| 100 | - if (hostname === entry.hostname) { | ||
| 101 | - return false | ||
| 102 | - } | ||
| 103 | - } else { | ||
| 104 | - // Don't proxy if the hostname ends with the no_proxy host. | ||
| 105 | - if (hostname.endsWith(entry.hostname.replace(/^\*/, ''))) { | ||
| 106 | - return false | ||
| 107 | - } | ||
| 98 | + // Don't proxy if the hostname is equal with the no_proxy host. | ||
| 99 | + if (hostname === entry.hostname) { | ||
| 100 | + return false | ||
| 101 | + } | ||
| 102 | + // Don't proxy if the hostname is the subdomain of the no_proxy host. | ||
| 103 | + // Reference - https://github.com/denoland/deno/blob/6fbce91e40cc07fc6da74068e5cc56fdd40f7b4c/ext/fetch/proxy.rs#L485 | ||
| 104 | + if (hostname.slice(-(entry.hostname.length + 1)) === `.${entry.hostname}`) { | ||
| 105 | + return false | ||
| 108 | 106 | } | |
| 109 | 107 | } | |
| 110 | 108 | ||
@@ -123,7 +121,8 @@ class EnvHttpProxyAgent extends DispatcherBase { | |||
| 123 | 121 | } | |
| 124 | 122 | const parsed = entry.match(/^(.+):(\d+)$/) | |
| 125 | 123 | noProxyEntries.push({ | |
| 126 | - hostname: (parsed ? parsed[1] : entry).toLowerCase(), | ||
| 124 | + // strip leading dot or asterisk with dot | ||
| 125 | + hostname: (parsed ? parsed[1] : entry).replace(/^\*?\./, '').toLowerCase(), | ||
| 127 | 126 | port: parsed ? Number.parseInt(parsed[2], 10) : 0 | |
| 128 | 127 | }) | |
| 129 | 128 | } | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -193,57 +193,92 @@ class CacheHandler { | |||
| 193 | 193 | // Not modified, re-use the cached value | |
| 194 | 194 | // https://www.rfc-editor.org/rfc/rfc9111.html#name-handling-304-not-modified | |
| 195 | 195 | if (statusCode === 304) { | |
| 196 | - /** | ||
| 197 | - * @type {import('../../types/cache-interceptor.d.ts').default.CacheValue} | ||
| 198 | - */ | ||
| 199 | - const cachedValue = this.#store.get(this.#cacheKey) | ||
| 200 | - if (!cachedValue) { | ||
| 201 | - // Do not create a new cache entry, as a 304 won't have a body - so cannot be cached. | ||
| 202 | - return downstreamOnHeaders() | ||
| 203 | - } | ||
| 204 | - | ||
| 205 | - // Re-use the cached value: statuscode, statusmessage, headers and body | ||
| 206 | - value.statusCode = cachedValue.statusCode | ||
| 207 | - value.statusMessage = cachedValue.statusMessage | ||
| 208 | - value.etag = cachedValue.etag | ||
| 209 | - value.headers = { ...cachedValue.headers, ...strippedHeaders } | ||
| 196 | + const handle304 = (cachedValue) => { | ||
| 197 | + if (!cachedValue) { | ||
| 198 | + // Do not create a new cache entry, as a 304 won't have a body - so cannot be cached. | ||
| 199 | + return downstreamOnHeaders() | ||
| 200 | + } | ||
| 210 | 201 | ||
| 211 | - downstreamOnHeaders() | ||
| 202 | + // Re-use the cached value: statuscode, statusmessage, headers and body | ||
| 203 | + value.statusCode = cachedValue.statusCode | ||
| 204 | + value.statusMessage = cachedValue.statusMessage | ||
| 205 | + value.etag = cachedValue.etag | ||
| 206 | + value.headers = { ...cachedValue.headers, ...strippedHeaders } | ||
| 212 | 207 | ||
| 213 | - this.#writeStream = this.#store.createWriteStream(this.#cacheKey, value) | ||
| 208 | + downstreamOnHeaders() | ||
| 214 | 209 | ||
| 215 | - if (!this.#writeStream || !cachedValue?.body) { | ||
| 216 | - return | ||
| 217 | - } | ||
| 210 | + this.#writeStream = this.#store.createWriteStream(this.#cacheKey, value) | ||
| 218 | 211 | ||
| 219 | - const bodyIterator = cachedValue.body.values() | ||
| 212 | + if (!this.#writeStream || !cachedValue?.body) { | ||
| 213 | + return | ||
| 214 | + } | ||
| 220 | 215 | ||
| 221 | - const streamCachedBody = () => { | ||
| 222 | - for (const chunk of bodyIterator) { | ||
| 223 | - const full = this.#writeStream.write(chunk) === false | ||
| 224 | - this.#handler.onResponseData?.(controller, chunk) | ||
| 225 | - // when stream is full stop writing until we get a 'drain' event | ||
| 226 | - if (full) { | ||
| 227 | - break | ||
| 216 | + if (typeof cachedValue.body.values === 'function') { | ||
| 217 | + const bodyIterator = cachedValue.body.values() | ||
| 218 | + | ||
| 219 | + const streamCachedBody = () => { | ||
| 220 | + for (const chunk of bodyIterator) { | ||
| 221 | + const full = this.#writeStream.write(chunk) === false | ||
| 222 | + this.#handler.onResponseData?.(controller, chunk) | ||
| 223 | + // when stream is full stop writing until we get a 'drain' event | ||
| 224 | + if (full) { | ||
| 225 | + break | ||
| 226 | + } | ||
| 227 | + } | ||
| 228 | 228 | } | |
| 229 | - } | ||
| 230 | - } | ||
| 231 | 229 | ||
| 232 | - this.#writeStream | ||
| 233 | - .on('error', function () { | ||
| 234 | - handler.#writeStream = undefined | ||
| 235 | - handler.#store.delete(handler.#cacheKey) | ||
| 236 | - }) | ||
| 237 | - .on('drain', () => { | ||
| 230 | + this.#writeStream | ||
| 231 | + .on('error', function () { | ||
| 232 | + handler.#writeStream = undefined | ||
| 233 | + handler.#store.delete(handler.#cacheKey) | ||
| 234 | + }) | ||
| 235 | + .on('drain', () => { | ||
| 236 | + streamCachedBody() | ||
| 237 | + }) | ||
| 238 | + .on('close', function () { | ||
| 239 | + if (handler.#writeStream === this) { | ||
| 240 | + handler.#writeStream = undefined | ||
| 241 | + } | ||
| 242 | + }) | ||
| 243 | + | ||
| 238 | 244 | streamCachedBody() | |
| 239 | - }) | ||
| 240 | - .on('close', function () { | ||
| 241 | - if (handler.#writeStream === this) { | ||
| 242 | - handler.#writeStream = undefined | ||
| 243 | - } | ||
| 244 | - }) | ||
| 245 | + } else if (typeof cachedValue.body.on === 'function') { | ||
| 246 | + // Readable stream body (e.g. from async/remote cache stores) | ||
| 247 | + cachedValue.body | ||
| 248 | + .on('data', (chunk) => { | ||
| 249 | + this.#writeStream.write(chunk) | ||
| 250 | + this.#handler.onResponseData?.(controller, chunk) | ||
| 251 | + }) | ||
| 252 | + .on('end', () => { | ||
| 253 | + this.#writeStream.end() | ||
| 254 | + }) | ||
| 255 | + .on('error', () => { | ||
| 256 | + this.#writeStream = undefined | ||
| 257 | + this.#store.delete(this.#cacheKey) | ||
| 258 | + }) | ||
| 259 | + | ||
| 260 | + this.#writeStream | ||
| 261 | + .on('error', function () { | ||
| 262 | + handler.#writeStream = undefined | ||
| 263 | + handler.#store.delete(handler.#cacheKey) | ||
| 264 | + }) | ||
| 265 | + .on('close', function () { | ||
| 266 | + if (handler.#writeStream === this) { | ||
| 267 | + handler.#writeStream = undefined | ||
| 268 | + } | ||
| 269 | + }) | ||
| 270 | + } | ||
| 271 | + } | ||
| 245 | 272 | ||
| 246 | - streamCachedBody() | ||
| 273 | + /** | ||
| 274 | + * @type {import('../../types/cache-interceptor.d.ts').default.CacheValue} | ||
| 275 | + */ | ||
| 276 | + const result = this.#store.get(this.#cacheKey) | ||
| 277 | + if (result && typeof result.then === 'function') { | ||
| 278 | + result.then(handle304) | ||
| 279 | + } else { | ||
| 280 | + handle304(result) | ||
| 281 | + } | ||
| 247 | 282 | } else { | |
| 248 | 283 | if (typeof resHeaders.etag === 'string' && isEtagUsable(resHeaders.etag)) { | |
| 249 | 284 | value.etag = resHeaders.etag | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -292,7 +292,7 @@ function handleResult ( | |||
| 292 | 292 | ||
| 293 | 293 | // Start background revalidation (fire-and-forget) | |
| 294 | 294 | queueMicrotask(() => { | |
| 295 | - let headers = { | ||
| 295 | + const headers = { | ||
| 296 | 296 | ...opts.headers, | |
| 297 | 297 | 'if-modified-since': new Date(result.cachedAt).toUTCString() | |
| 298 | 298 | } | |
@@ -302,9 +302,10 @@ function handleResult ( | |||
| 302 | 302 | } | |
| 303 | 303 | ||
| 304 | 304 | if (result.vary) { | |
| 305 | - headers = { | ||
| 306 | - ...headers, | ||
| 307 | - ...result.vary | ||
| 305 | + for (const key in result.vary) { | ||
| 306 | + if (result.vary[key] != null) { | ||
| 307 | + headers[key] = result.vary[key] | ||
| 308 | + } | ||
| 308 | 309 | } | |
| 309 | 310 | } | |
| 310 | 311 | ||
@@ -335,7 +336,7 @@ function handleResult ( | |||
| 335 | 336 | withinStaleIfErrorThreshold = now < (result.staleAt + (staleIfErrorExpiry * 1000)) | |
| 336 | 337 | } | |
| 337 | 338 | ||
| 338 | - let headers = { | ||
| 339 | + const headers = { | ||
| 339 | 340 | ...opts.headers, | |
| 340 | 341 | 'if-modified-since': new Date(result.cachedAt).toUTCString() | |
| 341 | 342 | } | |
@@ -345,9 +346,10 @@ function handleResult ( | |||
| 345 | 346 | } | |
| 346 | 347 | ||
| 347 | 348 | if (result.vary) { | |
| 348 | - headers = { | ||
| 349 | - ...headers, | ||
| 350 | - ...result.vary | ||
| 349 | + for (const key in result.vary) { | ||
| 350 | + if (result.vary[key] != null) { | ||
| 351 | + headers[key] = result.vary[key] | ||
| 352 | + } | ||
| 351 | 353 | } | |
| 352 | 354 | } | |
| 353 | 355 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -46,8 +46,6 @@ module.exports = (opts = {}) => { | |||
| 46 | 46 | // Convert to lowercase Set for case-insensitive header exclusion from deduplication key | |
| 47 | 47 | const excludeHeaderNamesSet = new Set(excludeHeaderNames.map(name => name.toLowerCase())) | |
| 48 | 48 | ||
| 49 | - const safeMethodsToNotDeduplicate = util.safeHTTPMethods.filter(method => methods.includes(method) === false) | ||
| 50 | - | ||
| 51 | 49 | /** | |
| 52 | 50 | * Map of pending requests for deduplication | |
| 53 | 51 | * @type {Map<string, DeduplicationHandler>} | |
@@ -56,7 +54,7 @@ module.exports = (opts = {}) => { | |||
| 56 | 54 | ||
| 57 | 55 | return dispatch => { | |
| 58 | 56 | return (opts, handler) => { | |
| 59 | - if (!opts.origin || safeMethodsToNotDeduplicate.includes(opts.method)) { | ||
| 57 | + if (!opts.origin || methods.includes(opts.method) === false) { | ||
| 60 | 58 | return dispatch(opts, handler) | |
| 61 | 59 | } | |
| 62 | 60 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1,5 +1,5 @@ | |||
| 1 | 1 | ||
| 2 | - > undici@7.21.0 build:wasm | ||
| 2 | + > undici@7.22.0 build:wasm | ||
| 3 | 3 | > node build/wasm.js --docker | |
| 4 | 4 | ||
| 5 | 5 | > docker run --rm --platform=linux/x86_64 --user 1001:1001 --mount type=bind,source=/home/runner/work/node/node/deps/undici/src/lib/llhttp,target=/home/node/build/lib/llhttp --mount type=bind,source=/home/runner/work/node/node/deps/undici/src/build,target=/home/node/build/build --mount type=bind,source=/home/runner/work/node/node/deps/undici/src/deps,target=/home/node/build/deps -t ghcr.io/nodejs/wasm-builder@sha256:975f391d907e42a75b8c72eb77c782181e941608687d4d8694c3e9df415a0970 node build/wasm.js | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -2302,6 +2302,41 @@ async function httpNetworkFetch ( | |||
| 2302 | 2302 | reject(error) | |
| 2303 | 2303 | }, | |
| 2304 | 2304 | ||
| 2305 | + onRequestUpgrade (_controller, status, headers, socket) { | ||
| 2306 | + // We need to support 200 for websocket over h2 as per RFC-8441 | ||
| 2307 | + // Absence of session means H1 | ||
| 2308 | + if ((socket.session != null && status !== 200) || (socket.session == null && status !== 101)) { | ||
| 2309 | + return false | ||
| 2310 | + } | ||
| 2311 | + | ||
| 2312 | + const headersList = new HeadersList() | ||
| 2313 | + | ||
| 2314 | + for (const [name, value] of Object.entries(headers)) { | ||
| 2315 | + if (value == null) { | ||
| 2316 | + continue | ||
| 2317 | + } | ||
| 2318 | + | ||
| 2319 | + const headerName = name.toLowerCase() | ||
| 2320 | + | ||
| 2321 | + if (Array.isArray(value)) { | ||
| 2322 | + for (const entry of value) { | ||
| 2323 | + headersList.append(headerName, String(entry), true) | ||
| 2324 | + } | ||
| 2325 | + } else { | ||
| 2326 | + headersList.append(headerName, String(value), true) | ||
| 2327 | + } | ||
| 2328 | + } | ||
| 2329 | + | ||
| 2330 | + resolve({ | ||
| 2331 | + status, | ||
| 2332 | + statusText: STATUS_CODES[status], | ||
| 2333 | + headersList, | ||
| 2334 | + socket | ||
| 2335 | + }) | ||
| 2336 | + | ||
| 2337 | + return true | ||
| 2338 | + }, | ||
| 2339 | + | ||
| 2305 | 2340 | onUpgrade (status, rawHeaders, socket) { | |
| 2306 | 2341 | // We need to support 200 for websocket over h2 as per RFC-8441 | |
| 2307 | 2342 | // Absence of session means H1 | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1439,7 +1439,7 @@ function hasAuthenticationEntry (request) { | |||
| 1439 | 1439 | */ | |
| 1440 | 1440 | function includesCredentials (url) { | |
| 1441 | 1441 | // A URL includes credentials if its username or password is not the empty string. | |
| 1442 | - return !!(url.username && url.password) | ||
| 1442 | + return !!(url.username || url.password) | ||
| 1443 | 1443 | } | |
| 1444 | 1444 | ||
| 1445 | 1445 | /** | |
| Back | FazBrowse Home | New Git URL |
0 commit comments