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.3.0 #43197

Merged
merged 1 commit into from
May 24, 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
3 changes: 2 additions & 1 deletion deps/undici/src/README.md
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Loading