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.5.1 #43412

Merged
merged 1 commit into from
Jun 14, 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
27 changes: 11 additions & 16 deletions deps/undici/src/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -185,13 +185,12 @@ Help us improve the test coverage by following instructions at [nodejs/undici/#9
Basic usage example:

```js
import {fetch} from 'undici';
import { fetch } from 'undici';

async function fetchJson() {
const res = await fetch('https://example.com')
const json = await res.json()
console.log(json);
}

const res = await fetch('https://example.com')
const json = await res.json()
console.log(json);
```

You can pass an optional dispatcher to `fetch` as:
Expand Down Expand Up @@ -235,24 +234,20 @@ const data = {
},
};

(async () => {
await fetch("https://example.com", { body: data, method: 'POST' });
})();
await fetch("https://example.com", { body: data, method: 'POST' });
```

#### `response.body`

Nodejs has two kinds of streams: [web streams](https://nodejs.org/dist/latest-v16.x/docs/api/webstreams.html), which follow the API of the WHATWG web standard found in browsers, and an older Node-specific [streams API](https://nodejs.org/api/stream.html). `response.body` returns a readable web stream. If you would prefer to work with a Node stream you can convert a web stream using `.fromWeb()`.

```js
import {fetch} from 'undici';
import {Readable} from 'node:stream';
import { fetch } from 'undici';
import { Readable } from 'node:stream';

async function fetchStream() {
const response = await fetch('https://example.com')
const readableWebStream = response.body;
const readableNodeStream = Readable.fromWeb(readableWebStream);
}
const response = await fetch('https://example.com')
const readableWebStream = response.body;
const readableNodeStream = Readable.fromWeb(readableWebStream);
```

#### Specification Compliance
Expand Down
22 changes: 19 additions & 3 deletions deps/undici/src/lib/api/api-connect.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ class ConnectHandler extends AsyncResource {
throw new InvalidArgumentError('invalid callback')
}

const { signal, opaque, responseHeaders } = opts
const { signal, opaque, responseHeaders, httpTunnel } = opts

if (signal && typeof signal.on !== 'function' && typeof signal.addEventListener !== 'function') {
throw new InvalidArgumentError('signal must be an EventEmitter or EventTarget')
Expand All @@ -27,6 +27,7 @@ class ConnectHandler extends AsyncResource {
this.responseHeaders = responseHeaders || null
this.callback = callback
this.abort = null
this.httpTunnel = httpTunnel

addSignal(this, signal)
}
Expand All @@ -40,8 +41,23 @@ class ConnectHandler extends AsyncResource {
this.context = context
}

onHeaders () {
throw new SocketError('bad connect', null)
onHeaders (statusCode) {
// when httpTunnel headers are allowed
if (this.httpTunnel) {
const { callback, opaque } = this
if (statusCode !== 200) {
if (callback) {
this.callback = null
const err = new RequestAbortedError('Proxy response !== 200 when HTTP Tunneling')
queueMicrotask(() => {
this.runInAsyncScope(callback, null, err, { opaque })
})
}
return 1
}
} else {
throw new SocketError('bad connect', null)
}
}

onUpgrade (statusCode, rawHeaders, socket) {
Expand Down
4 changes: 3 additions & 1 deletion deps/undici/src/lib/core/connect.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ function buildConnector ({ maxCachedSessions, socketPath, timeout, ...opts }) {
timeout = timeout == null ? 10e3 : timeout
maxCachedSessions = maxCachedSessions == null ? 100 : maxCachedSessions

return function connect ({ hostname, host, protocol, port, servername }, callback) {
return function connect ({ hostname, host, protocol, port, servername, httpSocket }, callback) {
let socket
if (protocol === 'https:') {
if (!tls) {
Expand All @@ -39,6 +39,7 @@ function buildConnector ({ maxCachedSessions, socketPath, timeout, ...opts }) {
...options,
servername,
session,
socket: httpSocket, // upgrade socket connection
port: port || 443,
host: hostname
})
Expand All @@ -65,6 +66,7 @@ function buildConnector ({ maxCachedSessions, socketPath, timeout, ...opts }) {
}
})
} else {
assert(!httpSocket, 'httpSocket can only be sent on TLS update')
socket = net.connect({
highWaterMark: 64 * 1024, // Same as nodejs fs streams.
...options,
Expand Down
15 changes: 9 additions & 6 deletions deps/undici/src/lib/core/request.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,11 @@ class Request {
}, handler) {
if (typeof path !== 'string') {
throw new InvalidArgumentError('path must be a string')
} else if (path[0] !== '/' && !(path.startsWith('http://') || path.startsWith('https://'))) {
} 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')
}

Expand Down Expand Up @@ -80,13 +84,12 @@ class Request {
this.body = null
} else if (util.isStream(body)) {
this.body = body
} else if (body instanceof DataView) {
// TODO: Why is DataView special?
this.body = body.buffer.byteLength ? Buffer.from(body.buffer) : null
} else if (body instanceof ArrayBuffer || ArrayBuffer.isView(body)) {
this.body = body.byteLength ? Buffer.from(body) : null
} else if (util.isBuffer(body)) {
this.body = body.byteLength ? body : null
} else if (ArrayBuffer.isView(body)) {
this.body = body.buffer.byteLength ? Buffer.from(body.buffer, body.byteOffset, body.byteLength) : null
} else if (body instanceof ArrayBuffer) {
this.body = body.byteLength ? Buffer.from(body) : null
} else if (typeof body === 'string') {
this.body = body.length ? Buffer.from(body) : null
} else if (util.isFormDataLike(body) || util.isIterable(body) || util.isBlobLike(body)) {
Expand Down
4 changes: 2 additions & 2 deletions deps/undici/src/lib/fetch/body.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const { kBodyUsed } = require('../core/symbols')
const assert = require('assert')
const { NotSupportedError } = require('../core/errors')
const { isErrored } = require('../core/util')
const { isUint8Array } = require('util/types')
const { isUint8Array, isArrayBuffer } = require('util/types')

let ReadableStream

Expand Down Expand Up @@ -61,7 +61,7 @@ function extractBody (object, keepalive = false) {

// Set Content-Type to `application/x-www-form-urlencoded;charset=UTF-8`.
contentType = 'application/x-www-form-urlencoded;charset=UTF-8'
} else if (object instanceof ArrayBuffer || ArrayBuffer.isView(object)) {
} else if (isArrayBuffer(object) || ArrayBuffer.isView(object)) {
// BufferSource

if (object instanceof DataView) {
Expand Down
71 changes: 54 additions & 17 deletions deps/undici/src/lib/fetch/formdata.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use strict'

const { isBlobLike, isFileLike, toUSVString } = require('./util')
const { isBlobLike, isFileLike, toUSVString, makeIterator } = require('./util')
const { kState } = require('./symbols')
const { File, FileLike } = require('./file')
const { Blob } = require('buffer')
Expand Down Expand Up @@ -187,45 +187,68 @@ class FormData {
return this.constructor.name
}

* entries () {
entries () {
if (!(this instanceof FormData)) {
throw new TypeError('Illegal invocation')
}

for (const pair of this) {
yield pair
}
return makeIterator(
makeIterable(this[kState], 'entries'),
'FormData'
)
}

* keys () {
keys () {
if (!(this instanceof FormData)) {
throw new TypeError('Illegal invocation')
}

for (const [key] of this) {
yield key
return makeIterator(
makeIterable(this[kState], 'keys'),
'FormData'
)
}

values () {
if (!(this instanceof FormData)) {
throw new TypeError('Illegal invocation')
}

return makeIterator(
makeIterable(this[kState], 'values'),
'FormData'
)
}

* values () {
/**
* @param {(value: string, key: string, self: FormData) => void} callbackFn
* @param {unknown} thisArg
*/
forEach (callbackFn, thisArg = globalThis) {
if (!(this instanceof FormData)) {
throw new TypeError('Illegal invocation')
}

for (const [, value] of this) {
yield value
if (arguments.length < 1) {
throw new TypeError(
`Failed to execute 'forEach' on 'FormData': 1 argument required, but only ${arguments.length} present.`
)
}
}

* [Symbol.iterator] () {
// The value pairs to iterate over are this’s entry list’s entries with
// the key being the name and the value being the value.
for (const { name, value } of this[kState]) {
yield [name, value]
if (typeof callbackFn !== 'function') {
throw new TypeError(
"Failed to execute 'forEach' on 'FormData': parameter 1 is not of type 'Function'."
)
}

for (const [key, value] of this) {
callbackFn.apply(thisArg, [value, key, this])
}
}
}

FormData.prototype[Symbol.iterator] = FormData.prototype.entries

function makeEntry (name, value, filename) {
// To create an entry for name, value, and optionally a filename, run these
// steps:
Expand Down Expand Up @@ -267,4 +290,18 @@ function makeEntry (name, value, filename) {
return entry
}

function * makeIterable (entries, type) {
// The value pairs to iterate over are this’s entry list’s entries
// with the key being the name and the value being the value.
for (const { name, value } of entries) {
if (type === 'entries') {
yield [name, value]
} else if (type === 'values') {
yield value
} else {
yield name
}
}
}

module.exports = { FormData }
34 changes: 4 additions & 30 deletions deps/undici/src/lib/fetch/headers.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const { validateHeaderName, validateHeaderValue } = require('http')
const { kHeadersList } = require('../core/symbols')
const { kGuard } = require('./symbols')
const { kEnumerableProperty } = require('../core/util')
const { makeIterator } = require('./util')

const kHeadersMap = Symbol('headers map')
const kHeadersSortedMap = Symbol('headers map sorted')
Expand Down Expand Up @@ -73,33 +74,6 @@ function fill (headers, object) {
}
}

// https://tc39.es/ecma262/#sec-%25iteratorprototype%25-object
const esIteratorPrototype = Object.getPrototypeOf(Object.getPrototypeOf([][Symbol.iterator]()))

// https://webidl.spec.whatwg.org/#dfn-iterator-prototype-object
function makeHeadersIterator (iterator) {
const i = {
next () {
if (Object.getPrototypeOf(this) !== i) {
throw new TypeError(
'\'next\' called on an object that does not implement interface Headers Iterator.'
)
}

return iterator.next()
},
// The class string of an iterator prototype object for a given interface is the
// result of concatenating the identifier of the interface and the string " Iterator".
[Symbol.toStringTag]: 'Headers Iterator'
}

// The [[Prototype]] internal slot of an iterator prototype object must be %IteratorPrototype%.
Object.setPrototypeOf(i, esIteratorPrototype)
// esIteratorPrototype needs to be the prototype of i
// which is the prototype of an empty object. Yes, it's confusing.
return Object.setPrototypeOf({}, i)
}

class HeadersList {
constructor (init) {
if (init instanceof HeadersList) {
Expand Down Expand Up @@ -306,23 +280,23 @@ class Headers {
throw new TypeError('Illegal invocation')
}

return makeHeadersIterator(this[kHeadersSortedMap].keys())
return makeIterator(this[kHeadersSortedMap].keys(), 'Headers')
}

values () {
if (!(this instanceof Headers)) {
throw new TypeError('Illegal invocation')
}

return makeHeadersIterator(this[kHeadersSortedMap].values())
return makeIterator(this[kHeadersSortedMap].values(), 'Headers')
}

entries () {
if (!(this instanceof Headers)) {
throw new TypeError('Illegal invocation')
}

return makeHeadersIterator(this[kHeadersSortedMap].entries())
return makeIterator(this[kHeadersSortedMap].entries(), 'Headers')
}

/**
Expand Down
2 changes: 1 addition & 1 deletion deps/undici/src/lib/fetch/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -1164,7 +1164,7 @@ async function httpRedirectFetch (fetchParams, response) {
if (
([301, 302].includes(actualResponse.status) && request.method === 'POST') ||
(actualResponse.status === 303 &&
!['GET', 'HEADER'].includes(request.method))
!['GET', 'HEAD'].includes(request.method))
) {
// then:
// 1. Set request’s method to `GET` and request’s body to null.
Expand Down
30 changes: 29 additions & 1 deletion deps/undici/src/lib/fetch/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,33 @@ function serializeJavascriptValueToJSONString (value) {
return result
}

// https://tc39.es/ecma262/#sec-%25iteratorprototype%25-object
const esIteratorPrototype = Object.getPrototypeOf(Object.getPrototypeOf([][Symbol.iterator]()))

// https://webidl.spec.whatwg.org/#dfn-iterator-prototype-object
function makeIterator (iterator, name) {
const i = {
next () {
if (Object.getPrototypeOf(this) !== i) {
throw new TypeError(
`'next' called on an object that does not implement interface ${name} Iterator.`
)
}

return iterator.next()
},
// The class string of an iterator prototype object for a given interface is the
// result of concatenating the identifier of the interface and the string " Iterator".
[Symbol.toStringTag]: `${name} Iterator`
}

// The [[Prototype]] internal slot of an iterator prototype object must be %IteratorPrototype%.
Object.setPrototypeOf(i, esIteratorPrototype)
// esIteratorPrototype needs to be the prototype of i
// which is the prototype of an empty object. Yes, it's confusing.
return Object.setPrototypeOf({}, i)
}

module.exports = {
isAborted,
isCancelled,
Expand Down Expand Up @@ -390,5 +417,6 @@ module.exports = {
isValidReasonPhrase,
sameOrigin,
normalizeMethod,
serializeJavascriptValueToJSONString
serializeJavascriptValueToJSONString,
makeIterator
}
Loading