Skip to content

Commit aac97c2

Browse files
nodejs-github-botdanielleadams
authored andcommitted
deps: update undici to 5.8.0
PR-URL: #43886 Reviewed-By: Michaël Zasso <targos@protonmail.com> Reviewed-By: Filip Skokan <panva.ip@gmail.com> Reviewed-By: Tobias Nießen <tniessen@tnie.de> Reviewed-By: Mohammed Keyvanzadeh <mohammadkeyvanzade94@gmail.com>
1 parent acfc33c commit aac97c2

File tree

8 files changed

+163
-15
lines changed

8 files changed

+163
-15
lines changed

deps/undici/src/docs/best-practices/proxy.md

+8-7
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,10 @@ import { createServer } from 'http'
2020
import proxy from 'proxy'
2121

2222
const server = await buildServer()
23-
const proxy = await buildProxy()
23+
const proxyServer = await buildProxy()
2424

2525
const serverUrl = `http://localhost:${server.address().port}`
26-
const proxyUrl = `http://localhost:${proxy.address().port}`
26+
const proxyUrl = `http://localhost:${proxyServer.address().port}`
2727

2828
server.on('request', (req, res) => {
2929
console.log(req.url) // '/hello?foo=bar'
@@ -47,7 +47,7 @@ console.log(response.statusCode) // 200
4747
console.log(JSON.parse(data)) // { hello: 'world' }
4848

4949
server.close()
50-
proxy.close()
50+
proxyServer.close()
5151
client.close()
5252

5353
function buildServer () {
@@ -73,12 +73,12 @@ import { createServer } from 'http'
7373
import proxy from 'proxy'
7474

7575
const server = await buildServer()
76-
const proxy = await buildProxy()
76+
const proxyServer = await buildProxy()
7777

7878
const serverUrl = `http://localhost:${server.address().port}`
79-
const proxyUrl = `http://localhost:${proxy.address().port}`
79+
const proxyUrl = `http://localhost:${proxyServer.address().port}`
8080

81-
proxy.authenticate = function (req, fn) {
81+
proxyServer.authenticate = function (req, fn) {
8282
fn(null, req.headers['proxy-authorization'] === `Basic ${Buffer.from('user:pass').toString('base64')}`)
8383
}
8484

@@ -107,7 +107,7 @@ console.log(response.statusCode) // 200
107107
console.log(JSON.parse(data)) // { hello: 'world' }
108108

109109
server.close()
110-
proxy.close()
110+
proxyServer.close()
111111
client.close()
112112

113113
function buildServer () {
@@ -124,3 +124,4 @@ function buildProxy () {
124124
})
125125
}
126126
```
127+

deps/undici/src/lib/balanced-pool.js

+81-4
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,17 @@ const { parseOrigin } = require('./core/util')
1818
const kFactory = Symbol('factory')
1919

2020
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+
}
2132

2233
function defaultFactory (origin, opts) {
2334
return new Pool(origin, opts)
@@ -28,6 +39,11 @@ class BalancedPool extends PoolBase {
2839
super()
2940

3041
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
3147

3248
if (!Array.isArray(upstreams)) {
3349
upstreams = [upstreams]
@@ -42,6 +58,7 @@ class BalancedPool extends PoolBase {
4258
for (const upstream of upstreams) {
4359
this.addUpstream(upstream)
4460
}
61+
this._updateBalancedPoolStats()
4562
}
4663

4764
addUpstream (upstream) {
@@ -54,12 +71,40 @@ class BalancedPool extends PoolBase {
5471
))) {
5572
return this
5673
}
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+
}
5798

58-
this[kAddClient](this[kFactory](upstreamOrigin, Object.assign({}, this[kOptions])))
99+
this._updateBalancedPoolStats()
59100

60101
return this
61102
}
62103

104+
_updateBalancedPoolStats () {
105+
this[kGreatestCommonDivisor] = this[kClients].map(p => p[kWeight]).reduce(getGreatestCommonDivisor, 0)
106+
}
107+
63108
removeUpstream (upstream) {
64109
const upstreamOrigin = parseOrigin(upstream).origin
65110

@@ -100,10 +145,42 @@ class BalancedPool extends PoolBase {
100145
return
101146
}
102147

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+
}
105180

106-
return dispatcher
181+
this[kCurrentWeight] = this[kClients][maxWeightIndex][kWeight]
182+
this[kIndex] = maxWeightIndex
183+
return this[kClients][maxWeightIndex]
107184
}
108185
}
109186

deps/undici/src/lib/core/request.js

+29
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,27 @@ const {
77
const assert = require('assert')
88
const util = require('./util')
99

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+
1031
const kHandler = Symbol('handler')
1132

1233
const channels = {}
@@ -54,10 +75,14 @@ class Request {
5475
method !== 'CONNECT'
5576
) {
5677
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')
5780
}
5881

5982
if (typeof method !== 'string') {
6083
throw new InvalidArgumentError('method must be a string')
84+
} else if (tokenRegExp.exec(method) === null) {
85+
throw new InvalidArgumentError('invalid request method')
6186
}
6287

6388
if (upgrade && typeof upgrade !== 'string') {
@@ -301,6 +326,10 @@ function processHeader (request, key, val) {
301326
key.toLowerCase() === 'expect'
302327
) {
303328
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`)
304333
} else {
305334
request.headers += `${key}: ${val}\r\n`
306335
}

deps/undici/src/lib/fetch/body.js

+16
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,10 @@ function bodyMixinMethods (instance) {
291291
const chunks = []
292292

293293
for await (const chunk of consumeBody(this[kState].body)) {
294+
if (!isUint8Array(chunk)) {
295+
throw new TypeError('Expected Uint8Array chunk')
296+
}
297+
294298
// Assemble one final large blob with Uint8Array's can exhaust memory.
295299
// That's why we create create multiple blob's and using references
296300
chunks.push(new Blob([chunk]))
@@ -314,6 +318,10 @@ function bodyMixinMethods (instance) {
314318
let offset = 0
315319

316320
for await (const chunk of consumeBody(this[kState].body)) {
321+
if (!isUint8Array(chunk)) {
322+
throw new TypeError('Expected Uint8Array chunk')
323+
}
324+
317325
buffer.set(chunk, offset)
318326
offset += chunk.length
319327
}
@@ -331,6 +339,10 @@ function bodyMixinMethods (instance) {
331339
let size = 0
332340

333341
for await (const chunk of consumeBody(this[kState].body)) {
342+
if (!isUint8Array(chunk)) {
343+
throw new TypeError('Expected Uint8Array chunk')
344+
}
345+
334346
chunks.push(chunk)
335347
size += chunk.byteLength
336348
}
@@ -355,6 +367,10 @@ function bodyMixinMethods (instance) {
355367
const textDecoder = new TextDecoder()
356368

357369
for await (const chunk of consumeBody(this[kState].body)) {
370+
if (!isUint8Array(chunk)) {
371+
throw new TypeError('Expected Uint8Array chunk')
372+
}
373+
358374
result += textDecoder.decode(chunk, { stream: true })
359375
}
360376

deps/undici/src/lib/handler/redirect.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,8 @@ function shouldRemoveHeader (header, removeContent, unknownOrigin) {
186186
return (
187187
(header.length === 4 && header.toString().toLowerCase() === 'host') ||
188188
(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')
190191
)
191192
}
192193

deps/undici/src/lib/mock/mock-utils.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ const {
88
kOrigin,
99
kGetNetConnect
1010
} = require('./mock-symbols')
11-
const { buildURL } = require('../core/util')
11+
const { buildURL, nop } = require('../core/util')
1212

1313
function matchValue (match, value) {
1414
if (typeof match === 'string') {
@@ -288,6 +288,7 @@ function mockDispatch (opts, handler) {
288288
const responseHeaders = generateKeyValues(headers)
289289
const responseTrailers = generateKeyValues(trailers)
290290

291+
handler.abort = nop
291292
handler.onHeaders(statusCode, responseHeaders, resume, getStatusText(statusCode))
292293
handler.onData(Buffer.from(responseData))
293294
handler.onComplete(responseTrailers)

deps/undici/src/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "undici",
3-
"version": "5.7.0",
3+
"version": "5.8.0",
44
"description": "An HTTP/1.1 client, written from scratch for Node.js",
55
"homepage": "https://undici.nodejs.org",
66
"bugs": {

deps/undici/undici.js

+24-1
Original file line numberDiff line numberDiff line change
@@ -2227,6 +2227,9 @@ Content-Type: ${value.type || "application/octet-stream"}\r
22272227
}
22282228
const chunks = [];
22292229
for await (const chunk of consumeBody(this[kState].body)) {
2230+
if (!isUint8Array(chunk)) {
2231+
throw new TypeError("Expected Uint8Array chunk");
2232+
}
22302233
chunks.push(new Blob([chunk]));
22312234
}
22322235
return new Blob(chunks, { type: this.headers.get("Content-Type") || "" });
@@ -2241,6 +2244,9 @@ Content-Type: ${value.type || "application/octet-stream"}\r
22412244
const buffer2 = new Uint8Array(contentLength);
22422245
let offset2 = 0;
22432246
for await (const chunk of consumeBody(this[kState].body)) {
2247+
if (!isUint8Array(chunk)) {
2248+
throw new TypeError("Expected Uint8Array chunk");
2249+
}
22442250
buffer2.set(chunk, offset2);
22452251
offset2 += chunk.length;
22462252
}
@@ -2249,6 +2255,9 @@ Content-Type: ${value.type || "application/octet-stream"}\r
22492255
const chunks = [];
22502256
let size = 0;
22512257
for await (const chunk of consumeBody(this[kState].body)) {
2258+
if (!isUint8Array(chunk)) {
2259+
throw new TypeError("Expected Uint8Array chunk");
2260+
}
22522261
chunks.push(chunk);
22532262
size += chunk.byteLength;
22542263
}
@@ -2267,6 +2276,9 @@ Content-Type: ${value.type || "application/octet-stream"}\r
22672276
let result = "";
22682277
const textDecoder = new TextDecoder();
22692278
for await (const chunk of consumeBody(this[kState].body)) {
2279+
if (!isUint8Array(chunk)) {
2280+
throw new TypeError("Expected Uint8Array chunk");
2281+
}
22702282
result += textDecoder.decode(chunk, { stream: true });
22712283
}
22722284
result += textDecoder.decode();
@@ -2350,6 +2362,9 @@ var require_request = __commonJS({
23502362
} = require_errors();
23512363
var assert = require("assert");
23522364
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]/;
23532368
var kHandler = Symbol("handler");
23542369
var channels = {};
23552370
var extractBody;
@@ -2388,9 +2403,13 @@ var require_request = __commonJS({
23882403
throw new InvalidArgumentError("path must be a string");
23892404
} else if (path[0] !== "/" && !(path.startsWith("http://") || path.startsWith("https://")) && method !== "CONNECT") {
23902405
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");
23912408
}
23922409
if (typeof method !== "string") {
23932410
throw new InvalidArgumentError("method must be a string");
2411+
} else if (tokenRegExp.exec(method) === null) {
2412+
throw new InvalidArgumentError("invalid request method");
23942413
}
23952414
if (upgrade && typeof upgrade !== "string") {
23962415
throw new InvalidArgumentError("upgrade must be a string");
@@ -2562,6 +2581,10 @@ var require_request = __commonJS({
25622581
throw new InvalidArgumentError("invalid upgrade header");
25632582
} else if (key.length === 6 && key.toLowerCase() === "expect") {
25642583
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`);
25652588
} else {
25662589
request.headers += `${key}: ${val}\r
25672590
`;
@@ -2685,7 +2708,7 @@ var require_redirect = __commonJS({
26852708
}
26862709
}
26872710
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";
26892712
}
26902713
function cleanRequestHeaders(headers, removeContent, unknownOrigin) {
26912714
const ret = [];

0 commit comments

Comments
 (0)