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

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

Closed
1 task
pothos-dev opened this issue Oct 7, 2024 · 4 comments · Fixed by #12173 or #12235
Closed
1 task

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

pothos-dev opened this issue Oct 7, 2024 · 4 comments · Fixed by #12173 or #12235
Assignees
Labels
- P3: minor bug An edge case that only affects very specific usage (priority) feat: routing Related to Astro routing (scope) needs triage Issue needs to be triaged

Comments

@pothos-dev
Copy link

pothos-dev commented Oct 7, 2024

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

  • 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 Oct 7, 2024
@pothos-dev pothos-dev changed the title Rewrite leads to 404 when using Server Actions in Forms Rewrite leads to 404 followed by 302 when using Server Actions in Forms Oct 7, 2024
@ematipico ematipico self-assigned this Oct 7, 2024
@ematipico
Copy link
Member

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
Copy link
Author

pothos-dev commented Oct 7, 2024

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
Copy link
Member

ematipico commented Oct 9, 2024

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.

@ematipico ematipico added - P3: minor bug An edge case that only affects very specific usage (priority) feat: routing Related to Astro routing (scope) and removed needs triage Issue needs to be triaged labels Oct 9, 2024
@bluwy
Copy link
Member

bluwy commented Oct 12, 2024

Re-opening due to revert in #12206

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
- P3: minor bug An edge case that only affects very specific usage (priority) feat: routing Related to Astro routing (scope) needs triage Issue needs to be triaged
Projects
None yet
3 participants