withastro / astro

The web framework for content-driven websites. ⭐️ Star to support our work!
https://astro.build
Other
46.81k stars 2.49k forks source link

Rewrite leads to 404 followed by 302 when using Server Actions in Forms #12146

Closed pothos-dev closed 4 weeks ago

pothos-dev commented 1 month ago

Astro Info

Astro                    v4.15.11
Node                     v22.6.0
System                   Windows (x64)
Package Manager          pnpm
Output                   server
Adapter                  none
Integrations             none

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

No response

Describe the Bug

I have a middleware that rewrites the URL (/post -> /with-post and /action -> /with-action).

// middleware.ts
export const onRequest = defineMiddleware(async (context, next) => {
  console.log("Incoming request", context.request.method, context.url.href)
  const { pathname, search } = context.url
  if (pathname == "/post") {
    return next(`/with-post${search}`)
  }
  if (pathname == "/action") {
    return next(`/with-action${search}`)
  }
  return next()
})

This works for regular GET and POST requests. For example, the with-post.astro page correctly handles the incoming form data:

---
// with-post.astro
let result: any = undefined
if (Astro.request.method === "POST") {
  const form = await Astro.request.formData()
  const a = parseFloat(form.get("a") as string)
  const b = parseFloat(form.get("b") as string)
  result = { data: a + b }
}
---
<html>
    <body>
        <form method="post">
            <input type="number" name="a" value="1" />
            <input type="number" name="b" value="2" />
            <button type="submit">Sum</button>
        </form>
        <p>Form result: {JSON.stringify(result)}</p>
    <body>
</html>

The server log of the GET and subsequent POST request look like this:

Incoming request GET http://localhost:4321/post
15:16:44 [200] (rewrite) /post 2ms
Incoming request POST http://localhost:4321/post
15:16:46 [200] (rewrite) POST /post 2ms

However, when using an Astro action, the rewrite does not work as expected, produces an internal 404 followed by an unwanted redirect and dropping the form data.

---
// with-action.astro
import { actions } from "astro:actions";
const result = Astro.getActionResult(actions.sum)
---

<html>
    <body>
        <form method="post" action={actions.sum}>
            <input type="number" name="a" value="1" />
            <input type="number" name="b" value="2" />
            <button type="submit">Sum</button>
        </form>
        <p>Form result: {JSON.stringify(result)}</p>

    <body>
</html>

The server log of the GET and subsequent POST request look like this:

Incoming request GET http://localhost:4321/action
15:19:09 [200] (rewrite) /action 4ms
Incoming request POST http://localhost:4321/action?_astroAction=sum
15:19:18 [404] POST /action 1ms
Incoming request GET http://localhost:4321/with-action
15:19:18 [200] /with-action 4ms

On the client, the POST request is responded with a 302 pointing to /with-action, which updates the URL in the browser.

The action implementation is as expected:

// actions.ts
import { defineAction } from "astro:actions"
import { z } from "astro:schema"

const sum = defineAction({
  accept: "form",
  input: z.object({
    a: z.number(),
    b: z.number(),
  }),
  async handler({ a, b }) {
    return a + b
  },
})

export const server = { sum }

What's the expected result?

I expect both implementations of the form to yield the same results. The action should not be influenced by the rewrite of the URL. A rewrite should not produce a 302 response.

Link to Minimal Reproducible Example

https://github.com/pothos-dev/astro-actions-rewrite

Participation

ematipico commented 1 month ago

I am not really sure what you're trying to do, however this seems to be the expected behaviour.

I need to understand why you have a /404, however the actions use a middleware under the hood, and when you call next('/with-action'), a new Request is created with /with-action as Request.pathname, and the middleware of Astro action uses that pathname when it does a redirect.

The action should not be influenced by the rewrite of the URL

I am unsure we can achieve this, considering we use a middleware here.

pothos-dev commented 1 month ago

The reason that I'm using a rewrite is this: I am trying to have different "apps" within the same Astro project. Each app lives within a separate root folder, like /pages/app1/, /pages/app2/.

The middleware inspects the requests and decides which app this particular user should be using. So a user navigates to /home and gets rewritten to /app1/home, another user would instead be rewritten to /app2/home. I want to hide the fact that there are different root folders, the users should just see the /home part of the URL.

So far, everything worked wonderfully, but after implementing an action, the issue arose.

I think the issue comes down to Astro.url pointing to the internal path of the page (e.g. /app1/home), while the user should only ever see a different url (/home). In my application code, I'm storing the "external" path as Astro.locals.pathname and use that whenever I need to deal with the URL.

Actions seem to do something under the hood that's not easy to understand for me (is there some more indepth documentation about how actions work internally?), and I can't seem to intercept to swap out internal and external paths. If the way actions are currently interacting with rewrites is the expected and correct way, I need to either avoid using actions altogether, or find another way to implement my multiple app folders within the project.

ematipico commented 1 month ago

Ok, I identified the issue and I have a PR ready, however, there's a bug of yours you should avoid. When rewriting, you should so only when ctx.request.method is GET.

At the moment, you're rewriting everything, even POST requests. A POST request is sent by your action with a Request.body. When doing a rewrite, we create a new Request and unfortunately the old body can't be carried over.

bluwy commented 1 month ago

Re-opening due to revert in https://github.com/withastro/astro/pull/12206