astro
Version:
Astro is a modern site builder with web best practices, performance, and DX front-of-mind.
68 lines (67 loc) • 3.1 kB
JavaScript
import { createCrossOriginForbiddenResponse, isForbiddenCrossOriginRequest } from "../core/app/origin-check.js";
import { markFeatureUsed, FetchFeatures } from "../core/fetch/features.js";
import { rejectInvalidEncoding } from "../core/routing/invalid-encoding.js";
import { getActionContext, serializeActionResult } from "./runtime/server.js";
/**
* Handles Astro Action requests in both modes:
*
* - **RPC**: POST requests to `/_actions/<name>` (originating from the
* generated JS client). Runs the action and returns the serialized result
* as the response, so the caller can short-circuit rendering.
* - **Form**: POST requests with `?_action=<name>` targeting a page route
* (originating from an HTML `<form action={actions.foo}>`). Runs the
* action, stashes the result into `locals._actionPayload`, and returns
* `undefined` so the caller continues to render the page.
*
* Non-action requests are a no-op (`undefined`).
*
* This handler is invoked at the bottom of the middleware chain, before
* page dispatch. That placement preserves the existing behavior where
* user middleware sees action requests and response finalization (cookies,
* sessions, etc.) runs around the action response.
*
* Expects the APIContext that is already being used by the render pipeline.
* Returns a `Response` when the action fully handles the request (RPC),
* or `undefined` when the caller should continue processing the request
* (form actions or non-action requests).
*/
export function handleAction(apiContext, state) {
markFeatureUsed(state.manifest, FetchFeatures.actions);
const invalidEncodingResponse = rejectInvalidEncoding(state);
if (invalidEncodingResponse) {
return Promise.resolve(invalidEncodingResponse);
}
if (apiContext.isPrerendered) {
return undefined;
}
const { action, setActionResult } = getActionContext(apiContext);
if (!action) {
return undefined;
}
// The origin check normally runs in the origin-check middleware, but the
// action dispatch can run before that middleware depending on how the
// pipeline is composed. Apply the same check here so it holds regardless
// of ordering.
if (state.manifest.checkOrigin && isForbiddenCrossOriginRequest(apiContext.request, apiContext.url, apiContext.isPrerendered)) {
return Promise.resolve(createCrossOriginForbiddenResponse(apiContext.request));
}
return executeAction(action, setActionResult);
}
async function executeAction(action, setActionResult) {
const actionResult = await action.handler();
const serialized = serializeActionResult(actionResult);
if (action.calledFrom === "rpc") {
if (serialized.type === "empty") {
return new Response(null, { status: serialized.status });
}
return new Response(serialized.body, {
status: serialized.status,
headers: { "Content-Type": serialized.contentType }
});
}
// Form action: stash the result in locals and let the caller continue
// to render the page. A subsequent call to `getActionContext` during
// page rendering will see the stored payload and skip re-running.
setActionResult(action.name, serialized);
return undefined;
}