UNPKG

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
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; }