Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cannot read form data with readFormData on vercel edge #1721

Closed
pi0 opened this issue Sep 12, 2023 · 9 comments
Closed

cannot read form data with readFormData on vercel edge #1721

pi0 opened this issue Sep 12, 2023 · 9 comments
Assignees

Comments

@pi0
Copy link
Member

pi0 commented Sep 12, 2023

Moving from #1718 reported by @cosbgn

Minimal reproduction:

routes/index.ts:

export default defineEventHandler(async (event) => {
  if (event.method === "POST") {
    return await readFormData(event)
      .then((r) => r.get("name"))
      .catch((e) => e.stack);
  }
  return `
  <script type="module">
    console.log('Fetching...')
    const data = new FormData();
    data.append("name", "John Doe");
    const res = await fetch("/", { method: 'POST', body: data });
    const text = await res.text();
    console.log('Fetch complete: ' + text) 
    alert(text)
  </script>
  <h1>Check Console</h1>
`;
});

(checking console, response never handles)

Workaround

You can use the older readMultipartFormData utility. It returns an array of form datas:

export default defineEventHandler(async (event) => {
  if (event.method === "POST") {
    return await readMultipartFormData(event)
      .then((data) =>
        data.find((d) => d.name === "name")?.data.toString("utf8")
      )
      .catch((e) => e.stack);
  }
  return `
  <script type="module">
    console.log('Fetching...')
    const data = new FormData();
    data.append("name", "John Doe");
    const res = await fetch("/", { method: 'POST', body: data });
    const text = await res.text();
    console.log('Fetch complete: ' + text)
    alert(text)
  </script>
  <h1>Check Console</h1>
`;
});

https://nitro-ppspi2qf2-pi0.vercel.app/

@passionate-bram
Copy link
Contributor

This also occurs on Netlify:

export default defineEventHandler(async event => {
  if (event.method !== "POST") {
    throw createError({statusCode:405, statusMessage: 'Method not allowed'});
  }
  let form: FormData;
  try {
    console.log('readFormData::enter');
    form = await readFormData(event);
    console.log('readFormData::exit');
  } catch (cause) {
    console.log('readFormData::error');
    throw createError({
      statusCode: 400,
      statusMessage: 'Request has no FormData',
      cause,
    });
  }
  return form;
});

Output (via Logs > Functions on Netlify's admin panel)

Nov 20, 02:46:34 PM: INIT_START Runtime Version: nodejs:18.v18	Runtime Version ARN: arn:aws:lambda:us-east-2::runtime:(snip)
Nov 20, 02:46:34 PM: (snip) INFO   readFormData::enter
Nov 20, 02:46:34 PM: (snip) Duration: 39.99 ms	Memory Usage: 78 MB	Init Duration: 215.70 ms	

The result is a 502 with message:

error decoding lambda response: invalid status code returned from lambda: 0

There are two major issues here:

  1. The catch clause is not triggered, leading me to believe the entire process crashed.
  2. By no means can the error be caught.

With this severity of a crash, I'd honestly prefer the readFormData function be replaced with an always throwing function. That would not change the absence of it's functionality and you would gain back the ability to recover from the failure.
Also, the developer(s) do not need to go around in circles trying out different small changes to their code to hunt this bug down.

@passionate-bram
Copy link
Contributor

Using console.log(event), the event.node.req.body property holds the actual body of the request already.

Reviewing the code for h3, specifically src/utils/body.ts, it seems there are wildly different ways of parsing the request. I wouldn't be surprised if some of the functions there would actually be able to parse the request properly.

For example, readRawBody does reach for the event.node.req.body property. While the readFormData does not.
I'll see if I can find a variant that works on netlify and then the toWebRequest or readFormData may be modified to use that approach as well.

@passionate-bram
Copy link
Contributor

This is a fixed version of readFormData that will work on netlify, and perhaps also vercel (given that it is also AWS based):

async function readFormData_fixed(event: H3Event) : Promise<FormData> {
  const request = new Request(getRequestURL(event), {
    // @ts-ignore Undici option
    duplex: 'half',
    method: event.method,
    headers: event.headers,
    body: await readRawBody(event),
  } as RequestInit);
  return await request.formData();
}

@besscroft
Copy link

I ran into a similar problem with Netlify: unjs/h3#590

@fezproof

This comment was marked as off-topic.

@AirBorne04

This comment was marked as off-topic.

@pi0

This comment was marked as off-topic.

@AirBorne04

This comment was marked as off-topic.

@pi0
Copy link
Member Author

pi0 commented Oct 21, 2024

This issue is already fixed with unjs/h3#616 (h3@1.10.1)

I have implemented an end-to-end test which passes on edge platforms:

Note: Tests are against unmodified Nitro behavior.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants