undici icon indicating copy to clipboard operation
undici copied to clipboard

Using a global FormData with undici.request causes a TypeError

Open KhafraDev opened this issue 6 months ago • 1 comments

Bug Description

Using a global FormData with undici.request causes a TypeError: Cannot read properties of null (reading 'byteLength')

"I found that this is the case because util.isFormDataLike() is true here https://github.com/nodejs/undici/blob/2ed2a8a1393b0da3215997e9941ec3a92a93f3d0/lib/dispatcher/client-h1.js#L1021 but webidl.is.FormData is false here https://github.com/nodejs/undici/blob/2ed2a8a1393b0da3215997e9941ec3a92a93f3d0/lib/web/fetch/body.js#L120"

Reproducible By

import { request, setGlobalDispatcher, Agent } from './index.js'
import { createServer } from 'node:http'
import { once } from 'node:events'

setGlobalDispatcher(new Agent())

const server = createServer((req, res) => {
  req.pipe(res).on('end', () => res.end())
}).listen(0)

await once(server, 'listening')

const fd = new FormData()
fd.set('a', 'b')

await request(`http://localhost:${server.address().port}`, {
  method: 'POST',
  body: fd
}).finally(() => server.close())

Expected Behavior

Logs & Screenshots

Environment

Additional context

KhafraDev avatar May 13 '25 16:05 KhafraDev

This patch seems to fix this:

diff --git a/lib/web/fetch/body.js b/lib/web/fetch/body.js
index 81d9b6d0e67a11ecb51241e0daf3b8a05cd11b49..d5edc13e5f2216ab47d21291ed8aca22f52b7191 100644
--- a/lib/web/fetch/body.js
+++ b/lib/web/fetch/body.js
@@ -117,7 +117,7 @@ function extractBody (object, keepalive = false) {
 
     // Set source to a copy of the bytes held by object.
     source = new Uint8Array(object.buffer.slice(object.byteOffset, object.byteOffset + object.byteLength))
-  } else if (webidl.is.FormData(object)) {
+  } else if (util.isFormDataLike(object)) {
     const boundary = `----formdata-undici-0${`${random(1e11)}`.padStart(11, '0')}`
     const prefix = `--${boundary}\r\nContent-Disposition: form-data`

tim-smart avatar May 15 '25 01:05 tim-smart

Seeing the same issue with FormData imported from undici

punkpeye avatar Jul 16 '25 14:07 punkpeye

Hi, struggling to find what resolved this, can you enlighten me please? A quick check reveals I can still reproduce this with 4608ef157cc75d5ce3d2802e6949cb865d73146c.

Jiralite avatar Jul 16 '25 16:07 Jiralite

I don't view this as a bug. At most, the error could be improved.

KhafraDev avatar Jul 16 '25 17:07 KhafraDev

I don't understand what's the workaround?

punkpeye avatar Jul 16 '25 18:07 punkpeye

Can you elaborate why you think not being able to pass global FormData in NodeJS to undici methods is not a bug, considering the native fetch() function in NodeJS is undici and accepts it just fine?

Qjuh avatar Jul 16 '25 19:07 Qjuh

I don't understand what's the workaround?

There are a few, but one for library authors:

import { FormData as UndiciFormData, request } from 'undici'

function globalToUndiciFormData(fd: globalThis.FormData): UndiciFormData {
  const clone = new UndiciFormData()

  for (const [name, value] of fd.entries()) {
    if (typeof value === 'string') {
      clone.append(name, value)
    } else {
      clone.append(name, value, value.name) // I don't know if this is needed
    }
  }

  return clone
}

const fd = new FormData()
fd.set('a', new Blob(['...']), 'blob.txt')

await request('https://a', {
  method: 'POST',
  body: globalToUndiciFormData(fd)
})

Can you elaborate why you think not being able to pass global FormData in NodeJS to undici methods is not a bug

https://github.com/nodejs/undici/issues/2674 https://github.com/nodejs/undici/issues/3896

the native fetch in node is a version of undici that is not necessarily compatible with an up-to-date (or outdated) version.

, considering the native fetch() function in NodeJS is undici and accepts it just fine?

This would be a bug, or it's what I am talking about above.

KhafraDev avatar Jul 16 '25 20:07 KhafraDev