| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -32,6 +32,8 @@ Returns: `Client` | |||
| 32 | 32 | * **allowH2**: `boolean` - Default: `false`. Enables support for H2 if the server has assigned bigger priority to it through ALPN negotiation. | |
| 33 | 33 | * **useH2c**: `boolean` - Default: `false`. Enforces h2c for non-https connections. | |
| 34 | 34 | * **maxConcurrentStreams**: `number` - Default: `100`. Dictates the maximum number of concurrent streams for a single H2 session. It can be overridden by a SETTINGS remote frame. | |
| 35 | + * **initialWindowSize**: `number` (optional) - Default: `262144` (256KB). Sets the HTTP/2 stream-level flow-control window size (SETTINGS_INITIAL_WINDOW_SIZE). Must be a positive integer greater than 0. This default is higher than Node.js core's default (65535 bytes) to improve throughput, Node's choice is very conservative for current high-bandwith networks. See [RFC 7540 Section 6.9.2](https://datatracker.ietf.org/doc/html/rfc7540#section-6.9.2) for more details. | ||
| 36 | + * **connectionWindowSize**: `number` (optional) - Default `524288` (512KB). Sets the HTTP/2 connection-level flow-control window size using `ClientHttp2Session.setLocalWindowSize()`. Must be a positive integer greater than 0. This provides better flow control for the entire connection across multiple streams. See [Node.js HTTP/2 documentation](https://nodejs.org/api/http2.html#clienthttp2sessionsetlocalwindowsize) for more details. | ||
| 35 | 37 | ||
| 36 | 38 | > **Notes about HTTP/2** | |
| 37 | 39 | > - It only works under TLS connections. h2c is not supported. | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -62,6 +62,8 @@ module.exports = { | |||
| 62 | 62 | kListeners: Symbol('listeners'), | |
| 63 | 63 | kHTTPContext: Symbol('http context'), | |
| 64 | 64 | kMaxConcurrentStreams: Symbol('max concurrent streams'), | |
| 65 | + kHTTP2InitialWindowSize: Symbol('http2 initial window size'), | ||
| 66 | + kHTTP2ConnectionWindowSize: Symbol('http2 connection window size'), | ||
| 65 | 67 | kEnableConnectProtocol: Symbol('http2session connect protocol'), | |
| 66 | 68 | kRemoteSettings: Symbol('http2session remote settings'), | |
| 67 | 69 | kHTTP2Stream: Symbol('http2session client stream'), | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -58,6 +58,8 @@ function wrapRequestBody (body) { | |||
| 58 | 58 | // to determine whether or not it has been disturbed. This is just | |
| 59 | 59 | // a workaround. | |
| 60 | 60 | return new BodyAsyncIterable(body) | |
| 61 | + } else if (body && isFormDataLike(body)) { | ||
| 62 | + return body | ||
| 61 | 63 | } else if ( | |
| 62 | 64 | body && | |
| 63 | 65 | typeof body !== 'string' && | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -25,6 +25,8 @@ const { | |||
| 25 | 25 | kOnError, | |
| 26 | 26 | kMaxConcurrentStreams, | |
| 27 | 27 | kHTTP2Session, | |
| 28 | + kHTTP2InitialWindowSize, | ||
| 29 | + kHTTP2ConnectionWindowSize, | ||
| 28 | 30 | kResume, | |
| 29 | 31 | kSize, | |
| 30 | 32 | kHTTPContext, | |
@@ -87,12 +89,16 @@ function parseH2Headers (headers) { | |||
| 87 | 89 | function connectH2 (client, socket) { | |
| 88 | 90 | client[kSocket] = socket | |
| 89 | 91 | ||
| 92 | + const http2InitialWindowSize = client[kHTTP2InitialWindowSize] | ||
| 93 | + const http2ConnectionWindowSize = client[kHTTP2ConnectionWindowSize] | ||
| 94 | + | ||
| 90 | 95 | const session = http2.connect(client[kUrl], { | |
| 91 | 96 | createConnection: () => socket, | |
| 92 | 97 | peerMaxConcurrentStreams: client[kMaxConcurrentStreams], | |
| 93 | 98 | settings: { | |
| 94 | 99 | // TODO(metcoder95): add support for PUSH | |
| 95 | - enablePush: false | ||
| 100 | + enablePush: false, | ||
| 101 | + ...(http2InitialWindowSize != null ? { initialWindowSize: http2InitialWindowSize } : null) | ||
| 96 | 102 | } | |
| 97 | 103 | }) | |
| 98 | 104 | ||
@@ -107,6 +113,11 @@ function connectH2 (client, socket) { | |||
| 107 | 113 | // States whether or not we have received the remote settings from the server | |
| 108 | 114 | session[kRemoteSettings] = false | |
| 109 | 115 | ||
| 116 | + // Apply connection-level flow control once connected (if supported). | ||
| 117 | + if (http2ConnectionWindowSize) { | ||
| 118 | + util.addListener(session, 'connect', applyConnectionWindowSize.bind(session, http2ConnectionWindowSize)) | ||
| 119 | + } | ||
| 120 | + | ||
| 110 | 121 | util.addListener(session, 'error', onHttp2SessionError) | |
| 111 | 122 | util.addListener(session, 'frameError', onHttp2FrameError) | |
| 112 | 123 | util.addListener(session, 'end', onHttp2SessionEnd) | |
@@ -211,6 +222,16 @@ function resumeH2 (client) { | |||
| 211 | 222 | } | |
| 212 | 223 | } | |
| 213 | 224 | ||
| 225 | + function applyConnectionWindowSize (connectionWindowSize) { | ||
| 226 | + try { | ||
| 227 | + if (typeof this.setLocalWindowSize === 'function') { | ||
| 228 | + this.setLocalWindowSize(connectionWindowSize) | ||
| 229 | + } | ||
| 230 | + } catch { | ||
| 231 | + // Best-effort only. | ||
| 232 | + } | ||
| 233 | + } | ||
| 234 | + | ||
| 214 | 235 | function onHttp2RemoteSettings (settings) { | |
| 215 | 236 | // Fallbacks are a safe bet, remote setting will always override | |
| 216 | 237 | this[kClient][kMaxConcurrentStreams] = settings.maxConcurrentStreams ?? this[kClient][kMaxConcurrentStreams] | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -52,6 +52,8 @@ const { | |||
| 52 | 52 | kOnError, | |
| 53 | 53 | kHTTPContext, | |
| 54 | 54 | kMaxConcurrentStreams, | |
| 55 | + kHTTP2InitialWindowSize, | ||
| 56 | + kHTTP2ConnectionWindowSize, | ||
| 55 | 57 | kResume | |
| 56 | 58 | } = require('../core/symbols.js') | |
| 57 | 59 | const connectH1 = require('./client-h1.js') | |
@@ -108,7 +110,9 @@ class Client extends DispatcherBase { | |||
| 108 | 110 | // h2 | |
| 109 | 111 | maxConcurrentStreams, | |
| 110 | 112 | allowH2, | |
| 111 | - useH2c | ||
| 113 | + useH2c, | ||
| 114 | + initialWindowSize, | ||
| 115 | + connectionWindowSize | ||
| 112 | 116 | } = {}) { | |
| 113 | 117 | if (keepAlive !== undefined) { | |
| 114 | 118 | throw new InvalidArgumentError('unsupported keepAlive, use pipelining=0 instead') | |
@@ -204,6 +208,14 @@ class Client extends DispatcherBase { | |||
| 204 | 208 | throw new InvalidArgumentError('useH2c must be a valid boolean value') | |
| 205 | 209 | } | |
| 206 | 210 | ||
| 211 | + if (initialWindowSize != null && (!Number.isInteger(initialWindowSize) || initialWindowSize < 1)) { | ||
| 212 | + throw new InvalidArgumentError('initialWindowSize must be a positive integer, greater than 0') | ||
| 213 | + } | ||
| 214 | + | ||
| 215 | + if (connectionWindowSize != null && (!Number.isInteger(connectionWindowSize) || connectionWindowSize < 1)) { | ||
| 216 | + throw new InvalidArgumentError('connectionWindowSize must be a positive integer, greater than 0') | ||
| 217 | + } | ||
| 218 | + | ||
| 207 | 219 | super() | |
| 208 | 220 | ||
| 209 | 221 | if (typeof connect !== 'function') { | |
@@ -239,6 +251,14 @@ class Client extends DispatcherBase { | |||
| 239 | 251 | this[kClosedResolve] = null | |
| 240 | 252 | this[kMaxResponseSize] = maxResponseSize > -1 ? maxResponseSize : -1 | |
| 241 | 253 | this[kMaxConcurrentStreams] = maxConcurrentStreams != null ? maxConcurrentStreams : 100 // Max peerConcurrentStreams for a Node h2 server | |
| 254 | + // HTTP/2 window sizes are set to higher defaults than Node.js core for better performance: | ||
| 255 | + // - initialWindowSize: 262144 (256KB) vs Node.js default 65535 (64KB - 1) | ||
| 256 | + // Allows more data to be sent before requiring acknowledgment, improving throughput | ||
| 257 | + // especially on high-latency networks. This matches common production HTTP/2 servers. | ||
| 258 | + // - connectionWindowSize: 524288 (512KB) vs Node.js default (none set) | ||
| 259 | + // Provides better flow control for the entire connection across multiple streams. | ||
| 260 | + this[kHTTP2InitialWindowSize] = initialWindowSize != null ? initialWindowSize : 262144 | ||
| 261 | + this[kHTTP2ConnectionWindowSize] = connectionWindowSize != null ? connectionWindowSize : 524288 | ||
| 242 | 262 | this[kHTTPContext] = null | |
| 243 | 263 | ||
| 244 | 264 | // kQueue is built up of 3 sections separated by | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -9,6 +9,23 @@ const CacheRevalidationHandler = require('../handler/cache-revalidation-handler' | |||
| 9 | 9 | const { assertCacheStore, assertCacheMethods, makeCacheKey, normalizeHeaders, parseCacheControlHeader } = require('../util/cache.js') | |
| 10 | 10 | const { AbortError } = require('../core/errors.js') | |
| 11 | 11 | ||
| 12 | + /** | ||
| 13 | + * @param {(string | RegExp)[] | undefined} origins | ||
| 14 | + * @param {string} name | ||
| 15 | + */ | ||
| 16 | + function assertCacheOrigins (origins, name) { | ||
| 17 | + if (origins === undefined) return | ||
| 18 | + if (!Array.isArray(origins)) { | ||
| 19 | + throw new TypeError(`expected ${name} to be an array or undefined, got ${typeof origins}`) | ||
| 20 | + } | ||
| 21 | + for (let i = 0; i < origins.length; i++) { | ||
| 22 | + const origin = origins[i] | ||
| 23 | + if (typeof origin !== 'string' && !(origin instanceof RegExp)) { | ||
| 24 | + throw new TypeError(`expected ${name}[${i}] to be a string or RegExp, got ${typeof origin}`) | ||
| 25 | + } | ||
| 26 | + } | ||
| 27 | + } | ||
| 28 | + | ||
| 12 | 29 | const nop = () => {} | |
| 13 | 30 | ||
| 14 | 31 | /** | |
@@ -372,7 +389,8 @@ module.exports = (opts = {}) => { | |||
| 372 | 389 | store = new MemoryCacheStore(), | |
| 373 | 390 | methods = ['GET'], | |
| 374 | 391 | cacheByDefault = undefined, | |
| 375 | - type = 'shared' | ||
| 392 | + type = 'shared', | ||
| 393 | + origins = undefined | ||
| 376 | 394 | } = opts | |
| 377 | 395 | ||
| 378 | 396 | if (typeof opts !== 'object' || opts === null) { | |
@@ -381,6 +399,7 @@ module.exports = (opts = {}) => { | |||
| 381 | 399 | ||
| 382 | 400 | assertCacheStore(store, 'opts.store') | |
| 383 | 401 | assertCacheMethods(methods, 'opts.methods') | |
| 402 | + assertCacheOrigins(origins, 'opts.origins') | ||
| 384 | 403 | ||
| 385 | 404 | if (typeof cacheByDefault !== 'undefined' && typeof cacheByDefault !== 'number') { | |
| 386 | 405 | throw new TypeError(`expected opts.cacheByDefault to be number or undefined, got ${typeof cacheByDefault}`) | |
@@ -406,6 +425,29 @@ module.exports = (opts = {}) => { | |||
| 406 | 425 | return dispatch(opts, handler) | |
| 407 | 426 | } | |
| 408 | 427 | ||
| 428 | + // Check if origin is in whitelist | ||
| 429 | + if (origins !== undefined) { | ||
| 430 | + const requestOrigin = opts.origin.toString().toLowerCase() | ||
| 431 | + let isAllowed = false | ||
| 432 | + | ||
| 433 | + for (let i = 0; i < origins.length; i++) { | ||
| 434 | + const allowed = origins[i] | ||
| 435 | + if (typeof allowed === 'string') { | ||
| 436 | + if (allowed.toLowerCase() === requestOrigin) { | ||
| 437 | + isAllowed = true | ||
| 438 | + break | ||
| 439 | + } | ||
| 440 | + } else if (allowed.test(requestOrigin)) { | ||
| 441 | + isAllowed = true | ||
| 442 | + break | ||
| 443 | + } | ||
| 444 | + } | ||
| 445 | + | ||
| 446 | + if (!isAllowed) { | ||
| 447 | + return dispatch(opts, handler) | ||
| 448 | + } | ||
| 449 | + } | ||
| 450 | + | ||
| 409 | 451 | opts = { | |
| 410 | 452 | ...opts, | |
| 411 | 453 | headers: normalizeHeaders(opts) | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1,5 +1,5 @@ | |||
| 1 | 1 | ||
| 2 | - > undici@7.18.2 build:wasm | ||
| 2 | + > undici@7.19.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 | |
|---|---|---|---|
@@ -22,7 +22,7 @@ const { | |||
| 22 | 22 | } = require('./mock-symbols') | |
| 23 | 23 | const MockClient = require('./mock-client') | |
| 24 | 24 | const MockPool = require('./mock-pool') | |
| 25 | - const { matchValue, normalizeSearchParams, buildAndValidateMockOptions } = require('./mock-utils') | ||
| 25 | + const { matchValue, normalizeSearchParams, buildAndValidateMockOptions, normalizeOrigin } = require('./mock-utils') | ||
| 26 | 26 | const { InvalidArgumentError, UndiciError } = require('../core/errors') | |
| 27 | 27 | const Dispatcher = require('../dispatcher/dispatcher') | |
| 28 | 28 | const PendingInterceptorsFormatter = require('./pending-interceptors-formatter') | |
@@ -56,9 +56,9 @@ class MockAgent extends Dispatcher { | |||
| 56 | 56 | } | |
| 57 | 57 | ||
| 58 | 58 | get (origin) { | |
| 59 | - const originKey = this[kIgnoreTrailingSlash] | ||
| 60 | - ? origin.replace(/\/$/, '') | ||
| 61 | - : origin | ||
| 59 | + // Normalize origin to handle URL objects and case-insensitive hostnames | ||
| 60 | + const normalizedOrigin = normalizeOrigin(origin) | ||
| 61 | + const originKey = this[kIgnoreTrailingSlash] ? normalizedOrigin.replace(/\/$/, '') : normalizedOrigin | ||
| 62 | 62 | ||
| 63 | 63 | let dispatcher = this[kMockAgentGet](originKey) | |
| 64 | 64 | ||
@@ -70,6 +70,8 @@ class MockAgent extends Dispatcher { | |||
| 70 | 70 | } | |
| 71 | 71 | ||
| 72 | 72 | dispatch (opts, handler) { | |
| 73 | + opts.origin = normalizeOrigin(opts.origin) | ||
| 74 | + | ||
| 73 | 75 | // Call MockAgent.get to perform additional setup before dispatching as normal | |
| 74 | 76 | this.get(opts.origin) | |
| 75 | 77 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -396,6 +396,18 @@ function checkNetConnect (netConnect, origin) { | |||
| 396 | 396 | return false | |
| 397 | 397 | } | |
| 398 | 398 | ||
| 399 | + function normalizeOrigin (origin) { | ||
| 400 | + if (typeof origin !== 'string' && !(origin instanceof URL)) { | ||
| 401 | + return origin | ||
| 402 | + } | ||
| 403 | + | ||
| 404 | + if (origin instanceof URL) { | ||
| 405 | + return origin.origin | ||
| 406 | + } | ||
| 407 | + | ||
| 408 | + return origin.toLowerCase() | ||
| 409 | + } | ||
| 410 | + | ||
| 399 | 411 | function buildAndValidateMockOptions (opts) { | |
| 400 | 412 | const { agent, ...mockOptions } = opts | |
| 401 | 413 | ||
@@ -430,5 +442,6 @@ module.exports = { | |||
| 430 | 442 | buildAndValidateMockOptions, | |
| 431 | 443 | getHeaderByName, | |
| 432 | 444 | buildHeadersFromArray, | |
| 433 | - normalizeSearchParams | ||
| 445 | + normalizeSearchParams, | ||
| 446 | + normalizeOrigin | ||
| 434 | 447 | } | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -794,9 +794,9 @@ class Cache { | |||
| 794 | 794 | // 5.5.2 | |
| 795 | 795 | for (const response of responses) { | |
| 796 | 796 | // 5.5.2.1 | |
| 797 | - const responseObject = fromInnerResponse(response, 'immutable') | ||
| 797 | + const responseObject = fromInnerResponse(cloneResponse(response), 'immutable') | ||
| 798 | 798 | ||
| 799 | - responseList.push(responseObject.clone()) | ||
| 799 | + responseList.push(responseObject) | ||
| 800 | 800 | ||
| 801 | 801 | if (responseList.length >= maxResponses) { | |
| 802 | 802 | break | |
| Back | FazBrowse Home | New Git URL |
0 commit comments