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.12.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.13.0
Choose a head ref

Commits on Oct 28, 2022

  1. Copy the full SHA
    cfe8819 View commit details
  2. Copy the full SHA
    7854c65 View commit details
  3. Copy the full SHA
    9251008 View commit details
  4. Copy the full SHA
    d23f5dd View commit details
  5. Copy the full SHA
    bc1ee0c View commit details

Commits on Nov 2, 2022

  1. Copy the full SHA
    6e09ddb View commit details
  2. feat: implement 8.2 Set request’s referrer policy on redirect (#1717)

    * feat: implement 8.2 Set request’s referrer policy on redirect
    
    * feat: extend to double-check if a valid referrer policy
    
    * refactor: apply review suggestions
    
    * feat: add support for fallback policies
    metcoder95 authored Nov 2, 2022
    Copy the full SHA
    7827118 View commit details

Commits on Nov 3, 2022

  1. Copy the full SHA
    0a7ba72 View commit details

Commits on Nov 4, 2022

  1. Copy the full SHA
    8665962 View commit details

Commits on Nov 5, 2022

  1. Copy the full SHA
    744431a View commit details
  2. Copy the full SHA
    f91cd5e View commit details

Commits on Nov 7, 2022

  1. feat(MockInterceptor): allow async reply callbacks (#1758)

    * feat(MockInterceptor): allow async reply callbacks
    
    * fix: v12 syntax
    KhafraDev authored Nov 7, 2022
    Copy the full SHA
    7b4114d View commit details

Commits on Nov 8, 2022

  1. Copy the full SHA
    93fd794 View commit details

Commits on Nov 13, 2022

  1. Copy the full SHA
    a3ce745 View commit details

Commits on Nov 16, 2022

  1. Copy the full SHA
    b58e294 View commit details
  2. Copy the full SHA
    b041f9a View commit details

Commits on Nov 17, 2022

  1. Copy the full SHA
    c4803cf View commit details

Commits on Nov 18, 2022

  1. Copy the full SHA
    9311f32 View commit details
  2. wpt: add gzip-body.any.js (#1778)

    * wpt: add gzip-body.any.js
    
    * fix: try fixing windows ci failure
    KhafraDev authored Nov 18, 2022
    Copy the full SHA
    80700c6 View commit details

Commits on Nov 22, 2022

  1. Copy the full SHA
    93f150f View commit details

Commits on Nov 25, 2022

  1. Copy the full SHA
    7ffc264 View commit details
  2. Copy the full SHA
    d16327c View commit details
  3. chore(doc/fetch) update doc for fetch duplex (#1760) (#1765)

    * chore(doc/fetch) update doc for fetch duplex (#1760)
    
    * chore(doc/fetch) update doc for fetch duplex (#1760)
    zizifn authored Nov 25, 2022
    Copy the full SHA
    0ab830b View commit details
  4. Bumped v5.13.0

    Signed-off-by: Matteo Collina <hello@matteocollina.com>
    mcollina committed Nov 25, 2022
    Copy the full SHA
    b3447ab View commit details
Showing with 1,101 additions and 790 deletions.
  1. +1 −0 .gitignore
  2. +7 −5 README.md
  3. BIN docs/assets/lifecycle-diagram.png
  4. +23 −23 index.d.ts
  5. +18 −20 lib/fetch/body.js
  6. +19 −1 lib/fetch/constants.js
  7. +5 −70 lib/fetch/dataURL.js
  8. +16 −40 lib/fetch/file.js
  9. +25 −72 lib/fetch/formdata.js
  10. +32 −83 lib/fetch/headers.js
  11. +22 −26 lib/fetch/index.js
  12. +34 −112 lib/fetch/request.js
  13. +25 −53 lib/fetch/response.js
  14. +63 −21 lib/fetch/util.js
  15. +38 −54 lib/fetch/webidl.js
  16. +23 −77 lib/fileapi/filereader.js
  17. +3 −9 lib/fileapi/progressevent.js
  18. +22 −4 lib/mock/mock-utils.js
  19. +4 −4 package.json
  20. +14 −0 test/fetch/data-uri.js
  21. +30 −0 test/fetch/general.js
  22. +136 −0 test/fetch/util.js
  23. +5 −0 test/imports/undici-import.ts
  24. +61 −0 test/jest/issue-1757.test.js
  25. +76 −0 test/mock-agent.js
  26. +0 −22 test/node-fetch/main.js
  27. +4 −4 test/types/dispatcher.events.test-d.ts
  28. +2 −2 test/types/errors.test-d.ts
  29. +2 −2 test/types/formdata.test-d.ts
  30. +2 −2 test/types/interceptor.test-d.ts
  31. +1 −1 test/types/readable.test-d.ts
  32. +20 −1 test/wpt/server/server.mjs
  33. +16 −0 test/wpt/tests/fetch/content-encoding/gzip-body.any.js
  34. BIN test/wpt/tests/fetch/content-encoding/resources/foo.octetstream.gz
  35. +2 −0 test/wpt/tests/fetch/content-encoding/resources/foo.octetstream.gz.headers
  36. BIN test/wpt/tests/fetch/content-encoding/resources/foo.text.gz
  37. +2 −0 test/wpt/tests/fetch/content-encoding/resources/foo.text.gz.headers
  38. 0 test/wpt/tests/xhr/{ → formdata}/append.any.js
  39. 0 test/wpt/tests/xhr/{ → formdata}/constructor.any.js
  40. 0 test/wpt/tests/xhr/{ → formdata}/delete.any.js
  41. 0 test/wpt/tests/xhr/{ → formdata}/foreach.any.js
  42. 0 test/wpt/tests/xhr/{ → formdata}/get.any.js
  43. 0 test/wpt/tests/xhr/{ → formdata}/has.any.js
  44. 0 test/wpt/tests/xhr/{ → formdata}/set-blob.any.js
  45. 0 test/wpt/tests/xhr/{ → formdata}/set.any.js
  46. +4 −5 types/agent.d.ts
  47. +1 −1 types/api.d.ts
  48. +3 −4 types/balanced-pool.d.ts
  49. +5 −5 types/client.d.ts
  50. +3 −3 types/connector.d.ts
  51. +4 −4 types/diagnostics-channel.d.ts
  52. +23 −23 types/dispatcher.d.ts
  53. +3 −3 types/errors.d.ts
  54. +1 −1 types/fetch.d.ts
  55. +8 −3 types/filereader.d.ts
  56. +1 −1 types/global-dispatcher.d.ts
  57. +2 −2 types/interceptors.d.ts
  58. +3 −3 types/mock-agent.d.ts
  59. +4 −4 types/mock-client.d.ts
  60. +3 −3 types/mock-errors.d.ts
  61. +1 −1 types/mock-interceptor.d.ts
  62. +4 −4 types/mock-pool.d.ts
  63. +51 −0 types/patch.d.ts
  64. +2 −2 types/pool-stats.d.ts
  65. +5 −6 types/pool.d.ts
  66. +3 −3 types/proxy-agent.d.ts
  67. +1 −1 types/readable.d.ts
  68. +213 −0 types/webidl.d.ts
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -78,3 +78,4 @@ fuzz-results-*.json

# Bundle output
undici-fetch.js
/test/imports/undici-import.js
12 changes: 7 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -178,10 +178,6 @@ Implements [fetch](https://fetch.spec.whatwg.org/#fetch-method).

Only supported on Node 16.8+.

This is [experimental](https://nodejs.org/api/documentation.html#documentation_stability_index) and is not yet fully compliant with the Fetch Standard.
We plan to ship breaking changes to this feature until it is out of experimental.
Help us improve the test coverage by following instructions at [nodejs/undici/#951](https://github.com/nodejs/undici/issues/951).

Basic usage example:

```js
@@ -234,9 +230,15 @@ const data = {
},
}

await fetch('https://example.com', { body: data, method: 'POST' })
await fetch('https://example.com', { body: data, method: 'POST', duplex: 'half' })
```

#### `request.duplex`

- half

In this implementation of fetch, `request.duplex` must be set if `request.body` is `ReadableStream` or `Async Iterables`. And fetch requests are currently always be full duplex. More detail refer to [Fetch Standard.](https://fetch.spec.whatwg.org/#dom-requestinit-duplex)

#### `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()`.
Binary file modified docs/assets/lifecycle-diagram.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
46 changes: 23 additions & 23 deletions index.d.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
import Dispatcher = require('./types/dispatcher')
import Dispatcher from'./types/dispatcher'
import { setGlobalDispatcher, getGlobalDispatcher } from './types/global-dispatcher'
import { setGlobalOrigin, getGlobalOrigin } from './types/global-origin'
import Pool = require('./types/pool')
import Pool from'./types/pool'
import { RedirectHandler, DecoratorHandler } from './types/handlers'

import BalancedPool = require('./types/balanced-pool')
import Client = require('./types/client')
import buildConnector = require('./types/connector')
import errors = require('./types/errors')
import Agent = require('./types/agent')
import MockClient = require('./types/mock-client')
import MockPool = require('./types/mock-pool')
import MockAgent = require('./types/mock-agent')
import mockErrors = require('./types/mock-errors')
import ProxyAgent = require('./types/proxy-agent')
import BalancedPool from './types/balanced-pool'
import Client from'./types/client'
import buildConnector from'./types/connector'
import errors from'./types/errors'
import Agent from'./types/agent'
import MockClient from'./types/mock-client'
import MockPool from'./types/mock-pool'
import MockAgent from'./types/mock-agent'
import mockErrors from'./types/mock-errors'
import ProxyAgent from'./types/proxy-agent'
import { request, pipeline, stream, connect, upgrade } from './types/api'

export * from './types/fetch'
@@ -27,26 +27,26 @@ export { Dispatcher, BalancedPool, Pool, Client, buildConnector, errors, Agent,
export default Undici

declare namespace Undici {
var Dispatcher: typeof import('./types/dispatcher')
var Pool: typeof import('./types/pool');
var Dispatcher: typeof import('./types/dispatcher').default
var Pool: typeof import('./types/pool').default;
var RedirectHandler: typeof import ('./types/handlers').RedirectHandler
var DecoratorHandler: typeof import ('./types/handlers').DecoratorHandler
var createRedirectInterceptor: typeof import ('./types/interceptors').createRedirectInterceptor
var BalancedPool: typeof import('./types/balanced-pool');
var Client: typeof import('./types/client');
var buildConnector: typeof import('./types/connector');
var errors: typeof import('./types/errors');
var Agent: typeof import('./types/agent');
var BalancedPool: typeof import('./types/balanced-pool').default;
var Client: typeof import('./types/client').default;
var buildConnector: typeof import('./types/connector').default;
var errors: typeof import('./types/errors').default;
var Agent: typeof import('./types/agent').default;
var setGlobalDispatcher: typeof import('./types/global-dispatcher').setGlobalDispatcher;
var getGlobalDispatcher: typeof import('./types/global-dispatcher').getGlobalDispatcher;
var request: typeof import('./types/api').request;
var stream: typeof import('./types/api').stream;
var pipeline: typeof import('./types/api').pipeline;
var connect: typeof import('./types/api').connect;
var upgrade: typeof import('./types/api').upgrade;
var MockClient: typeof import('./types/mock-client');
var MockPool: typeof import('./types/mock-pool');
var MockAgent: typeof import('./types/mock-agent');
var mockErrors: typeof import('./types/mock-errors');
var MockClient: typeof import('./types/mock-client').default;
var MockPool: typeof import('./types/mock-pool').default;
var MockAgent: typeof import('./types/mock-agent').default;
var mockErrors: typeof import('./types/mock-errors').default;
var fetch: typeof import('./types/fetch').fetch;
}
38 changes: 18 additions & 20 deletions lib/fetch/body.js
Original file line number Diff line number Diff line change
@@ -2,7 +2,7 @@

const Busboy = require('busboy')
const util = require('../core/util')
const { ReadableStreamFrom, toUSVString, isBlobLike, isReadableStreamLike, readableStreamClose } = require('./util')
const { ReadableStreamFrom, isBlobLike, isReadableStreamLike, readableStreamClose } = require('./util')
const { FormData } = require('./formdata')
const { kState } = require('./symbols')
const { webidl } = require('./webidl')
@@ -66,9 +66,13 @@ function extractBody (object, keepalive = false) {
let type = null

// 10. Switch on object:
if (object == null) {
// Note: The IDL processor cannot handle this situation. See
// https://crbug.com/335871.
if (typeof object === 'string') {
// Set source to the UTF-8 encoding of object.
// Note: setting source to a Uint8Array here breaks some mocking assumptions.
source = object

// Set type to `text/plain;charset=UTF-8`.
type = 'text/plain;charset=UTF-8'
} else if (object instanceof URLSearchParams) {
// URLSearchParams

@@ -126,7 +130,8 @@ function extractBody (object, keepalive = false) {

yield * value.stream()

yield enc.encode('\r\n')
// '\r\n' encoded
yield new Uint8Array([13, 10])
}
}

@@ -157,6 +162,11 @@ function extractBody (object, keepalive = false) {
if (object.type) {
type = object.type
}
} else if (object instanceof Uint8Array) {
// byte sequence

// Set source to object.
source = object
} else if (typeof object[Symbol.asyncIterator] === 'function') {
// If keepalive is true, then throw a TypeError.
if (keepalive) {
@@ -172,17 +182,10 @@ function extractBody (object, keepalive = false) {

stream =
object instanceof ReadableStream ? object : ReadableStreamFrom(object)
} else {
// TODO: byte sequence?
// TODO: scalar value string?
// TODO: else?
source = toUSVString(object)
type = 'text/plain;charset=UTF-8'
}

// 11. If source is a byte sequence, then set action to a
// step that returns source and length to source’s length.
// TODO: What is a "byte sequence?"
if (typeof source === 'string' || util.isBuffer(source)) {
length = Buffer.byteLength(source)
}
@@ -329,9 +332,7 @@ function bodyMixinMethods (instance) {
},

async formData () {
if (!(this instanceof instance)) {
throw new TypeError('Illegal invocation')
}
webidl.brandCheck(this, instance)

throwIfAborted(this[kState])

@@ -433,7 +434,7 @@ function bodyMixinMethods (instance) {
throwIfAborted(this[kState])

// Otherwise, throw a TypeError.
webidl.errors.exception({
throw webidl.errors.exception({
header: `${instance.name}.formData`,
message: 'Could not parse content as FormData.'
})
@@ -450,11 +451,8 @@ function mixinBody (prototype) {

// https://fetch.spec.whatwg.org/#concept-body-consume-body
async function specConsumeBody (object, type, instance) {
if (!(object instanceof instance)) {
throw new TypeError('Illegal invocation')
}
webidl.brandCheck(object, instance)

// TODO: why is this needed?
throwIfAborted(object[kState])

// 1. If object is unusable, then return a promise rejected
20 changes: 19 additions & 1 deletion lib/fetch/constants.js
Original file line number Diff line number Diff line change
@@ -8,6 +8,17 @@ const nullBodyStatus = [101, 204, 205, 304]

const redirectStatus = [301, 302, 303, 307, 308]

// https://fetch.spec.whatwg.org/#block-bad-port
const badPorts = [
'1', '7', '9', '11', '13', '15', '17', '19', '20', '21', '22', '23', '25', '37', '42', '43', '53', '69', '77', '79',
'87', '95', '101', '102', '103', '104', '109', '110', '111', '113', '115', '117', '119', '123', '135', '137',
'139', '143', '161', '179', '389', '427', '465', '512', '513', '514', '515', '526', '530', '531', '532',
'540', '548', '554', '556', '563', '587', '601', '636', '989', '990', '993', '995', '1719', '1720', '1723',
'2049', '3659', '4045', '5060', '5061', '6000', '6566', '6665', '6666', '6667', '6668', '6669', '6697',
'10080'
]

// https://w3c.github.io/webappsec-referrer-policy/#referrer-policies
const referrerPolicy = [
'',
'no-referrer',
@@ -44,6 +55,11 @@ const requestBodyHeader = [
'content-type'
]

// https://fetch.spec.whatwg.org/#enumdef-requestduplex
const requestDuplex = [
'half'
]

// http://fetch.spec.whatwg.org/#forbidden-method
const forbiddenMethods = ['CONNECT', 'TRACE', 'TRACK']

@@ -108,5 +124,7 @@ module.exports = {
redirectStatus,
corsSafeListedMethods,
nullBodyStatus,
safeMethods
safeMethods,
badPorts,
requestDuplex
}
75 changes: 5 additions & 70 deletions lib/fetch/dataURL.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const assert = require('assert')
const { atob } = require('buffer')
const { isValidHTTPToken } = require('./util')
const { format } = require('url')
const { isValidHTTPToken, isomorphicDecode } = require('./util')

const encoder = new TextEncoder()

@@ -54,15 +55,15 @@ function dataURLProcessor (dataURL) {
const encodedBody = input.slice(mimeTypeLength + 1)

// 10. Let body be the percent-decoding of encodedBody.
/** @type {Uint8Array|string} */
let body = stringPercentDecode(encodedBody)

// 11. If mimeType ends with U+003B (;), followed by
// zero or more U+0020 SPACE, followed by an ASCII
// case-insensitive match for "base64", then:
if (/;(\u0020){0,}base64$/i.test(mimeType)) {
// 1. Let stringBody be the isomorphic decode of body.
const stringBody = decodeURIComponent(new TextDecoder('utf-8').decode(body))
const stringBody = isomorphicDecode(body)

// 2. Set body to the forgiving-base64 decode of
// stringBody.
body = forgivingBase64(stringBody)
@@ -111,73 +112,7 @@ function dataURLProcessor (dataURL) {
* @param {boolean} excludeFragment
*/
function URLSerializer (url, excludeFragment = false) {
// 1. Let output be url’s scheme and U+003A (:) concatenated.
let output = url.protocol

// 2. If url’s host is non-null:
if (url.host.length > 0) {
// 1. Append "//" to output.
output += '//'

// 2. If url includes credentials, then:
if (url.username.length > 0 || url.password.length > 0) {
// 1. Append url’s username to output.
output += url.username

// 2. If url’s password is not the empty string, then append U+003A (:),
// followed by url’s password, to output.
if (url.password.length > 0) {
output += ':' + url.password
}

// 3. Append U+0040 (@) to output.
output += '@'
}

// 3. Append url’s host, serialized, to output.
output += decodeURIComponent(url.hostname)

// 4. If url’s port is non-null, append U+003A (:) followed by url’s port,
// serialized, to output.
if (url.port.length > 0) {
output += ':' + url.port
}
}

// 3. If url’s host is null, url does not have an opaque path,
// url’s path’s size is greater than 1, and url’s path[0]
// is the empty string, then append U+002F (/) followed by
// U+002E (.) to output.
// Note: This prevents web+demo:/.//not-a-host/ or web+demo:/path/..//not-a-host/,
// when parsed and then serialized, from ending up as web+demo://not-a-host/
// (they end up as web+demo:/.//not-a-host/).
// Undici implementation note: url's path[0] can never be an
// empty string, so we have to slightly alter what the spec says.
if (
url.host.length === 0 &&
url.pathname.length > 1 &&
url.href.slice(url.protocol.length + 1)[0] === '.'
) {
output += '/.'
}

// 4. Append the result of URL path serializing url to output.
output += url.pathname

// 5. If url’s query is non-null, append U+003F (?),
// followed by url’s query, to output.
if (url.search.length > 0) {
output += url.search
}

// 6. If exclude fragment is false and url’s fragment is non-null,
// then append U+0023 (#), followed by url’s fragment, to output.
if (excludeFragment === false && url.hash.length > 0) {
output += url.hash
}

// 7. Return output.
return output
return format(url, { fragment: !excludeFragment })
}

// https://infra.spec.whatwg.org/#collect-a-sequence-of-code-points
Loading