Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: nodejs/undici
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v5.7.0
Choose a base ref
...
head repository: nodejs/undici
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: v5.8.0
Choose a head ref
  • 11 commits
  • 16 files changed
  • 5 contributors

Commits on Jul 12, 2022

  1. Bumped v5.7.0

    Signed-off-by: Matteo Collina <hello@matteocollina.com>
    mcollina committed Jul 12, 2022
    1

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    e7d3bf3 View commit details

Commits on Jul 13, 2022

  1. Copy the full SHA
    93e31a2 View commit details
  2. Copy the full SHA
    5b57e8c View commit details

Commits on Jul 14, 2022

  1. feat: use weighted round robin in balancedPool (#1069)

    * fixes
    
    * more fixes
    
    * add test
    
    * remove console.log
    
    * rename startingWeightPerServer to maxWeightPerServer
    
    * add another test
    jodevsa authored Jul 14, 2022
    Copy the full SHA
    99205ec View commit details
  2. ci: fix up exclude (#1544)

    Unfortunately, shared worklfows can't take objects as params - this needs to be a string...
    dominykas authored Jul 14, 2022
    Copy the full SHA
    22e2f39 View commit details
  3. Copy the full SHA
    6c9e634 View commit details

Commits on Jul 16, 2022

  1. Copy the full SHA
    b6af4e6 View commit details

Commits on Jul 18, 2022

  1. Copy the full SHA
    722976c View commit details
  2. Merge pull request from GHSA-3cvr-822r-rqcc

    * Sanitize \r\n in headers
    
    Signed-off-by: Matteo Collina <hello@matteocollina.com>
    
    * fixup, use regexp
    
    Signed-off-by: Matteo Collina <hello@matteocollina.com>
    
    * fixup, handle method and path too
    
    Signed-off-by: Matteo Collina <hello@matteocollina.com>
    mcollina authored Jul 18, 2022
    Copy the full SHA
    a29a151 View commit details
  3. Merge pull request from GHSA-q768-x9m6-m9qp

    Signed-off-by: Matteo Collina <hello@matteocollina.com>
    mcollina authored Jul 18, 2022
    Copy the full SHA
    0a5bee9 View commit details
  4. Bumped v5.8.0

    Signed-off-by: Matteo Collina <hello@matteocollina.com>
    mcollina committed Jul 18, 2022
    Copy the full SHA
    26f60b7 View commit details
21 changes: 0 additions & 21 deletions .github/workflows/lint-pr.yml

This file was deleted.

3 changes: 3 additions & 0 deletions .github/workflows/nodejs.yml
Original file line number Diff line number Diff line change
@@ -20,6 +20,9 @@ jobs:
include: |
- runs-on: ubuntu-latest
node-version: 16.8
exclude: |
- runs-on: windows-latest
node-version: 16
automerge:
if: >
github.event_name == 'pull_request' &&
15 changes: 8 additions & 7 deletions docs/best-practices/proxy.md
Original file line number Diff line number Diff line change
@@ -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'
@@ -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 () {
@@ -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')}`)
}

@@ -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 () {
@@ -124,3 +124,4 @@ function buildProxy () {
})
}
```

85 changes: 81 additions & 4 deletions lib/balanced-pool.js
Original file line number Diff line number Diff line change
@@ -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)
@@ -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]
@@ -42,6 +58,7 @@ class BalancedPool extends PoolBase {
for (const upstream of upstreams) {
this.addUpstream(upstream)
}
this._updateBalancedPoolStats()
}

addUpstream (upstream) {
@@ -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

@@ -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]
}
}

29 changes: 29 additions & 0 deletions lib/core/request.js
Original file line number Diff line number Diff line change
@@ -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 = {}
@@ -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') {
@@ -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`
}
16 changes: 16 additions & 0 deletions lib/fetch/body.js
Original file line number Diff line number Diff line change
@@ -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]))
@@ -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
}
@@ -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
}
@@ -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 })
}

3 changes: 2 additions & 1 deletion lib/handler/redirect.js
Original file line number Diff line number Diff line change
@@ -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')
)
}

3 changes: 2 additions & 1 deletion lib/mock/mock-utils.js
Original file line number Diff line number Diff line change
@@ -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') {
@@ -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)
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "undici",
"version": "5.6.1",
"version": "5.8.0",
"description": "An HTTP/1.1 client, written from scratch for Node.js",
"homepage": "https://undici.nodejs.org",
"bugs": {
Loading