GeezerVison is showing github.com — deps: update undici to 5.8.0 · nodejs/node@aac97c2
File treeExpand file treeCollapse file tree Open diff view settings Expand file treeCollapse file tree Open diff view settings Collapse file Copy file name to clipboardExpand all lines: deps/undici/src/docs/best-practices/proxy.md +8-7Lines changed: 8 additions & 7 deletions - Display the source diff
- Display the rich diff
| Original file line number | Diff line number | Diff line change |
|---|
@@ -20,10 +20,10 @@ import { createServer } from 'http' | 20 | 20 | import proxy from 'proxy' | 21 | 21 |
| 22 | 22 | const server = await buildServer() | 23 | | -const proxy = await buildProxy() | | 23 | +const proxyServer = await buildProxy() | 24 | 24 |
| 25 | 25 | const serverUrl = `http://localhost:${server.address().port}` | 26 | | -const proxyUrl = `http://localhost:${proxy.address().port}` | | 26 | +const proxyUrl = `http://localhost:${proxyServer.address().port}` | 27 | 27 |
| 28 | 28 | server.on('request', (req, res) => { | 29 | 29 | console.log(req.url) // '/hello?foo=bar' | @@ -47,7 +47,7 @@ console.log(response.statusCode) // 200 | 47 | 47 | console.log(JSON.parse(data)) // { hello: 'world' } | 48 | 48 |
| 49 | 49 | server.close() | 50 | | -proxy.close() | | 50 | +proxyServer.close() | 51 | 51 | client.close() | 52 | 52 |
| 53 | 53 | function buildServer () { | @@ -73,12 +73,12 @@ import { createServer } from 'http' | 73 | 73 | import proxy from 'proxy' | 74 | 74 |
| 75 | 75 | const server = await buildServer() | 76 | | -const proxy = await buildProxy() | | 76 | +const proxyServer = await buildProxy() | 77 | 77 |
| 78 | 78 | const serverUrl = `http://localhost:${server.address().port}` | 79 | | -const proxyUrl = `http://localhost:${proxy.address().port}` | | 79 | +const proxyUrl = `http://localhost:${proxyServer.address().port}` | 80 | 80 |
| 81 | | -proxy.authenticate = function (req, fn) { | | 81 | +proxyServer.authenticate = function (req, fn) { | 82 | 82 | fn(null, req.headers['proxy-authorization'] === `Basic ${Buffer.from('user:pass').toString('base64')}`) | 83 | 83 | } | 84 | 84 |
| @@ -107,7 +107,7 @@ console.log(response.statusCode) // 200 | 107 | 107 | console.log(JSON.parse(data)) // { hello: 'world' } | 108 | 108 |
| 109 | 109 | server.close() | 110 | | -proxy.close() | | 110 | +proxyServer.close() | 111 | 111 | client.close() | 112 | 112 |
| 113 | 113 | function buildServer () { | @@ -124,3 +124,4 @@ function buildProxy () { | 124 | 124 | }) | 125 | 125 | } | 126 | 126 | ``` | | 127 | + |
Collapse file Copy file name to clipboardExpand all lines: deps/undici/src/lib/balanced-pool.js +81-4Lines changed: 81 additions & 4 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -18,6 +18,17 @@ const { parseOrigin } = require('./core/util') | 18 | 18 | const kFactory = Symbol('factory') | 19 | 19 |
| 20 | 20 | const kOptions = Symbol('options') | | 21 | +const kGreatestCommonDivisor = Symbol('kGreatestCommonDivisor') | | 22 | +const kCurrentWeight = Symbol('kCurrentWeight') | | 23 | +const kIndex = Symbol('kIndex') | | 24 | +const kWeight = Symbol('kWeight') | | 25 | +const kMaxWeightPerServer = Symbol('kMaxWeightPerServer') | | 26 | +const kErrorPenalty = Symbol('kErrorPenalty') | | 27 | + | | 28 | +function getGreatestCommonDivisor (a, b) { | | 29 | + if (b === 0) return a | | 30 | + return getGreatestCommonDivisor(b, a % b) | | 31 | +} | 21 | 32 |
| 22 | 33 | function defaultFactory (origin, opts) { | 23 | 34 | return new Pool(origin, opts) | @@ -28,6 +39,11 @@ class BalancedPool extends PoolBase { | 28 | 39 | super() | 29 | 40 |
| 30 | 41 | this[kOptions] = opts | | 42 | + this[kIndex] = -1 | | 43 | + this[kCurrentWeight] = 0 | | 44 | + | | 45 | + this[kMaxWeightPerServer] = this[kOptions].maxWeightPerServer || 100 | | 46 | + this[kErrorPenalty] = this[kOptions].errorPenalty || 15 | 31 | 47 |
| 32 | 48 | if (!Array.isArray(upstreams)) { | 33 | 49 | upstreams = [upstreams] | @@ -42,6 +58,7 @@ class BalancedPool extends PoolBase { | 42 | 58 | for (const upstream of upstreams) { | 43 | 59 | this.addUpstream(upstream) | 44 | 60 | } | | 61 | + this._updateBalancedPoolStats() | 45 | 62 | } | 46 | 63 |
| 47 | 64 | addUpstream (upstream) { | @@ -54,12 +71,40 @@ class BalancedPool extends PoolBase { | 54 | 71 | ))) { | 55 | 72 | return this | 56 | 73 | } | | 74 | + const pool = this[kFactory](upstreamOrigin, Object.assign({}, this[kOptions])) | | 75 | + | | 76 | + this[kAddClient](pool) | | 77 | + pool.on('connect', () => { | | 78 | + pool[kWeight] = Math.min(this[kMaxWeightPerServer], pool[kWeight] + this[kErrorPenalty]) | | 79 | + }) | | 80 | + | | 81 | + pool.on('connectionError', () => { | | 82 | + pool[kWeight] = Math.max(1, pool[kWeight] - this[kErrorPenalty]) | | 83 | + this._updateBalancedPoolStats() | | 84 | + }) | | 85 | + | | 86 | + pool.on('disconnect', (...args) => { | | 87 | + const err = args[2] | | 88 | + if (err && err.code === 'UND_ERR_SOCKET') { | | 89 | + // decrease the weight of the pool. | | 90 | + pool[kWeight] = Math.max(1, pool[kWeight] - this[kErrorPenalty]) | | 91 | + this._updateBalancedPoolStats() | | 92 | + } | | 93 | + }) | | 94 | + | | 95 | + for (const client of this[kClients]) { | | 96 | + client[kWeight] = this[kMaxWeightPerServer] | | 97 | + } | 57 | 98 |
| 58 | | - this[kAddClient](this[kFactory](upstreamOrigin, Object.assign({}, this[kOptions]))) | | 99 | + this._updateBalancedPoolStats() | 59 | 100 |
| 60 | 101 | return this | 61 | 102 | } | 62 | 103 |
| | 104 | + _updateBalancedPoolStats () { | | 105 | + this[kGreatestCommonDivisor] = this[kClients].map(p => p[kWeight]).reduce(getGreatestCommonDivisor, 0) | | 106 | + } | | 107 | + | 63 | 108 | removeUpstream (upstream) { | 64 | 109 | const upstreamOrigin = parseOrigin(upstream).origin | 65 | 110 |
| @@ -100,10 +145,42 @@ class BalancedPool extends PoolBase { | 100 | 145 | return | 101 | 146 | } | 102 | 147 |
| 103 | | - this[kClients].splice(this[kClients].indexOf(dispatcher), 1) | 104 | | - this[kClients].push(dispatcher) | | 148 | + const allClientsBusy = this[kClients].map(pool => pool[kNeedDrain]).reduce((a, b) => a && b, true) | | 149 | + | | 150 | + if (allClientsBusy) { | | 151 | + return | | 152 | + } | | 153 | + | | 154 | + let counter = 0 | | 155 | + | | 156 | + let maxWeightIndex = this[kClients].findIndex(pool => !pool[kNeedDrain]) | | 157 | + | | 158 | + while (counter++ < this[kClients].length) { | | 159 | + this[kIndex] = (this[kIndex] + 1) % this[kClients].length | | 160 | + const pool = this[kClients][this[kIndex]] | | 161 | + | | 162 | + // find pool index with the largest weight | | 163 | + if (pool[kWeight] > this[kClients][maxWeightIndex][kWeight] && !pool[kNeedDrain]) { | | 164 | + maxWeightIndex = this[kIndex] | | 165 | + } | | 166 | + | | 167 | + // decrease the current weight every `this[kClients].length`. | | 168 | + if (this[kIndex] === 0) { | | 169 | + // Set the current weight to the next lower weight. | | 170 | + this[kCurrentWeight] = this[kCurrentWeight] - this[kGreatestCommonDivisor] | | 171 | + | | 172 | + if (this[kCurrentWeight] <= 0) { | | 173 | + this[kCurrentWeight] = this[kMaxWeightPerServer] | | 174 | + } | | 175 | + } | | 176 | + if (pool[kWeight] >= this[kCurrentWeight] && (!pool[kNeedDrain])) { | | 177 | + return pool | | 178 | + } | | 179 | + } | 105 | 180 |
| 106 | | - return dispatcher | | 181 | + this[kCurrentWeight] = this[kClients][maxWeightIndex][kWeight] | | 182 | + this[kIndex] = maxWeightIndex | | 183 | + return this[kClients][maxWeightIndex] | 107 | 184 | } | 108 | 185 | } | 109 | 186 |
| |
Collapse file Copy file name to clipboardExpand all lines: deps/undici/src/lib/core/request.js +29Lines changed: 29 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| 7 | 7 | const assert = require('assert') | 8 | 8 | const util = require('./util') | 9 | 9 |
| | 10 | +// tokenRegExp and headerCharRegex have been lifted from | | 11 | +// https://github.com/nodejs/node/blob/main/lib/_http_common.js | | 12 | + | | 13 | +/** | | 14 | + * Verifies that the given val is a valid HTTP token | | 15 | + * per the rules defined in RFC 7230 | | 16 | + * See https://tools.ietf.org/html/rfc7230#section-3.2.6 | | 17 | + */ | | 18 | +const tokenRegExp = /^[\^_`a-zA-Z\-0-9!#$%&'*+.|~]+$/ | | 19 | + | | 20 | +/** | | 21 | + * Matches if val contains an invalid field-vchar | | 22 | + * field-value = *( field-content / obs-fold ) | | 23 | + * field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ] | | 24 | + * field-vchar = VCHAR / obs-text | | 25 | + */ | | 26 | +const headerCharRegex = /[^\t\x20-\x7e\x80-\xff]/ | | 27 | + | | 28 | +// Verifies that a given path is valid does not contain control chars \x00 to \x20 | | 29 | +const invalidPathRegex = /[^\u0021-\u00ff]/ | | 30 | + | 10 | 31 | const kHandler = Symbol('handler') | 11 | 32 |
| 12 | 33 | const channels = {} | @@ -54,10 +75,14 @@ class Request { | 54 | 75 | method !== 'CONNECT' | 55 | 76 | ) { | 56 | 77 | throw new InvalidArgumentError('path must be an absolute URL or start with a slash') | | 78 | + } else if (invalidPathRegex.exec(path) !== null) { | | 79 | + throw new InvalidArgumentError('invalid request path') | 57 | 80 | } | 58 | 81 |
| 59 | 82 | if (typeof method !== 'string') { | 60 | 83 | throw new InvalidArgumentError('method must be a string') | | 84 | + } else if (tokenRegExp.exec(method) === null) { | | 85 | + throw new InvalidArgumentError('invalid request method') | 61 | 86 | } | 62 | 87 |
| 63 | 88 | if (upgrade && typeof upgrade !== 'string') { | @@ -301,6 +326,10 @@ function processHeader (request, key, val) { | 301 | 326 | key.toLowerCase() === 'expect' | 302 | 327 | ) { | 303 | 328 | throw new NotSupportedError('expect header not supported') | | 329 | + } else if (tokenRegExp.exec(key) === null) { | | 330 | + throw new InvalidArgumentError('invalid header key') | | 331 | + } else if (headerCharRegex.exec(val) !== null) { | | 332 | + throw new InvalidArgumentError(`invalid ${key} header`) | 304 | 333 | } else { | 305 | 334 | request.headers += `${key}: ${val}\r\n` | 306 | 335 | } | |
Collapse file Copy file name to clipboardExpand all lines: deps/undici/src/lib/fetch/body.js +16Lines changed: 16 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -291,6 +291,10 @@ function bodyMixinMethods (instance) { | 291 | 291 | const chunks = [] | 292 | 292 |
| 293 | 293 | for await (const chunk of consumeBody(this[kState].body)) { | | 294 | + if (!isUint8Array(chunk)) { | | 295 | + throw new TypeError('Expected Uint8Array chunk') | | 296 | + } | | 297 | + | 294 | 298 | // Assemble one final large blob with Uint8Array's can exhaust memory. | 295 | 299 | // That's why we create create multiple blob's and using references | 296 | 300 | chunks.push(new Blob([chunk])) | @@ -314,6 +318,10 @@ function bodyMixinMethods (instance) { | 314 | 318 | let offset = 0 | 315 | 319 |
| 316 | 320 | for await (const chunk of consumeBody(this[kState].body)) { | | 321 | + if (!isUint8Array(chunk)) { | | 322 | + throw new TypeError('Expected Uint8Array chunk') | | 323 | + } | | 324 | + | 317 | 325 | buffer.set(chunk, offset) | 318 | 326 | offset += chunk.length | 319 | 327 | } | @@ -331,6 +339,10 @@ function bodyMixinMethods (instance) { | 331 | 339 | let size = 0 | 332 | 340 |
| 333 | 341 | for await (const chunk of consumeBody(this[kState].body)) { | | 342 | + if (!isUint8Array(chunk)) { | | 343 | + throw new TypeError('Expected Uint8Array chunk') | | 344 | + } | | 345 | + | 334 | 346 | chunks.push(chunk) | 335 | 347 | size += chunk.byteLength | 336 | 348 | } | @@ -355,6 +367,10 @@ function bodyMixinMethods (instance) { | 355 | 367 | const textDecoder = new TextDecoder() | 356 | 368 |
| 357 | 369 | for await (const chunk of consumeBody(this[kState].body)) { | | 370 | + if (!isUint8Array(chunk)) { | | 371 | + throw new TypeError('Expected Uint8Array chunk') | | 372 | + } | | 373 | + | 358 | 374 | result += textDecoder.decode(chunk, { stream: true }) | 359 | 375 | } | 360 | 376 |
| |
Collapse file Copy file name to clipboardExpand all lines: deps/undici/src/lib/handler/redirect.js +2-1Lines changed: 2 additions & 1 deletion | Original file line number | Diff line number | Diff line change |
|---|
@@ -186,7 +186,8 @@ function shouldRemoveHeader (header, removeContent, unknownOrigin) { | 186 | 186 | return ( | 187 | 187 | (header.length === 4 && header.toString().toLowerCase() === 'host') || | 188 | 188 | (removeContent && header.toString().toLowerCase().indexOf('content-') === 0) || | 189 | | - (unknownOrigin && header.length === 13 && header.toString().toLowerCase() === 'authorization') | | 189 | + (unknownOrigin && header.length === 13 && header.toString().toLowerCase() === 'authorization') || | | 190 | + (unknownOrigin && header.length === 6 && header.toString().toLowerCase() === 'cookie') | 190 | 191 | ) | 191 | 192 | } | 192 | 193 |
| |
Collapse file Copy file name to clipboardExpand all lines: deps/undici/src/lib/mock/mock-utils.js +2-1Lines changed: 2 additions & 1 deletion | Original file line number | Diff line number | Diff line change |
|---|
| 8 | 8 | kOrigin, | 9 | 9 | kGetNetConnect | 10 | 10 | } = require('./mock-symbols') | 11 | | -const { buildURL } = require('../core/util') | | 11 | +const { buildURL, nop } = require('../core/util') | 12 | 12 |
| 13 | 13 | function matchValue (match, value) { | 14 | 14 | if (typeof match === 'string') { | @@ -288,6 +288,7 @@ function mockDispatch (opts, handler) { | 288 | 288 | const responseHeaders = generateKeyValues(headers) | 289 | 289 | const responseTrailers = generateKeyValues(trailers) | 290 | 290 |
| | 291 | + handler.abort = nop | 291 | 292 | handler.onHeaders(statusCode, responseHeaders, resume, getStatusText(statusCode)) | 292 | 293 | handler.onData(Buffer.from(responseData)) | 293 | 294 | handler.onComplete(responseTrailers) | |
Collapse file Copy file name to clipboardExpand all lines: deps/undici/src/package.json +1-1Lines changed: 1 addition & 1 deletion | Original file line number | Diff line number | Diff line change |
|---|
| 1 | 1 | { | 2 | 2 | "name": "undici", | 3 | | - "version": "5.7.0", | | 3 | + "version": "5.8.0", | 4 | 4 | "description": "An HTTP/1.1 client, written from scratch for Node.js", | 5 | 5 | "homepage": "https://undici.nodejs.org", | 6 | 6 | "bugs": { | |
Collapse file Copy file name to clipboardExpand all lines: deps/undici/undici.js +24-1Lines changed: 24 additions & 1 deletion | Original file line number | Diff line number | Diff line change |
|---|
@@ -2227,6 +2227,9 @@ Content-Type: ${value.type || "application/octet-stream"}\r | 2227 | 2227 | } | 2228 | 2228 | const chunks = []; | 2229 | 2229 | for await (const chunk of consumeBody(this[kState].body)) { | | 2230 | + if (!isUint8Array(chunk)) { | | 2231 | + throw new TypeError("Expected Uint8Array chunk"); | | 2232 | + } | 2230 | 2233 | chunks.push(new Blob([chunk])); | 2231 | 2234 | } | 2232 | 2235 | return new Blob(chunks, { type: this.headers.get("Content-Type") || "" }); | @@ -2241,6 +2244,9 @@ Content-Type: ${value.type || "application/octet-stream"}\r | 2241 | 2244 | const buffer2 = new Uint8Array(contentLength); | 2242 | 2245 | let offset2 = 0; | 2243 | 2246 | for await (const chunk of consumeBody(this[kState].body)) { | | 2247 | + if (!isUint8Array(chunk)) { | | 2248 | + throw new TypeError("Expected Uint8Array chunk"); | | 2249 | + } | 2244 | 2250 | buffer2.set(chunk, offset2); | 2245 | 2251 | offset2 += chunk.length; | 2246 | 2252 | } | @@ -2249,6 +2255,9 @@ Content-Type: ${value.type || "application/octet-stream"}\r | 2249 | 2255 | const chunks = []; | 2250 | 2256 | let size = 0; | 2251 | 2257 | for await (const chunk of consumeBody(this[kState].body)) { | | 2258 | + if (!isUint8Array(chunk)) { | | 2259 | + throw new TypeError("Expected Uint8Array chunk"); | | 2260 | + } | 2252 | 2261 | chunks.push(chunk); | 2253 | 2262 | size += chunk.byteLength; | 2254 | 2263 | } | @@ -2267,6 +2276,9 @@ Content-Type: ${value.type || "application/octet-stream"}\r | 2267 | 2276 | let result = ""; | 2268 | 2277 | const textDecoder = new TextDecoder(); | 2269 | 2278 | for await (const chunk of consumeBody(this[kState].body)) { | | 2279 | + if (!isUint8Array(chunk)) { | | 2280 | + throw new TypeError("Expected Uint8Array chunk"); | | 2281 | + } | 2270 | 2282 | result += textDecoder.decode(chunk, { stream: true }); | 2271 | 2283 | } | 2272 | 2284 | result += textDecoder.decode(); | @@ -2350,6 +2362,9 @@ var require_request = __commonJS({ | 2350 | 2362 | } = require_errors(); | 2351 | 2363 | var assert = require("assert"); | 2352 | 2364 | var util = require_util(); | | 2365 | + var tokenRegExp = /^[\^_`a-zA-Z\-0-9!#$%&'*+.|~]+$/; | | 2366 | + var headerCharRegex = /[^\t\x20-\x7e\x80-\xff]/; | | 2367 | + var invalidPathRegex = /[^\u0021-\u00ff]/; | 2353 | 2368 | var kHandler = Symbol("handler"); | 2354 | 2369 | var channels = {}; | 2355 | 2370 | var extractBody; | @@ -2388,9 +2403,13 @@ var require_request = __commonJS({ | 2388 | 2403 | throw new InvalidArgumentError("path must be a string"); | 2389 | 2404 | } else if (path[0] !== "/" && !(path.startsWith("http://") || path.startsWith("https://")) && method !== "CONNECT") { | 2390 | 2405 | throw new InvalidArgumentError("path must be an absolute URL or start with a slash"); | | 2406 | + } else if (invalidPathRegex.exec(path) !== null) { | | 2407 | + throw new InvalidArgumentError("invalid request path"); | 2391 | 2408 | } | 2392 | 2409 | if (typeof method !== "string") { | 2393 | 2410 | throw new InvalidArgumentError("method must be a string"); | | 2411 | + } else if (tokenRegExp.exec(method) === null) { | | 2412 | + throw new InvalidArgumentError("invalid request method"); | 2394 | 2413 | } | 2395 | 2414 | if (upgrade && typeof upgrade !== "string") { | 2396 | 2415 | throw new InvalidArgumentError("upgrade must be a string"); | @@ -2562,6 +2581,10 @@ var require_request = __commonJS({ | 2562 | 2581 | throw new InvalidArgumentError("invalid upgrade header"); | 2563 | 2582 | } else if (key.length === 6 && key.toLowerCase() === "expect") { | 2564 | 2583 | throw new NotSupportedError("expect header not supported"); | | 2584 | + } else if (tokenRegExp.exec(key) === null) { | | 2585 | + throw new InvalidArgumentError("invalid header key"); | | 2586 | + } else if (headerCharRegex.exec(val) !== null) { | | 2587 | + throw new InvalidArgumentError(`invalid ${key} header`); | 2565 | 2588 | } else { | 2566 | 2589 | request.headers += `${key}: ${val}\r | 2567 | 2590 | `; | @@ -2685,7 +2708,7 @@ var require_redirect = __commonJS({ | 2685 | 2708 | } | 2686 | 2709 | } | 2687 | 2710 | function shouldRemoveHeader(header, removeContent, unknownOrigin) { | 2688 | | - return header.length === 4 && header.toString().toLowerCase() === "host" || removeContent && header.toString().toLowerCase().indexOf("content-") === 0 || unknownOrigin && header.length === 13 && header.toString().toLowerCase() === "authorization"; | | 2711 | + return header.length === 4 && header.toString().toLowerCase() === "host" || removeContent && header.toString().toLowerCase().indexOf("content-") === 0 || unknownOrigin && header.length === 13 && header.toString().toLowerCase() === "authorization" || unknownOrigin && header.length === 6 && header.toString().toLowerCase() === "cookie"; | 2689 | 2712 | } | 2690 | 2713 | function cleanRequestHeaders(headers, removeContent, unknownOrigin) { | 2691 | 2714 | const ret = []; | |
|