Skip to content

Commit

Permalink
deps: update undici to 5.8.0
Browse files Browse the repository at this point in the history
PR-URL: #43886
Reviewed-By: Michaël Zasso <[email protected]>
Reviewed-By: Filip Skokan <[email protected]>
Reviewed-By: Tobias Nießen <[email protected]>
Reviewed-By: Mohammed Keyvanzadeh <[email protected]>
  • Loading branch information
nodejs-github-bot authored and danielleadams committed Jul 26, 2022
1 parent acfc33c commit aac97c2
Show file tree
Hide file tree
Showing 8 changed files with 163 additions and 15 deletions.
15 changes: 8 additions & 7 deletions deps/undici/src/docs/best-practices/proxy.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@ import { createServer } from 'http'
import proxy from 'proxy'

const server = await buildServer()
const proxy = await buildProxy()
const proxyServer = await buildProxy()

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

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

server.close()
proxy.close()
proxyServer.close()
client.close()

function buildServer () {
Expand All @@ -73,12 +73,12 @@ import { createServer } from 'http'
import proxy from 'proxy'

const server = await buildServer()
const proxy = await buildProxy()
const proxyServer = await buildProxy()

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

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

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

server.close()
proxy.close()
proxyServer.close()
client.close()

function buildServer () {
Expand All @@ -124,3 +124,4 @@ function buildProxy () {
})
}
```

85 changes: 81 additions & 4 deletions deps/undici/src/lib/balanced-pool.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,17 @@ const { parseOrigin } = require('./core/util')
const kFactory = Symbol('factory')

const kOptions = Symbol('options')
const kGreatestCommonDivisor = Symbol('kGreatestCommonDivisor')
const kCurrentWeight = Symbol('kCurrentWeight')
const kIndex = Symbol('kIndex')
const kWeight = Symbol('kWeight')
const kMaxWeightPerServer = Symbol('kMaxWeightPerServer')
const kErrorPenalty = Symbol('kErrorPenalty')

function getGreatestCommonDivisor (a, b) {
if (b === 0) return a
return getGreatestCommonDivisor(b, a % b)
}

function defaultFactory (origin, opts) {
return new Pool(origin, opts)
Expand All @@ -28,6 +39,11 @@ class BalancedPool extends PoolBase {
super()

this[kOptions] = opts
this[kIndex] = -1
this[kCurrentWeight] = 0

this[kMaxWeightPerServer] = this[kOptions].maxWeightPerServer || 100
this[kErrorPenalty] = this[kOptions].errorPenalty || 15

if (!Array.isArray(upstreams)) {
upstreams = [upstreams]
Expand All @@ -42,6 +58,7 @@ class BalancedPool extends PoolBase {
for (const upstream of upstreams) {
this.addUpstream(upstream)
}
this._updateBalancedPoolStats()
}

addUpstream (upstream) {
Expand All @@ -54,12 +71,40 @@ class BalancedPool extends PoolBase {
))) {
return this
}
const pool = this[kFactory](upstreamOrigin, Object.assign({}, this[kOptions]))

this[kAddClient](pool)
pool.on('connect', () => {
pool[kWeight] = Math.min(this[kMaxWeightPerServer], pool[kWeight] + this[kErrorPenalty])
})

pool.on('connectionError', () => {
pool[kWeight] = Math.max(1, pool[kWeight] - this[kErrorPenalty])
this._updateBalancedPoolStats()
})

pool.on('disconnect', (...args) => {
const err = args[2]
if (err && err.code === 'UND_ERR_SOCKET') {
// decrease the weight of the pool.
pool[kWeight] = Math.max(1, pool[kWeight] - this[kErrorPenalty])
this._updateBalancedPoolStats()
}
})

for (const client of this[kClients]) {
client[kWeight] = this[kMaxWeightPerServer]
}

this[kAddClient](this[kFactory](upstreamOrigin, Object.assign({}, this[kOptions])))
this._updateBalancedPoolStats()

return this
}

_updateBalancedPoolStats () {
this[kGreatestCommonDivisor] = this[kClients].map(p => p[kWeight]).reduce(getGreatestCommonDivisor, 0)
}

removeUpstream (upstream) {
const upstreamOrigin = parseOrigin(upstream).origin

Expand Down Expand Up @@ -100,10 +145,42 @@ class BalancedPool extends PoolBase {
return
}

this[kClients].splice(this[kClients].indexOf(dispatcher), 1)
this[kClients].push(dispatcher)
const allClientsBusy = this[kClients].map(pool => pool[kNeedDrain]).reduce((a, b) => a && b, true)

if (allClientsBusy) {
return
}

let counter = 0

let maxWeightIndex = this[kClients].findIndex(pool => !pool[kNeedDrain])

while (counter++ < this[kClients].length) {
this[kIndex] = (this[kIndex] + 1) % this[kClients].length
const pool = this[kClients][this[kIndex]]

// find pool index with the largest weight
if (pool[kWeight] > this[kClients][maxWeightIndex][kWeight] && !pool[kNeedDrain]) {
maxWeightIndex = this[kIndex]
}

// decrease the current weight every `this[kClients].length`.
if (this[kIndex] === 0) {
// Set the current weight to the next lower weight.
this[kCurrentWeight] = this[kCurrentWeight] - this[kGreatestCommonDivisor]

if (this[kCurrentWeight] <= 0) {
this[kCurrentWeight] = this[kMaxWeightPerServer]
}
}
if (pool[kWeight] >= this[kCurrentWeight] && (!pool[kNeedDrain])) {
return pool
}
}

return dispatcher
this[kCurrentWeight] = this[kClients][maxWeightIndex][kWeight]
this[kIndex] = maxWeightIndex
return this[kClients][maxWeightIndex]
}
}

Expand Down
29 changes: 29 additions & 0 deletions deps/undici/src/lib/core/request.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,27 @@ const {
const assert = require('assert')
const util = require('./util')

// tokenRegExp and headerCharRegex have been lifted from
// https://github.com/nodejs/node/blob/main/lib/_http_common.js

/**
* Verifies that the given val is a valid HTTP token
* per the rules defined in RFC 7230
* See https://tools.ietf.org/html/rfc7230#section-3.2.6
*/
const tokenRegExp = /^[\^_`a-zA-Z\-0-9!#$%&'*+.|~]+$/

/**
* Matches if val contains an invalid field-vchar
* field-value = *( field-content / obs-fold )
* field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ]
* field-vchar = VCHAR / obs-text
*/
const headerCharRegex = /[^\t\x20-\x7e\x80-\xff]/

// Verifies that a given path is valid does not contain control chars \x00 to \x20
const invalidPathRegex = /[^\u0021-\u00ff]/

const kHandler = Symbol('handler')

const channels = {}
Expand Down Expand Up @@ -54,10 +75,14 @@ class Request {
method !== 'CONNECT'
) {
throw new InvalidArgumentError('path must be an absolute URL or start with a slash')
} else if (invalidPathRegex.exec(path) !== null) {
throw new InvalidArgumentError('invalid request path')
}

if (typeof method !== 'string') {
throw new InvalidArgumentError('method must be a string')
} else if (tokenRegExp.exec(method) === null) {
throw new InvalidArgumentError('invalid request method')
}

if (upgrade && typeof upgrade !== 'string') {
Expand Down Expand Up @@ -301,6 +326,10 @@ function processHeader (request, key, val) {
key.toLowerCase() === 'expect'
) {
throw new NotSupportedError('expect header not supported')
} else if (tokenRegExp.exec(key) === null) {
throw new InvalidArgumentError('invalid header key')
} else if (headerCharRegex.exec(val) !== null) {
throw new InvalidArgumentError(`invalid ${key} header`)
} else {
request.headers += `${key}: ${val}\r\n`
}
Expand Down
16 changes: 16 additions & 0 deletions deps/undici/src/lib/fetch/body.js
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,10 @@ function bodyMixinMethods (instance) {
const chunks = []

for await (const chunk of consumeBody(this[kState].body)) {
if (!isUint8Array(chunk)) {
throw new TypeError('Expected Uint8Array chunk')
}

// Assemble one final large blob with Uint8Array's can exhaust memory.
// That's why we create create multiple blob's and using references
chunks.push(new Blob([chunk]))
Expand All @@ -314,6 +318,10 @@ function bodyMixinMethods (instance) {
let offset = 0

for await (const chunk of consumeBody(this[kState].body)) {
if (!isUint8Array(chunk)) {
throw new TypeError('Expected Uint8Array chunk')
}

buffer.set(chunk, offset)
offset += chunk.length
}
Expand All @@ -331,6 +339,10 @@ function bodyMixinMethods (instance) {
let size = 0

for await (const chunk of consumeBody(this[kState].body)) {
if (!isUint8Array(chunk)) {
throw new TypeError('Expected Uint8Array chunk')
}

chunks.push(chunk)
size += chunk.byteLength
}
Expand All @@ -355,6 +367,10 @@ function bodyMixinMethods (instance) {
const textDecoder = new TextDecoder()

for await (const chunk of consumeBody(this[kState].body)) {
if (!isUint8Array(chunk)) {
throw new TypeError('Expected Uint8Array chunk')
}

result += textDecoder.decode(chunk, { stream: true })
}

Expand Down
3 changes: 2 additions & 1 deletion deps/undici/src/lib/handler/redirect.js
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,8 @@ function shouldRemoveHeader (header, removeContent, unknownOrigin) {
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 === 13 && header.toString().toLowerCase() === 'authorization') ||
(unknownOrigin && header.length === 6 && header.toString().toLowerCase() === 'cookie')
)
}

Expand Down
3 changes: 2 additions & 1 deletion deps/undici/src/lib/mock/mock-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const {
kOrigin,
kGetNetConnect
} = require('./mock-symbols')
const { buildURL } = require('../core/util')
const { buildURL, nop } = require('../core/util')

function matchValue (match, value) {
if (typeof match === 'string') {
Expand Down Expand Up @@ -288,6 +288,7 @@ function mockDispatch (opts, handler) {
const responseHeaders = generateKeyValues(headers)
const responseTrailers = generateKeyValues(trailers)

handler.abort = nop
handler.onHeaders(statusCode, responseHeaders, resume, getStatusText(statusCode))
handler.onData(Buffer.from(responseData))
handler.onComplete(responseTrailers)
Expand Down
2 changes: 1 addition & 1 deletion deps/undici/src/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "undici",
"version": "5.7.0",
"version": "5.8.0",
"description": "An HTTP/1.1 client, written from scratch for Node.js",
"homepage": "https://undici.nodejs.org",
"bugs": {
Expand Down
25 changes: 24 additions & 1 deletion deps/undici/undici.js
Original file line number Diff line number Diff line change
Expand Up @@ -2227,6 +2227,9 @@ Content-Type: ${value.type || "application/octet-stream"}\r
}
const chunks = [];
for await (const chunk of consumeBody(this[kState].body)) {
if (!isUint8Array(chunk)) {
throw new TypeError("Expected Uint8Array chunk");
}
chunks.push(new Blob([chunk]));
}
return new Blob(chunks, { type: this.headers.get("Content-Type") || "" });
Expand All @@ -2241,6 +2244,9 @@ Content-Type: ${value.type || "application/octet-stream"}\r
const buffer2 = new Uint8Array(contentLength);
let offset2 = 0;
for await (const chunk of consumeBody(this[kState].body)) {
if (!isUint8Array(chunk)) {
throw new TypeError("Expected Uint8Array chunk");
}
buffer2.set(chunk, offset2);
offset2 += chunk.length;
}
Expand All @@ -2249,6 +2255,9 @@ Content-Type: ${value.type || "application/octet-stream"}\r
const chunks = [];
let size = 0;
for await (const chunk of consumeBody(this[kState].body)) {
if (!isUint8Array(chunk)) {
throw new TypeError("Expected Uint8Array chunk");
}
chunks.push(chunk);
size += chunk.byteLength;
}
Expand All @@ -2267,6 +2276,9 @@ Content-Type: ${value.type || "application/octet-stream"}\r
let result = "";
const textDecoder = new TextDecoder();
for await (const chunk of consumeBody(this[kState].body)) {
if (!isUint8Array(chunk)) {
throw new TypeError("Expected Uint8Array chunk");
}
result += textDecoder.decode(chunk, { stream: true });
}
result += textDecoder.decode();
Expand Down Expand Up @@ -2350,6 +2362,9 @@ var require_request = __commonJS({
} = require_errors();
var assert = require("assert");
var util = require_util();
var tokenRegExp = /^[\^_`a-zA-Z\-0-9!#$%&'*+.|~]+$/;
var headerCharRegex = /[^\t\x20-\x7e\x80-\xff]/;
var invalidPathRegex = /[^\u0021-\u00ff]/;
var kHandler = Symbol("handler");
var channels = {};
var extractBody;
Expand Down Expand Up @@ -2388,9 +2403,13 @@ var require_request = __commonJS({
throw new InvalidArgumentError("path must be a string");
} else if (path[0] !== "/" && !(path.startsWith("http://") || path.startsWith("https://")) && method !== "CONNECT") {
throw new InvalidArgumentError("path must be an absolute URL or start with a slash");
} else if (invalidPathRegex.exec(path) !== null) {
throw new InvalidArgumentError("invalid request path");
}
if (typeof method !== "string") {
throw new InvalidArgumentError("method must be a string");
} else if (tokenRegExp.exec(method) === null) {
throw new InvalidArgumentError("invalid request method");
}
if (upgrade && typeof upgrade !== "string") {
throw new InvalidArgumentError("upgrade must be a string");
Expand Down Expand Up @@ -2562,6 +2581,10 @@ var require_request = __commonJS({
throw new InvalidArgumentError("invalid upgrade header");
} else if (key.length === 6 && key.toLowerCase() === "expect") {
throw new NotSupportedError("expect header not supported");
} else if (tokenRegExp.exec(key) === null) {
throw new InvalidArgumentError("invalid header key");
} else if (headerCharRegex.exec(val) !== null) {
throw new InvalidArgumentError(`invalid ${key} header`);
} else {
request.headers += `${key}: ${val}\r
`;
Expand Down Expand Up @@ -2685,7 +2708,7 @@ var require_redirect = __commonJS({
}
}
function shouldRemoveHeader(header, removeContent, unknownOrigin) {
return header.length === 4 && header.toString().toLowerCase() === "host" || removeContent && header.toString().toLowerCase().indexOf("content-") === 0 || unknownOrigin && header.length === 13 && header.toString().toLowerCase() === "authorization";
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";
}
function cleanRequestHeaders(headers, removeContent, unknownOrigin) {
const ret = [];
Expand Down

0 comments on commit aac97c2

Please sign in to comment.