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

@astro/node gcloud [ERROR] TypeError: Error: Unexpected end of multipart data #10870

Closed
1 task
rafaellucio opened this issue Apr 24, 2024 · 5 comments
Closed
1 task
Labels
needs triage Issue needs to be triaged

Comments

@rafaellucio
Copy link

Astro Info

Astro                    v4.5.17
Node                     v20.0.0
System                   macOS (arm64)
Package Manager          npm
Output                   server
Adapter                  @astrojs/node
Integrations             @astrojs/react
                         @astrojs/tailwind
                         simple-stream

If this issue only occurs in one browser, which browser is a problem?

No response

Describe the Bug

I receibe this message when I deploy my app [ERROR] TypeError: Error: Unexpected end of multipart data, following this documentation https://docs.astro.build/en/recipes/build-forms-api/#recipe

Today I use the standalone node setup in astro.config.mjs

import { defineConfig } from 'astro/config';
import react from '@astrojs/react';
import tailwind from '@astrojs/tailwind';
import node from '@astrojs/node';
import { fileURLToPath } from 'node:url';

import simpleStackStream from "simple-stack-stream";

// https://astro.build/config
export default defineConfig({
  output: 'server',
  adapter: node({
    mode: 'standalone'
  }),
  outDir: './dist',
  integrations: [react(), tailwind({
    configFile: fileURLToPath(new URL('./tailwind.config.mjs', import.meta.url))
  }), simpleStackStream()]
});

This is my API test

// my file /api/login.ts
import type { APIContext } from 'astro';

export async function POST({ request }: APIContext) {
  const formData = await request.formData();

  return new Response(
    JSON.stringify({
      email: formData.get('email'),
      password: formData.get('password'),
    }),
  );
}

export async function GET({ request }: APIContext) {
  return new Response(
    JSON.stringify({
      name: 'Astro',
      url: 'https://astro.build/',
    }),
  );
}

After call GET this works, but post doesn't works

GET /ssrdineramainsights/api/login

❯ curl --location 'https://us-central1-dinerama-2912c.cloudfunctions.net/ssrdineramainsights/api/login'

{"name":"Astro","url":"https://astro.build/"}

POST /ssrdineramainsights/api/login

curl --location 'https://us-central1-dinerama-2912c.cloudfunctions.net/ssrdineramainsights/api/login' \
-F email="my@domain.com" \
-F password="pass123" -v

* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
* h2h3 [:method: POST]
* h2h3 [:path: /ssrdineramainsights/api/login]
* h2h3 [:scheme: https]
* h2h3 [user-agent: curl/7.84.0]
* h2h3 [accept: */*]
* h2h3 [content-length: 257]
* h2h3 [content-type: multipart/form-data; boundary=------------------------03196803d06dcd43]
* Using Stream ID: 1 (easy handle 0x12f811800)
> POST /ssrdineramainsights/api/login HTTP/2
> user-agent: curl/7.84.0
> accept: */*
> content-length: 257
> content-type: multipart/form-data; boundary=------------------------03196803d06dcd43
>
* We are completely uploaded and fine
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
* old SSL session ID is stale, removing
< HTTP/2 500
< x-cloud-trace-context: 077bcf6d7925f41efa7d4329bcc97534;o=1
< date: Wed, 24 Apr 2024 14:34:18 GMT
< content-type: text/html
< server: Google Frontend
< content-length: 0
< alt-svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000
<
* Connection #0 to host us-central1-dinerama-2912c.cloudfunctions.net left intact

The log em Google Cloud Function is:

[ERROR] TypeError: Error: Unexpected end of multipart data

Screenshot 2024-04-24 at 11 50 28

What's the expected result?

{
  email: 'my@domain.com',
  password: 'pass123',
}

Link to Minimal Reproducible Example

https://stackblitz.com/edit/github-ao77yt?file=src%2Fpages%2Findex.astro,src%2Fpages%2Fapi%2Flogin.ts,package.json

Participation

  • I am willing to submit a pull request for this issue.
@github-actions github-actions bot added the needs triage Issue needs to be triaged label Apr 24, 2024
@matthewp
Copy link
Contributor

If you remove simple-stack-stream do you still have the issue?

@matthewp matthewp added needs response Issue needs response from OP and removed needs triage Issue needs to be triaged labels Apr 24, 2024
@rafaellucio
Copy link
Author

In this sample https://stackblitz.com/edit/github-ao77yt?file=src%2Fpages%2Findex.astro,src%2Fpages%2Fapi%2Flogin.ts

I don't use simple-stack-stream and receive the similar error when submit data

And I remove simple-stack-stream

import { defineConfig } from 'astro/config';
import react from '@astrojs/react';
import tailwind from '@astrojs/tailwind';
import node from '@astrojs/node';
import { fileURLToPath } from 'node:url';

// import simpleStackStream from "simple-stack-stream";

// https://astro.build/config
export default defineConfig({
  output: 'server',
  adapter: node({
    mode: 'standalone',
  }),
  outDir: './dist',
  integrations: [
    react(),
    tailwind({
      configFile: fileURLToPath(
        new URL('./tailwind.config.mjs', import.meta.url),
      ),
    }),
  ],
});

But receive de same error 😢

image

@rafaellucio
Copy link
Author

rafaellucio commented Apr 24, 2024

In this sample https://stackblitz.com/edit/github-ao77yt?file=src%2Fpages%2Findex.astro,src%2Fpages%2Fapi%2Flogin.ts

....


This error is very very strange, because when I run build and run locally

❯ node dist/server/entry.mjs

Works!!, But after deploy doesn't works

I use this actions to deploy my app in gcloud https://github.com/FirebaseExtended/action-hosting-deploy

@rafaellucio
Copy link
Author

rafaellucio commented Apr 27, 2024

@matthewp I identified the problem, when this action-hosting-deploy use the CLI to create an api function in Google Cloud Function this function use an library called firebase-frameworks, this library haven't support to application/x-www-form-urlencoded, when Astro run context.request.formData() the formData don't exists.

Related issue

Basically add a simple form like this:

<form action="/api/test" method="POST">
  <input name="user" />
  <input name="pass" />
  <button>submit</button>
</form>

When I use Astro the content type of the request context receives an application/x-www-form-urlencoded and to obtain these values ​​I need to parse my request to FormData using request.formData() as request.json() and added a new method in the request context called formData

My workarround was create an Astro middleware and use the busboy library to obtain values from request, like this

import { defineMiddleware } from 'astro:middleware';
import Busboy from 'busboy';

const getFieldsFromFormData = (headers: any, body: any) =>
  new Promise(async (resolve) => {
    const busboy = Busboy({ headers });
    let fields: any = {};

    busboy.on('field', (field: string, val: any) => {
      fields = JSON.parse(field);
    });
    busboy.on('finish', () => resolve(fields));
    busboy.end(body);
  });

export const formBody = defineMiddleware(async (context, next) => {
  const req = context.request.clone();
  const headers = Object.fromEntries(req.headers);

  if (
    req.method === 'POST' &&
    req.headers.get('content-type') === 'application/x-www-form-urlencoded'
  ) {
    try {
      const text = await req.text();
      const fields: any = await getFieldsFromFormData(headers, text);

      context.request.formData = async function () {
        return {
          ...fields,
          get: (key: string) => fields[key] ?? '',
        };
      };
    } catch (err) {
      console.error(err);
    }
  }
  return next();
});

This works but it's a really bad solution 😢

I look this library firebase-framework-tools/astro maybe can create an Github Actions, and use CLI to publish function using this library

@matthewp
Copy link
Contributor

matthewp commented May 3, 2024

Ah ok, x-www-form-urlencoded is not the right content type for FormData. Instead you want to use URLSearchParams like so let params = new URLSearchParams(await request.text()).

@matthewp matthewp closed this as completed May 3, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
needs triage Issue needs to be triaged
Projects
None yet
Development

No branches or pull requests

2 participants