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: 9d5f23177408dc16d3d4cbb8cebf463081c54e16
Choose a base ref
...
head repository: nodejs/undici
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: 9457c9719029945ef9ff36b71d58557443730942
Choose a head ref
  • 8 commits
  • 12 files changed
  • 7 contributors

Commits on Jan 19, 2023

  1. Verified

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

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

Commits on Jan 20, 2023

  1. fix: don't set socket on client before init

    Refs: #1633
    ronag committed Jan 20, 2023
    Copy the full SHA
    f61a902 View commit details
  2. Fix fetch parameters not being applied correctly (#1870)

    * Fix default fetch parameters
    
    * Preserve existing behavior with 300 second timeout if not defined
    
    * Add test for 300 second timeout as default
    
    * Cleanup old unused tests
    
    * Simplify how fetch utilizes timeouts from agent
    xconverge authored Jan 20, 2023

    Verified

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

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

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

Commits on Jan 21, 2023

  1. Verified

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

Commits on Jan 22, 2023

  1. 5.15.2

    ronag committed Jan 22, 2023
    Copy the full SHA
    9457c97 View commit details
Showing with 174 additions and 53 deletions.
  1. +1 −1 CONTRIBUTING.md
  2. +9 −1 index.js
  3. +4 −4 lib/client.js
  4. +16 −1 lib/core/util.js
  5. +12 −9 lib/fetch/dataURL.js
  6. +1 −3 lib/fetch/index.js
  7. +0 −1 lib/websocket/connection.js
  8. +2 −1 package.json
  9. +49 −0 test/fetch/fetch-timeouts.js
  10. +80 −0 test/fetch/formdata.js
  11. +0 −16 test/node-fetch/main.js
  12. +0 −16 test/node-fetch/utils/server.js
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -50,7 +50,7 @@ npm i
> This requires [docker](https://www.docker.com/) installed on your machine.
```bash
npm run build-wasm
npm run build:wasm
```

#### Copy the sources to `undici`
10 changes: 9 additions & 1 deletion index.js
Original file line number Diff line number Diff line change
@@ -24,6 +24,14 @@ const nodeVersion = process.versions.node.split('.')
const nodeMajor = Number(nodeVersion[0])
const nodeMinor = Number(nodeVersion[1])

let hasCrypto
try {
require('crypto')
hasCrypto = true
} catch {
hasCrypto = false
}

Object.assign(Dispatcher.prototype, api)

module.exports.Dispatcher = Dispatcher
@@ -128,7 +136,7 @@ if (nodeMajor >= 16) {
module.exports.setCookie = setCookie
}

if (nodeMajor >= 18) {
if (nodeMajor >= 18 && hasCrypto) {
const { WebSocket } = require('./lib/websocket/websocket')

module.exports.WebSocket = WebSocket
8 changes: 4 additions & 4 deletions lib/client.js
Original file line number Diff line number Diff line change
@@ -1074,8 +1074,6 @@ async function connect (client) {

assert(socket)

client[kSocket] = socket

socket[kNoRef] = false
socket[kWriting] = false
socket[kReset] = false
@@ -1091,6 +1089,8 @@ async function connect (client) {
.on('end', onSocketEnd)
.on('close', onSocketClose)

client[kSocket] = socket

if (channels.connected.hasSubscribers) {
channels.connected.publish({
connectParams: {
@@ -1176,7 +1176,7 @@ function _resume (client, sync) {

const socket = client[kSocket]

if (socket) {
if (socket && !socket.destroyed) {
if (client[kSize] === 0) {
if (!socket[kNoRef] && socket.unref) {
socket.unref()
@@ -1243,7 +1243,7 @@ function _resume (client, sync) {

if (!socket) {
connect(client)
continue
return
}

if (socket.destroyed || socket[kWriting] || socket[kReset] || socket[kBlocking]) {
17 changes: 16 additions & 1 deletion lib/core/util.js
Original file line number Diff line number Diff line change
@@ -354,8 +354,23 @@ function ReadableStreamFrom (iterable) {
)
}

// The chunk should be a FormData instance and contains
// all the required methods.
function isFormDataLike (chunk) {
return chunk && chunk.constructor && chunk.constructor.name === 'FormData'
return (chunk &&
chunk.constructor && chunk.constructor.name === 'FormData' &&
typeof chunk === 'object' &&
(typeof chunk.append === 'function' &&
typeof chunk.delete === 'function' &&
typeof chunk.get === 'function' &&
typeof chunk.getAll === 'function' &&
typeof chunk.has === 'function' &&
typeof chunk.set === 'function' &&
typeof chunk.entries === 'function' &&
typeof chunk.keys === 'function' &&
typeof chunk.values === 'function' &&
typeof chunk.forEach === 'function')
)
}

const kEnumerableProperty = Object.create(null)
21 changes: 12 additions & 9 deletions lib/fetch/dataURL.js
Original file line number Diff line number Diff line change
@@ -5,6 +5,12 @@ const { isValidHTTPToken, isomorphicDecode } = require('./util')

const encoder = new TextEncoder()

// Regex
const HTTP_TOKEN_CODEPOINTS = /^[!#$%&'*+-.^_|~A-z0-9]+$/
const HTTP_WHITESPACE_REGEX = /(\u000A|\u000D|\u0009|\u0020)/ // eslint-disable-line
// https://mimesniff.spec.whatwg.org/#http-quoted-string-token-code-point
const HTTP_QUOTED_STRING_TOKENS = /^(\u0009|\x{0020}-\x{007E}|\x{0080}-\x{00FF})+$/ // eslint-disable-line

// https://fetch.spec.whatwg.org/#data-url-processor
/** @param {URL} dataURL */
function dataURLProcessor (dataURL) {
@@ -217,7 +223,7 @@ function parseMIMEType (input) {
// 4. If type is the empty string or does not solely
// contain HTTP token code points, then return failure.
// https://mimesniff.spec.whatwg.org/#http-token-code-point
if (type.length === 0 || !/^[!#$%&'*+-.^_|~A-z0-9]+$/.test(type)) {
if (type.length === 0 || !HTTP_TOKEN_CODEPOINTS.test(type)) {
return 'failure'
}

@@ -244,7 +250,7 @@ function parseMIMEType (input) {

// 9. If subtype is the empty string or does not solely
// contain HTTP token code points, then return failure.
if (subtype.length === 0 || !/^[!#$%&'*+-.^_|~A-z0-9]+$/.test(subtype)) {
if (subtype.length === 0 || !HTTP_TOKEN_CODEPOINTS.test(subtype)) {
return 'failure'
}

@@ -258,9 +264,7 @@ function parseMIMEType (input) {
/** @type {Map<string, string>} */
parameters: new Map(),
// https://mimesniff.spec.whatwg.org/#mime-type-essence
get essence () {
return `${this.type}/${this.subtype}`
}
essence: `${type}/${subtype}`
}

// 11. While position is not past the end of input:
@@ -272,7 +276,7 @@ function parseMIMEType (input) {
// whitespace from input given position.
collectASequenceOfCodePoints(
// https://fetch.spec.whatwg.org/#http-whitespace
(char) => /(\u000A|\u000D|\u0009|\u0020)/.test(char), // eslint-disable-line
char => HTTP_WHITESPACE_REGEX.test(char),
input,
position
)
@@ -355,9 +359,8 @@ function parseMIMEType (input) {
// then set mimeType’s parameters[parameterName] to parameterValue.
if (
parameterName.length !== 0 &&
/^[!#$%&'*+-.^_|~A-z0-9]+$/.test(parameterName) &&
// https://mimesniff.spec.whatwg.org/#http-quoted-string-token-code-point
!/^(\u0009|\x{0020}-\x{007E}|\x{0080}-\x{00FF})+$/.test(parameterValue) && // eslint-disable-line
HTTP_TOKEN_CODEPOINTS.test(parameterName) &&
!HTTP_QUOTED_STRING_TOKENS.test(parameterValue) &&
!mimeType.parameters.has(parameterName)
) {
mimeType.parameters.set(parameterName, parameterValue)
4 changes: 1 addition & 3 deletions lib/fetch/index.js
Original file line number Diff line number Diff line change
@@ -56,7 +56,7 @@ const { Readable, pipeline } = require('stream')
const { isErrored, isReadable } = require('../core/util')
const { dataURLProcessor, serializeAMimeType } = require('./dataURL')
const { TransformStream } = require('stream/web')
const { getGlobalDispatcher } = require('../../index')
const { getGlobalDispatcher } = require('../global')
const { webidl } = require('./webidl')
const { STATUS_CODES } = require('http')

@@ -1951,8 +1951,6 @@ async function httpNetworkFetch (
body: fetchParams.controller.dispatcher.isMockActive ? request.body && request.body.source : body,
headers: request.headersList[kHeadersCaseInsensitive],
maxRedirections: 0,
bodyTimeout: 300_000,
headersTimeout: 300_000,
upgrade: request.mode === 'websocket' ? 'websocket' : undefined
},
{
1 change: 0 additions & 1 deletion lib/websocket/connection.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
'use strict'

// TODO: crypto isn't available in all environments
const { randomBytes, createHash } = require('crypto')
const diagnosticsChannel = require('diagnostics_channel')
const { uid, states } = require('./constants')
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "undici",
"version": "5.15.1",
"version": "5.15.2",
"description": "An HTTP/1.1 client, written from scratch for Node.js",
"homepage": "https://undici.nodejs.org",
"bugs": {
@@ -79,6 +79,7 @@
"cronometro": "^1.0.5",
"delay": "^5.0.0",
"docsify-cli": "^4.4.3",
"form-data": "^4.0.0",
"formdata-node": "^4.3.1",
"https-pem": "^3.0.0",
"husky": "^8.0.1",
49 changes: 49 additions & 0 deletions test/fetch/fetch-timeouts.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
'use strict'

const { test } = require('tap')

const { fetch, Agent } = require('../..')
const { createServer } = require('http')
const FakeTimers = require('@sinonjs/fake-timers')

test('Fetch very long request, timeout overridden so no error', (t) => {
const minutes = 6
const msToDelay = 1000 * 60 * minutes

t.setTimeout(undefined)
t.plan(1)

const clock = FakeTimers.install()
t.teardown(clock.uninstall.bind(clock))

const server = createServer((req, res) => {
setTimeout(() => {
res.end('hello')
}, msToDelay)
clock.tick(msToDelay + 1)
})
t.teardown(server.close.bind(server))

server.listen(0, () => {
fetch(`http://localhost:${server.address().port}`, {
path: '/',
method: 'GET',
dispatcher: new Agent({
headersTimeout: 0,
connectTimeout: 0,
bodyTimeout: 0
})
})
.then((response) => response.text())
.then((response) => {
t.equal('hello', response)
t.end()
})
.catch((err) => {
// This should not happen, a timeout error should not occur
t.error(err)
})

clock.tick(msToDelay - 1)
})
})
80 changes: 80 additions & 0 deletions test/fetch/formdata.js
Original file line number Diff line number Diff line change
@@ -4,6 +4,8 @@ const { test } = require('tap')
const { FormData, File, Response } = require('../../')
const { Blob: ThirdPartyBlob } = require('formdata-node')
const { Blob } = require('buffer')
const { isFormDataLike } = require('../../lib/core/util')
const ThirdPartyFormDataInvalid = require('form-data')

test('arg validation', (t) => {
const form = new FormData()
@@ -285,6 +287,84 @@ test('formData.constructor.name', (t) => {
t.end()
})

test('formData should be an instance of FormData', (t) => {
t.plan(3)

t.test('Invalid class FormData', (t) => {
class FormData {
constructor () {
this.data = []
}

append (key, value) {
this.data.push([key, value])
}

get (key) {
return this.data.find(([k]) => k === key)
}
}

const form = new FormData()
t.equal(isFormDataLike(form), false)
t.end()
})

t.test('Invalid function FormData', (t) => {
function FormData () {
const data = []
return {
append (key, value) {
data.push([key, value])
},
get (key) {
return data.find(([k]) => k === key)
}
}
}

const form = new FormData()
t.equal(isFormDataLike(form), false)
t.end()
})

test('Invalid third-party FormData', (t) => {
const form = new ThirdPartyFormDataInvalid()
t.equal(isFormDataLike(form), false)
t.end()
})

t.test('Valid FormData', (t) => {
const form = new FormData()
t.equal(isFormDataLike(form), true)
t.end()
})
})

test('FormData should be compatible with third-party libraries', (t) => {
t.plan(1)

class FormData {
constructor () {
this.data = []
}

append () {}
delete () {}
get () {}
getAll () {}
has () {}
set () {}
entries () {}
keys () {}
values () {}
forEach () {}
}

const form = new FormData()
t.equal(isFormDataLike(form), true)
})

test('arguments', (t) => {
t.equal(FormData.constructor.length, 1)
t.equal(FormData.prototype.append.length, 2)
16 changes: 0 additions & 16 deletions test/node-fetch/main.js
Original file line number Diff line number Diff line change
@@ -1660,20 +1660,4 @@ describe('node-fetch', () => {
expect(res.ok).to.be.false
})
})

// it('should not time out waiting for a response 60 seconds', function () {
// this.timeout(65_000)
// return fetch(`${base}timeout60s`).then(res => {
// expect(res.status).to.equal(200)
// expect(res.ok).to.be.true
// return res.text().then(result => {
// expect(result).to.equal('text')
// })
// })
// })

// it('should time out waiting for more than 300 seconds', function () {
// this.timeout(305_000)
// return expect(fetch(`${base}timeout300s`)).to.eventually.be.rejectedWith(TypeError)
// })
})
16 changes: 0 additions & 16 deletions test/node-fetch/utils/server.js
Original file line number Diff line number Diff line change
@@ -227,22 +227,6 @@ module.exports = class TestServer {
}, 1000)
}

if (p === '/timeout60s') {
setTimeout(() => {
res.statusCode = 200
res.setHeader('Content-Type', 'text/plain')
res.end('text')
}, 60_000)
}

if (p === '/timeout300s') {
setTimeout(() => {
res.statusCode = 200
res.setHeader('Content-Type', 'text/plain')
res.end('text')
}, 300_000)
}

if (p === '/slow') {
res.statusCode = 200
res.setHeader('Content-Type', 'text/plain')