Skip to content

Commit

Permalink
deps: update undici to 5.3.0
Browse files Browse the repository at this point in the history
PR-URL: #43197
Reviewed-By: Darshan Sen <raisinten@gmail.com>
Reviewed-By: Robert Nagy <ronagy@icloud.com>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Mohammed Keyvanzadeh <mohammadkeyvanzade94@gmail.com>
Reviewed-By: Tobias Nießen <tniessen@tnie.de>
Reviewed-By: Luigi Pinca <luigipinca@gmail.com>
  • Loading branch information
nodejs-github-bot committed May 24, 2022
1 parent 4a3ba87 commit 810893f
Show file tree
Hide file tree
Showing 22 changed files with 487 additions and 284 deletions.
3 changes: 2 additions & 1 deletion deps/undici/src/README.md
Expand Up @@ -198,7 +198,7 @@ You can pass an optional dispatcher to `fetch` as:

```js
import { fetch, Agent } from 'undici'

const res = await fetch('https://example.com', {
// Mocks are also supported
dispatcher: new Agent({
Expand Down Expand Up @@ -375,6 +375,7 @@ Refs: https://fetch.spec.whatwg.org/#atomic-http-redirect-handling
* [__Daniele Belardi__](https://github.com/dnlup), <https://www.npmjs.com/~dnlup>
* [__Ethan Arrowood__](https://github.com/ethan-arrowood), <https://www.npmjs.com/~ethan_arrowood>
* [__Matteo Collina__](https://github.com/mcollina), <https://www.npmjs.com/~matteo.collina>
* [__Matthew Aitken__](https://github.com/KhafraDev), <https://www.npmjs.com/~khaf>
* [__Robert Nagy__](https://github.com/ronag), <https://www.npmjs.com/~ronag>
* [__Szymon Marczak__](https://github.com/szmarczak), <https://www.npmjs.com/~szmarczak>
* [__Tomas Della Vedova__](https://github.com/delvedor), <https://www.npmjs.com/~delvedor>
Expand Down
4 changes: 3 additions & 1 deletion deps/undici/src/docs/api/Dispatcher.md
Expand Up @@ -194,18 +194,20 @@ Returns: `Boolean` - `false` if dispatcher is busy and further dispatch calls wo
* **method** `string`
* **body** `string | Buffer | Uint8Array | stream.Readable | Iterable | AsyncIterable | null` (optional) - Default: `null`
* **headers** `UndiciHeaders | string[]` (optional) - Default: `null`.
* **query** `Record<string, any> | null` (optional) - Default: `null` - Query string params to be embedded in the request URL. Note that both keys and values of query are encoded using `encodeURIComponent`. If for some reason you need to send them unencoded, embed query params into path directly instead.
* **idempotent** `boolean` (optional) - Default: `true` if `method` is `'HEAD'` or `'GET'` - Whether the requests can be safely retried or not. If `false` the request won't be sent until all preceding requests in the pipeline has completed.
* **blocking** `boolean` (optional) - Default: `false` - Whether the response is expected to take a long time and would end up blocking the pipeline. When this is set to `true` further pipelining will be avoided on the same connection until headers have been received.
* **upgrade** `string | null` (optional) - Default: `null` - Upgrade the request. Should be used to specify the kind of upgrade i.e. `'Websocket'`.
* **bodyTimeout** `number | null` (optional) - The timeout after which a request will time out, in milliseconds. Monitors time between receiving body data. Use `0` to disable it entirely. Defaults to 30 seconds.
* **headersTimeout** `number | null` (optional) - The amount of time the parser will wait to receive the complete HTTP headers. Defaults to 30 seconds.
* **throwOnError** `boolean` (optional) - Default: `false` - Whether Undici should throw an error upon receiving a 4xx or 5xx response from the server.

#### Parameter: `DispatchHandler`

* **onConnect** `(abort: () => void, context: object) => void` - Invoked before request is dispatched on socket. May be invoked multiple times when a request is retried when the request at the head of the pipeline fails.
* **onError** `(error: Error) => void` - Invoked when an error has occurred. May not throw.
* **onUpgrade** `(statusCode: number, headers: Buffer[], socket: Duplex) => void` (optional) - Invoked when request is upgraded. Required if `DispatchOptions.upgrade` is defined or `DispatchOptions.method === 'CONNECT'`.
* **onHeaders** `(statusCode: number, headers: Buffer[], resume: () => void) => boolean` - Invoked when statusCode and headers have been received. May be invoked multiple times due to 1xx informational headers. Not required for `upgrade` requests.
* **onHeaders** `(statusCode: number, headers: Buffer[], resume: () => void, statusText: string) => boolean` - Invoked when statusCode and headers have been received. May be invoked multiple times due to 1xx informational headers. Not required for `upgrade` requests.
* **onData** `(chunk: Buffer) => boolean` - Invoked when response payload data is received. Not required for `upgrade` requests.
* **onComplete** `(trailers: Buffer[]) => void` - Invoked when response payload and trailers have been received and the request has completed. Not required for `upgrade` requests.
* **onBodySent** `(chunk: string | Buffer | Uint8Array) => void` - Invoked when a body chunk is sent to the server. Not required. For a stream or iterable body this will be invoked for every chunk. For other body types, it will be invoked once after the body is sent.
Expand Down
32 changes: 32 additions & 0 deletions deps/undici/src/docs/best-practices/mocking-request.md
Expand Up @@ -101,4 +101,36 @@ const badRequest = await bankTransfer('1234567890', '100')
// subsequent request to origin http://localhost:3000 was not allowed (net.connect disabled)
```

## Reply with data based on request

If the mocked response needs to be dynamically derived from the request parameters, you can provide a function instead of an object to `reply`

```js
mockPool.intercept({
path: '/bank-transfer',
method: 'POST',
headers: {
'X-TOKEN-SECRET': 'SuperSecretToken',
},
body: JSON.stringify({
recepient: '1234567890',
amount: '100'
})
}).reply(200, (opts) => {
// do something with opts

return { message: 'transaction processed' }
})
```

in this case opts will be

```
{
method: 'POST',
headers: { 'X-TOKEN-SECRET': 'SuperSecretToken' },
body: '{"recepient":"1234567890","amount":"100"}',
origin: 'http://localhost:3000',
path: '/bank-transfer'
}
```
1 change: 1 addition & 0 deletions deps/undici/src/index.d.ts
Expand Up @@ -16,6 +16,7 @@ import { request, pipeline, stream, connect, upgrade } from './types/api'
export * from './types/fetch'
export * from './types/file'
export * from './types/formdata'
export * from './types/diagnostics-channel'
export { Interceptable } from './types/mock-interceptor'

export { Dispatcher, BalancedPool, Pool, Client, buildConnector, errors, Agent, request, stream, pipeline, connect, upgrade, setGlobalDispatcher, getGlobalDispatcher, MockClient, MockPool, MockAgent, mockErrors, ProxyAgent }
Expand Down
15 changes: 12 additions & 3 deletions deps/undici/src/lib/api/api-request.js
Expand Up @@ -3,7 +3,8 @@
const Readable = require('./readable')
const {
InvalidArgumentError,
RequestAbortedError
RequestAbortedError,
ResponseStatusCodeError
} = require('../core/errors')
const util = require('../core/util')
const { AsyncResource } = require('async_hooks')
Expand All @@ -15,7 +16,7 @@ class RequestHandler extends AsyncResource {
throw new InvalidArgumentError('invalid opts')
}

const { signal, method, opaque, body, onInfo, responseHeaders } = opts
const { signal, method, opaque, body, onInfo, responseHeaders, throwOnError } = opts

try {
if (typeof callback !== 'function') {
Expand Down Expand Up @@ -51,6 +52,7 @@ class RequestHandler extends AsyncResource {
this.trailers = {}
this.context = null
this.onInfo = onInfo || null
this.throwOnError = throwOnError

if (util.isStream(body)) {
body.on('error', (err) => {
Expand All @@ -70,7 +72,7 @@ class RequestHandler extends AsyncResource {
this.context = context
}

onHeaders (statusCode, rawHeaders, resume) {
onHeaders (statusCode, rawHeaders, resume, statusMessage) {
const { callback, opaque, abort, context } = this

if (statusCode < 200) {
Expand All @@ -89,6 +91,13 @@ class RequestHandler extends AsyncResource {
const headers = this.responseHeaders === 'raw' ? util.parseRawHeaders(rawHeaders) : util.parseHeaders(rawHeaders)

if (callback !== null) {
if (this.throwOnError && statusCode >= 400) {
this.runInAsyncScope(callback, null,
new ResponseStatusCodeError(`Response status code ${statusCode}${statusMessage ? `: ${statusMessage}` : ''}`, statusCode, headers)
)
return
}

this.runInAsyncScope(callback, null, null, {
statusCode,
headers,
Expand Down
14 changes: 14 additions & 0 deletions deps/undici/src/lib/core/errors.js
Expand Up @@ -56,6 +56,19 @@ class BodyTimeoutError extends UndiciError {
}
}

class ResponseStatusCodeError extends UndiciError {
constructor (message, statusCode, headers) {
super(message)
Error.captureStackTrace(this, ResponseStatusCodeError)
this.name = 'ResponseStatusCodeError'
this.message = message || 'Response Status Code Error'
this.code = 'UND_ERR_RESPONSE_STATUS_CODE'
this.status = statusCode
this.statusCode = statusCode
this.headers = headers
}
}

class InvalidArgumentError extends UndiciError {
constructor (message) {
super(message)
Expand Down Expand Up @@ -186,6 +199,7 @@ module.exports = {
BodyTimeoutError,
RequestContentLengthMismatchError,
ConnectTimeoutError,
ResponseStatusCodeError,
InvalidArgumentError,
InvalidReturnValueError,
RequestAbortedError,
Expand Down
10 changes: 7 additions & 3 deletions deps/undici/src/lib/core/request.js
Expand Up @@ -4,8 +4,8 @@ const {
InvalidArgumentError,
NotSupportedError
} = require('./errors')
const util = require('./util')
const assert = require('assert')
const util = require('./util')

const kHandler = Symbol('handler')

Expand Down Expand Up @@ -38,11 +38,13 @@ class Request {
method,
body,
headers,
query,
idempotent,
blocking,
upgrade,
headersTimeout,
bodyTimeout
bodyTimeout,
throwOnError
}, handler) {
if (typeof path !== 'string') {
throw new InvalidArgumentError('path must be a string')
Expand Down Expand Up @@ -70,6 +72,8 @@ class Request {

this.bodyTimeout = bodyTimeout

this.throwOnError = throwOnError === true

this.method = method

if (body == null) {
Expand Down Expand Up @@ -97,7 +101,7 @@ class Request {

this.upgrade = upgrade || null

this.path = path
this.path = query ? util.buildURL(path, query) : path

this.origin = origin

Expand Down
48 changes: 47 additions & 1 deletion deps/undici/src/lib/core/util.js
Expand Up @@ -26,6 +26,51 @@ function isBlobLike (object) {
)
}

function isObject (val) {
return val !== null && typeof val === 'object'
}

// this escapes all non-uri friendly characters
function encode (val) {
return encodeURIComponent(val)
}

// based on https://github.com/axios/axios/blob/63e559fa609c40a0a460ae5d5a18c3470ffc6c9e/lib/helpers/buildURL.js (MIT license)
function buildURL (url, queryParams) {
if (url.includes('?') || url.includes('#')) {
throw new Error('Query params cannot be passed when url already contains "?" or "#".')
}
if (!isObject(queryParams)) {
throw new Error('Query params must be an object')
}

const parts = []
for (let [key, val] of Object.entries(queryParams)) {
if (val === null || typeof val === 'undefined') {
continue
}

if (!Array.isArray(val)) {
val = [val]
}

for (const v of val) {
if (isObject(v)) {
throw new Error('Passing object as a query param is not supported, please serialize to string up-front')
}
parts.push(encode(key) + '=' + encode(v))
}
}

const serializedParams = parts.join('&')

if (serializedParams) {
url += '?' + serializedParams
}

return url
}

function parseURL (url) {
if (typeof url === 'string') {
url = new URL(url)
Expand Down Expand Up @@ -357,5 +402,6 @@ module.exports = {
isBuffer,
validateHandler,
getSocketInfo,
isFormDataLike
isFormDataLike,
buildURL
}
8 changes: 0 additions & 8 deletions deps/undici/src/lib/fetch/file.js
Expand Up @@ -69,10 +69,6 @@ class File extends Blob {
}

get [Symbol.toStringTag] () {
if (!(this instanceof File)) {
throw new TypeError('Illegal invocation')
}

return this.constructor.name
}
}
Expand Down Expand Up @@ -190,10 +186,6 @@ class FileLike {
}

get [Symbol.toStringTag] () {
if (!(this instanceof FileLike)) {
throw new TypeError('Illegal invocation')
}

return 'File'
}
}
Expand Down
8 changes: 3 additions & 5 deletions deps/undici/src/lib/fetch/formdata.js
Expand Up @@ -6,6 +6,8 @@ const { File, FileLike } = require('./file')
const { Blob } = require('buffer')

class FormData {
static name = 'FormData'

constructor (...args) {
if (args.length > 0 && !(args[0]?.constructor?.name === 'HTMLFormElement')) {
throw new TypeError(
Expand Down Expand Up @@ -182,10 +184,6 @@ class FormData {
}

get [Symbol.toStringTag] () {
if (!(this instanceof FormData)) {
throw new TypeError('Illegal invocation')
}

return this.constructor.name
}

Expand Down Expand Up @@ -269,4 +267,4 @@ function makeEntry (name, value, filename) {
return entry
}

module.exports = { FormData: globalThis.FormData ?? FormData }
module.exports = { FormData }
18 changes: 3 additions & 15 deletions deps/undici/src/lib/fetch/index.js
Expand Up @@ -31,7 +31,6 @@ const {
coarsenedSharedCurrentTime,
createDeferredPromise,
isBlobLike,
CORBCheck,
sameOrigin,
isCancelled,
isAborted
Expand All @@ -52,7 +51,6 @@ const EE = require('events')
const { Readable, pipeline } = require('stream')
const { isErrored, isReadable } = require('../core/util')
const { dataURLProcessor } = require('./dataURL')
const { kIsMockActive } = require('../mock/mock-symbols')
const { TransformStream } = require('stream/web')

/** @type {import('buffer').resolveObjectURL} */
Expand Down Expand Up @@ -588,18 +586,8 @@ async function mainFetch (fetchParams, recursive = false) {
// 2. Set request’s response tainting to "opaque".
request.responseTainting = 'opaque'

// 3. Let noCorsResponse be the result of running scheme fetch given
// fetchParams.
const noCorsResponse = await schemeFetch(fetchParams)

// 4. If noCorsResponse is a filtered response or the CORB check with
// request and noCorsResponse returns allowed, then return noCorsResponse.
if (noCorsResponse.status === 0 || CORBCheck(request, noCorsResponse) === 'allowed') {
return noCorsResponse
}

// 5. Return a new response whose status is noCorsResponse’s status.
return makeResponse({ status: noCorsResponse.status })
// 3. Return the result of running scheme fetch given fetchParams.
return await schemeFetch(fetchParams)
}

// request’s current URL’s scheme is not an HTTP(S) scheme
Expand Down Expand Up @@ -1923,7 +1911,7 @@ async function httpNetworkFetch (
path: url.pathname + url.search,
origin: url.origin,
method: request.method,
body: fetchParams.controller.dispatcher[kIsMockActive] ? request.body && request.body.source : body,
body: fetchParams.controller.dispatcher.isMockActive ? request.body && request.body.source : body,
headers: [...request.headersList].flat(),
maxRedirections: 0,
bodyTimeout: 300_000,
Expand Down
4 changes: 0 additions & 4 deletions deps/undici/src/lib/fetch/request.js
Expand Up @@ -516,10 +516,6 @@ class Request {
}

get [Symbol.toStringTag] () {
if (!(this instanceof Request)) {
throw new TypeError('Illegal invocation')
}

return this.constructor.name
}

Expand Down

0 comments on commit 810893f

Please sign in to comment.