Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

deps: update undici to 5.8.0 #43886

Merged
merged 1 commit into from
Jul 18, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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