ofetch icon indicating copy to clipboard operation
ofetch copied to clipboard

FormData support

Open danielroe opened this issue 3 years ago • 18 comments

Seems a good idea for using [object FormData] to append proper content-type (adding in short)

Originally posted by @pi0 in https://github.com/unjs/ohmyfetch/pull/36#discussion_r767707178

Other notes:

  • validate formdata content or ensure we're not in browser environment to avoid setting header when sending files/blobs
  • Content-Disposition header

danielroe avatar Dec 13 '21 12:12 danielroe

Very interested in gaining access to the Content-Disposition header. That is something we need for our project.

jshimkoski avatar Jul 19 '22 13:07 jshimkoski

I did not know about Content-Disposition and also I don't really understand why I have to pass the formData variable to body and Content-Disposition header but this seems to work:

const formData = new FormData()

formData.append('avatar', avatar)
formData.append('model', model)
formData.append('id', id)

$fetch('/avatar', {
  method: 'POST',
  body: formData,
  headers: { 'Content-Disposition': formData }
})

IsraelOrtuno avatar Sep 29 '22 12:09 IsraelOrtuno

Still a bug in nuxt 3

bernlorbis avatar Jan 09 '23 00:01 bernlorbis

I think the problem comes from the content-length header. The error happens before sending the request:

  cause: RequestContentLengthMismatchError: Request body length does not match content-length header
      at AsyncWriter.write (node:internal/deps/undici/undici:10139:19)
      at writeIterable (node:internal/deps/undici/undici:10103:23) {
    code: 'UND_ERR_REQ_CONTENT_LENGTH_MISMATCH'

Axios seems to be able to send the same exact request

posva avatar Mar 09 '23 21:03 posva

@posva We're seeing people encounter issues with undici - do you encounter the same issue if you downgrade y our node version?

danielroe avatar Mar 09 '23 22:03 danielroe

I get the same result with both node 18 and node 16 (latest patchs)

posva avatar Mar 09 '23 22:03 posva

I did not know about Content-Disposition and also I don't really understand why I have to pass the formData variable to body and Content-Disposition header but this seems to work:

const formData = new FormData()

formData.append('avatar', avatar)
formData.append('model', model)
formData.append('id', id)

$fetch('/avatar', {
  method: 'POST',
  body: formData,
  headers: { 'Content-Disposition': formData }
})

when I set Content-Disposition to an arbitrary string, it works too

maximlopin avatar Mar 30 '23 11:03 maximlopin

const formData = new FormData();
const keys = Object.keys(payload);
keys.forEach(key => {
        formData.append(key, payload[key]);
});
$fetch(`/data-imports/`,
      {
        method: 'POST',
        body: formData,
        headers: {
          'Content-Type': 'multipart/form-data'
        }
      }
    )
      .then(res => {
        console.log(res);
      })
      .catch(error => {
        console.log(error);
      });

Can't send FormData. Any updates? By the way, I am using it in the Nuxt-2-bridge as the default package.

TusharRoy23 avatar Jul 24 '23 08:07 TusharRoy23

const formData = new FormData();
const keys = Object.keys(payload);
keys.forEach(key => {
        formData.append(key, payload[key]);
});
$fetch(`/data-imports/`,
      {
        method: 'POST',
        body: formData,
        headers: {
          'Content-Type': 'multipart/form-data'
        }
      }
    )
      .then(res => {
        console.log(res);
      })
      .catch(error => {
        console.log(error);
      });

Can't send FormData. Any updates? By the way, I am using it in the Nuxt-2-bridge as the default package.

The issue has been resolved after removing the Content-type from the request header and the browser will detect that automatically. And also check in the request header if the Accept: */* is like that or not.

TusharRoy23 avatar Jul 25 '23 05:07 TusharRoy23

As mentioned by @TusharRoy23, when using formData, do not specify a 'Content-Type' header, as it will automatically be set by fetch. @posva do you have a repro for this error 🙏🏽 ?

Hebilicious avatar Aug 08 '23 14:08 Hebilicious

I don't have one anymore but should be reproducible (if still there) when trying to send images, something like https://vueuse.org/core/useFileDialog/#usefiledialog.

posva avatar Aug 08 '23 14:08 posva

@Hebilicious I don't have. But If you still having this issue then you have to forcefully remove the Content-type from the header.

TusharRoy23 avatar Aug 08 '23 14:08 TusharRoy23

any solution to this, having same issue ,

EDIT: my server had issue , its working fine

geminigeek avatar Aug 16 '23 12:08 geminigeek

I am having this issue. I am not setting the content type, but I am setting auth headers.

It keeps sending the content-type as application/json

Lukeharris30 avatar Sep 13 '23 21:09 Lukeharris30

Meanwhile, Axios works great with this setup without any magic. Just create its instance in nuxt plugin

sanyaches avatar Nov 06 '23 15:11 sanyaches

Any update on this? Have to use vanilla fetch to have it working

jeannen avatar Nov 11 '23 12:11 jeannen

same problem :/

nonomaxxis avatar Dec 04 '23 15:12 nonomaxxis

same problem

Idji avatar Dec 06 '23 07:12 Idji

I can confirm from the latest version:

  • do not manually set content-type header, it will be detected and set (with extra things like boundary)
  • no need to set content-disposition manually

tewshi avatar Jun 06 '24 09:06 tewshi

Excellent. If anyone is still experiencing this, please let me know and I'll happily reopen.

danielroe avatar Jun 06 '24 10:06 danielroe

Hi, using nuxt 3, this doesn't work for me:

const formData = new FormData();
Object.keys(fields).forEach((field) => {
        formData.append(field, fields[field]);
});

formData.append('file', new Blob([JSON.stringify(content)], { type: 'application/json' }));

//Investigate in the future why $fetch doesn't work!!!
const response = await $fetch(url, {
            method: 'POST',
            body: formData
 });

console.log('Response: ',response);

It also doesn't work if I don't specify { type: 'application/json' } for the file.

Any ideas what I should do? My workaround at the moment is to use fetch instead of $fetch.

belfortf avatar Jun 22 '24 07:06 belfortf

@belfortf assuming you mean that the form body is not being sent, both JSON and form bodies work fine for me. The content information headers should be set automatically too. Could you provide some code of how you handle the incoming request? Or if the error is something else maybe provide the error?

maxibue avatar Jun 25 '24 19:06 maxibue

image

I have created ofetch instance to use inside the app. Every API call receives JSON data, but I have one endpoint that needs to send a formData instance. So, I need to manually disable the Content-Type header by setting it to undefined. Everything works, but I receive a typescript error. Maybe we need to extend our type definitions?

image

hrynevychroman avatar Jul 10 '24 07:07 hrynevychroman

I have encountered a difference in the content type used by $fetch in Nuxt 3 (^3.12.4) compared to the native fetch and ofetch methods.

Specifically, $fetch utilizes application/json with Form Data, while the native fetch and ofetch methods use multipart/form-data; boundary=----WebKitFormBoundarySOMETHING. I'm curious to understand the reason behind this difference.

gokhantaskan avatar Aug 01 '24 18:08 gokhantaskan

@gokhantaskan $fetch is created with ofetch and should not differ (except perhaps in internal requests, which are powered by unenv) so I would be grateful of a reproduction and new issue if you are happy to make one.

danielroe avatar Aug 01 '24 18:08 danielroe

@gokhantaskan $fetch is created with ofetch and should not differ (except perhaps in internal requests, which are powered by unenv) so I would be grateful of a reproduction and new issue if you are happy to make one.

I noticed an issue with the $api implementation at the following URL, https://nuxt.com/docs/guide/recipes/custom-usefetch#custom-fetch. It seems that $fetch.create might be causing the problem. I don't have time to reproduce it right now, but I will attempt to do so tomorrow.

Tested with ofetchand $fetchdirectly in my Nuxt codebase, it automatically added the Content-Type header.

gokhantaskan avatar Aug 01 '24 19:08 gokhantaskan

I've identified the issue, and it's on my end:

const $api = $fetch.create({
      baseURL: config.public.apiPath,
      credentials: "include",
      // Convert request body to snake_case
      onRequest({ options }) {
        if (options.body) {
          options.body = changeObjectKeysCase(options.body, "snake");
          ^^^ This line manipulates FormData and makes it plain object
        }
      },
...

I solved it with the following code:

onRequest({ options }) {
  if (options.body) {
    if (options.body instanceof FormData) {
      const formData = new FormData();

      for (const [key, value] of options.body.entries()) {
        formData.append(snakeCase(key), value);
      }

      options.body = formData;
    } else {
      options.body = changeObjectKeysCase(options.body, "snake");
    }
  }
},

gokhantaskan avatar Aug 01 '24 20:08 gokhantaskan