kit icon indicating copy to clipboard operation
kit copied to clipboard

Formdata send by Sveltekit Endpoint is send by plain/text with no data

Open garytube opened this issue 3 years ago • 6 comments

Describe the bug

SvelteKit:

If you try to send a POST Request from Server-to-Server with FormData the request will fail. Sveltekits fetch() does not set content-type multipart/form-data. The Request will be text/plain and no Formdata will be submitted

This probably relates to #5349 #5292

I tried my best and created an Express Server and a Sveltekit App (see below) for debugging.

export const POST: RequestHandler = async ({ request }) => {
//	const formDataSendbyFrontend = await request.formData();
	const demoData = new FormData();
	demoData.append('data', "lorem ipsum");
	const res = await fetch('https://sveltekit-express-bug-tester.herokuapp.com/formdata', {
		method: 'POST',
		body: demoData
	})

The Request will fail, because Sveltekit sends text/plain and no FormData (???)

// Req Object Express Server
{
  body: {
    headers: {
      host: 'sveltekit-express-bug-tester.herokuapp.com',
      connection: 'close',
      accept: '*/*',
      'content-type': 'text/plain;charset=UTF-8',
      'accept-language': '*',
      'sec-fetch-mode': 'cors',
      'user-agent': 'undici',
      'accept-encoding': 'br, gzip, deflate',
      'x-request-id': '4f31b926-31c7-4cd2-a612-0bf85edb01fe',
      'x-forwarded-for': '1.1.1.1',
      'x-forwarded-proto': 'https',
      'x-forwarded-port': '443',
      via: '1.1 vegur',
      'connect-time': '0',
      'x-request-start': '1658828491278',
      'total-route-time': '0',
      'content-length': '17'
    },
    error: 'Not multipart/form-data'
  }
}

This used to Work with node-fetch and Sveltekit v.350 (not sure which version it got broken)

Reproduction

I prepared to Repos with a Server (Express + Multer) and a minimal Sveltekit App

💻 Server Repo: https://github.com/garytube/sveltekit-formdata-bug-server 🌍 Sveltekit Repo: https://github.com/garytube/sveltekit-formdata-bug-sveltekit

Send a POST Request with FormData from a SvelteKit Endpoint to http://sveltekit-express-bug-tester.herokuapp.com/formdata

	const demoData = new FormData();
	demoData.append('data', "lorem ipsum");
	const res = await fetch('http://sveltekit-express-bug-tester.herokuapp.com/formdata', {
		method: 'POST',
		body: demoData
	})

/formdata can also recive a File (Fieldname: image) (can be anything txt, jpg, pdf up to 1mb) Maybe this helps debugging

<input bind:files  type="file" accept="image/*" />

formData.append('image', files[0]')

The Sveltekit Demo Project will

  1. Send Form Data to Sveltekit Endpoint
  2. Sveltekit Endpoint will forward Request TO OFF-SITE Server
  3. Request will fail because req.content-type of Sveltekit to OFF-SITE server is text/plain and formdata is missing

Logs

REQUEST LOG EXPRESS SERVER

2022-07-26T10:52:17.994877+00:00 app[web.1]: 415 {
2022-07-26T10:52:17.994884+00:00 app[web.1]:   headers: {
2022-07-26T10:52:17.994885+00:00 app[web.1]:     host: 'sveltekit-express-bug-tester.herokuapp.com',
2022-07-26T10:52:17.994886+00:00 app[web.1]:     connection: 'close',
2022-07-26T10:52:17.994887+00:00 app[web.1]:     'content-type': 'text/plain;charset=UTF-8',
2022-07-26T10:52:17.994887+00:00 app[web.1]:     accept: '*/*',
2022-07-26T10:52:17.994888+00:00 app[web.1]:     'accept-language': '*',
2022-07-26T10:52:17.994888+00:00 app[web.1]:     'sec-fetch-mode': 'cors',
2022-07-26T10:52:17.994888+00:00 app[web.1]:     'user-agent': 'undici',
2022-07-26T10:52:17.994889+00:00 app[web.1]:     'accept-encoding': 'br, gzip, deflate',
2022-07-26T10:52:17.994889+00:00 app[web.1]:     'x-request-id': '47463367-0ee3-4c57-b58a-cbdbf0d7cf68',
2022-07-26T10:52:17.994889+00:00 app[web.1]:     'x-forwarded-for': '-------------',
2022-07-26T10:52:17.994890+00:00 app[web.1]:     'x-forwarded-proto': 'https',
2022-07-26T10:52:17.994890+00:00 app[web.1]:     'x-forwarded-port': '443',
2022-07-26T10:52:17.994890+00:00 app[web.1]:     via: '1.1 vegur',
2022-07-26T10:52:17.994890+00:00 app[web.1]:     'connect-time': '0',
2022-07-26T10:52:17.994891+00:00 app[web.1]:     'x-request-start': '1658832737992',
2022-07-26T10:52:17.994891+00:00 app[web.1]:     'total-route-time': '0',
2022-07-26T10:52:17.994891+00:00 app[web.1]:     'content-length': '17'
2022-07-26T10:52:17.994892+00:00 app[web.1]:   },
2022-07-26T10:52:17.994892+00:00 app[web.1]:   file: undefined,
2022-07-26T10:52:17.994892+00:00 app[web.1]:   body: undefined
2022-07-26T10:52:17.994892+00:00 app[web.1]: }

System Info

System:
    OS: Windows 11
    CPU: (12) x64 Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz
    Memory: 1.79 GB / 15.70 GB
  Binaries:
    Node: 18.1.0 - C:\Program Files\nodejs\node.EXE
    npm: 8.15.0 - C:\Program Files\nodejs\npm.CMD
  Browsers:
    Chrome: 103.0.5060.134
    Edge: Spartan (44.22000.120.0), Chromium (103.0.1264.71)
    Internet Explorer: 11.0.22000.120
  npmPackages:
    @sveltejs/adapter-auto: next => 1.0.0-next.63
    @sveltejs/kit: next => 1.0.0-next.394
    svelte: ^3.46.0 => 3.49.0
    vite: ^3.0.0 => 3.0.3

Severity

blocking an upgrade

Additional Information

Background: My Realworld App is public Facing but only the Sveltekit Server is allowed to talk to the "Secure" API. So instead of directly calling the "Secure" API from the Form, I send everything through the Sveltekit Server. This is why I need to send FromData Server-to-Server.

Used to work until undici came along ;)

garytube avatar Jul 26 '22 11:07 garytube

YES, I'm having the same exact issue. I've managed to use getReader() because it was telling me that it was a Stream and I can get the data, but everything I send is corrupted. Your mention of ASCII solves a mystery for me as the file size I could get closest to was ASCII via StringDecoder(). I assumed everything was/is UTF8.

irishburlybear avatar Jul 26 '22 11:07 irishburlybear

my workaround is to import node-fetch and replace undici in my POST Endpoints

import nodeFetch from 'node-fetch';

    const res = await nodeFetch('https://sveltekit-express-bug-tester.herokuapp.com/formdata', {
      method: 'POST',
      body: formDataFromFrontend
    });

PROPPER MULITPART CONTENT TYPE ⭐⭐⭐⭐⭐

2022-07-26T11:45:22.308100+00:00 app[web.1]: {
2022-07-26T11:45:22.308112+00:00 app[web.1]:   headers: {
2022-07-26T11:45:22.308115+00:00 app[web.1]:     host: 'sveltekit-express-bug-tester.herokuapp.com',
2022-07-26T11:45:22.308116+00:00 app[web.1]:     connection: 'close',
2022-07-26T11:45:22.308116+00:00 app[web.1]:     accept: '*/*',
2022-07-26T11:45:22.308117+00:00 app[web.1]:     'accept-encoding': 'gzip, deflate, br',
2022-07-26T11:45:22.308118+00:00 app[web.1]:     'content-type': 'multipart/form-data; boundary=----3734578923909508881256374353',
2022-07-26T11:45:22.308118+00:00 app[web.1]:     'user-agent': 'node-fetch',
2022-07-26T11:45:22.308119+00:00 app[web.1]:     'x-request-id': '7f3578c7-6963-4098-a193-7bf762561f37',
2022-07-26T11:45:22.308119+00:00 app[web.1]:     'x-forwarded-for': '------------',
2022-07-26T11:45:22.308119+00:00 app[web.1]:     'x-forwarded-proto': 'https',
2022-07-26T11:45:22.308120+00:00 app[web.1]:     'x-forwarded-port': '443',
2022-07-26T11:45:22.308120+00:00 app[web.1]:     via: '1.1 vegur',
2022-07-26T11:45:22.308120+00:00 app[web.1]:     'connect-time': '0',
2022-07-26T11:45:22.308120+00:00 app[web.1]:     'x-request-start': '1658835922308',
2022-07-26T11:45:22.308121+00:00 app[web.1]:     'total-route-time': '0',
2022-07-26T11:45:22.308121+00:00 app[web.1]:     'content-length': '218'
2022-07-26T11:45:22.308121+00:00 app[web.1]:   },
2022-07-26T11:45:22.308122+00:00 app[web.1]:   file: undefined,
2022-07-26T11:45:22.308122+00:00 app[web.1]:   body: [Object: null prototype] { text1: 'Hello', text2: 'World' }

garytube avatar Jul 26 '22 11:07 garytube

it's upstream (Undici) issue, so we can only wait until NodeJS will fix this. There is also related issue https://github.com/nodejs/undici/issues/974

Mlocik97 avatar Jul 26 '22 16:07 Mlocik97

In my opinion, this issue is not related to multipart parsing. But still this is an undici bug.

import * as undici from 'undici';
import * as nodeFetch from 'node-fetch';

function contentType({ FormData, Request }) {
	const body = new FormData();
	body.set('name', 'value');
	const request = new Request('http://localhost', { method: 'POST', body });

	return request.headers.get('content-type');
}

console.log('node FormData\nnode Request\n%s\n', contentType(globalThis));
// multipart/form-data; boundary=----formdata-undici-0.7115225130884331

console.log('undici FormData\nundici Request\n%s\n', contentType(undici));
// multipart/form-data; boundary=----formdata-undici-0.7891744173188513

console.log('node FormData\nundici Request\n%s\n', contentType({ FormData, Request: undici.Request }));
// text/plain;charset=UTF-8

console.log('node FormData\nnode-fetch Request\n%s\n', contentType({ FormData, Request: nodeFetch.Request }));
// multipart/form-data; boundary=----9692094296204514736585854209

repsac-by avatar Jul 27 '22 15:07 repsac-by

How come? Once you send FormData it break's?

garytube avatar Jul 27 '22 16:07 garytube

https://github.com/nodejs/undici/pull/1643

repsac-by avatar Sep 13 '22 12:09 repsac-by

my workaround is to import node-fetch and replace undici in my POST Endpoints

import nodeFetch from 'node-fetch';

    const res = await nodeFetch('https://sveltekit-express-bug-tester.herokuapp.com/formdata', {
      method: 'POST',
      body: formDataFromFrontend
    });

PROPPER MULITPART CONTENT TYPE ⭐⭐⭐⭐⭐

2022-07-26T11:45:22.308100+00:00 app[web.1]: {
2022-07-26T11:45:22.308112+00:00 app[web.1]:   headers: {
2022-07-26T11:45:22.308115+00:00 app[web.1]:     host: 'sveltekit-express-bug-tester.herokuapp.com',
2022-07-26T11:45:22.308116+00:00 app[web.1]:     connection: 'close',
2022-07-26T11:45:22.308116+00:00 app[web.1]:     accept: '*/*',
2022-07-26T11:45:22.308117+00:00 app[web.1]:     'accept-encoding': 'gzip, deflate, br',
2022-07-26T11:45:22.308118+00:00 app[web.1]:     'content-type': 'multipart/form-data; boundary=----3734578923909508881256374353',
2022-07-26T11:45:22.308118+00:00 app[web.1]:     'user-agent': 'node-fetch',
2022-07-26T11:45:22.308119+00:00 app[web.1]:     'x-request-id': '7f3578c7-6963-4098-a193-7bf762561f37',
2022-07-26T11:45:22.308119+00:00 app[web.1]:     'x-forwarded-for': '------------',
2022-07-26T11:45:22.308119+00:00 app[web.1]:     'x-forwarded-proto': 'https',
2022-07-26T11:45:22.308120+00:00 app[web.1]:     'x-forwarded-port': '443',
2022-07-26T11:45:22.308120+00:00 app[web.1]:     via: '1.1 vegur',
2022-07-26T11:45:22.308120+00:00 app[web.1]:     'connect-time': '0',
2022-07-26T11:45:22.308120+00:00 app[web.1]:     'x-request-start': '1658835922308',
2022-07-26T11:45:22.308121+00:00 app[web.1]:     'total-route-time': '0',
2022-07-26T11:45:22.308121+00:00 app[web.1]:     'content-length': '218'
2022-07-26T11:45:22.308121+00:00 app[web.1]:   },
2022-07-26T11:45:22.308122+00:00 app[web.1]:   file: undefined,
2022-07-26T11:45:22.308122+00:00 app[web.1]:   body: [Object: null prototype] { text1: 'Hello', text2: 'World' }

This is weird. This worked for me locally, but does not work on prod (vercel npm run preview), its sent by plain/text with no data. Any1 have any ideas why?

oyenmwen avatar Sep 26 '22 13:09 oyenmwen