UNPKG

astro

Version:

Astro is a modern site builder with web best practices, performance, and DX front-of-mind.

125 lines (124 loc) • 5.36 kB
import { renderEndpoint } from "../../runtime/server/endpoint.js"; import { renderPage } from "../../runtime/server/index.js"; import { ASTRO_ERROR_HEADER } from "../constants.js"; import { rejectInvalidEncoding } from "../routing/invalid-encoding.js"; import { createCrossOriginForbiddenResponse, isForbiddenCrossOriginRequest } from "../app/origin-check.js"; import { getCookiesFromResponse } from "../cookies/response.js"; import { renderErrorFromState } from "../errors/handler.js"; import { isRoute404, isRoute500 } from "../routing/internal/route-errors.js"; // Shared empty-slots object so we don't allocate `{}` on every render for // requests that don't come from the container API. Safe to share because // the slots object is read-only from the runtime's perspective. const EMPTY_SLOTS = Object.freeze({}); /** * Handles dispatch of a matched route (endpoint / redirect / page / fallback) * at the bottom of the middleware chain. This is a pure dispatch layer — it * renders whatever route the `FetchState` currently points to without any * rewrite logic. Rewrites are handled upstream: `executeRewrite()` for * `Astro.rewrite()` and `handleMiddleware` for `next(payload)`. * * `handlePages` is the `next` callback that `handleMiddleware` invokes at * the end of the middleware chain. Error handlers and the container also * use it directly for the same dispatch behavior. */ export async function handlePages(state, ctx) { const { logger, streaming } = state; state.resetResponseMetadata(); let response; const componentInstance = await state.loadComponentInstance(); switch (state.routeData.type) { case "endpoint": { response = await renderEndpoint(componentInstance, ctx, state.routeData.prerender, logger, state); break; } case "page": { const props = await state.getProps(); const actionApiContext = state.getActionAPIContext(); const result = await state.createResult(componentInstance, actionApiContext); try { response = await renderPage(result, componentInstance?.default, props, state.slots ?? EMPTY_SLOTS, streaming, state.routeData); } catch (e) { // If there is an error in the page's frontmatter or instantiation of the RenderTemplate fails midway, // we signal to the rest of the internals that we can ignore the results of existing renders and avoid kicking off more of them. result.cancelled = true; throw e; } // Signal to the i18n middleware to maybe act on this response state.responseRouteType = "page"; // Signal to the error-page-rerouting infra to let this response pass through to avoid loops if (isRoute404(state.routeData.route) || isRoute500(state.routeData.route)) { state.skipErrorReroute = true; } break; } case "redirect": { return new Response(null, { status: 404, headers: { [ASTRO_ERROR_HEADER]: "true" } }); } case "fallback": { state.responseRouteType = "fallback"; return new Response(null, { status: 500 }); } } // We need to merge the cookies from the response back into the cookies // because they may need to be passed along from a rewrite. const responseCookies = getCookiesFromResponse(response); if (responseCookies) { state.cookies.merge(responseCookies); } state.response = response; return response; } /** * Like `handlePages`, but mirrors the app-level error handling that * `handleRequest` provides on the standard path: unmatched routes * return a 404 marked with `X-Astro-Error` for the app's post-check * to render the 404 error page, and render-time errors are logged * and render the 500 error page instead of propagating to the host * framework. * * Used by the composable `astro/fetch` `pages()` entry point, where * there is no surrounding `handleRequest` to supply this fallback. */ export async function handlePagesWithErrorFallback(state) { const invalidEncodingResponse = rejectInvalidEncoding(state); if (invalidEncodingResponse) { return invalidEncodingResponse; } // `FetchState` falls back to an SSR 404 route when nothing matches, // so routeData is only missing when the custom 404 page is // prerendered (or absent). Return a marked 404 and let the app's // `X-Astro-Error` post-check render the error page a level up, // the same way the un-dispatched `redirect` case above does. if (!state.routeData) { return new Response(null, { status: 404, headers: { [ASTRO_ERROR_HEADER]: "true" } }); } const ctx = state.getAPIContext(); // The origin check normally runs in the origin-check middleware, but a // composable pipeline can dispatch here without running `middleware()` // first (or at all). Apply the same check so it holds regardless of how // the pipeline is composed. if (state.manifest.checkOrigin && isForbiddenCrossOriginRequest(ctx.request, ctx.url, ctx.isPrerendered)) { return createCrossOriginForbiddenResponse(ctx.request); } state.applyDefaultStatus(); try { await state.getProps(); return await handlePages(state, state.getAPIContext()); } catch (err) { // The header marker can't carry the error object, so render the // 500 page directly to preserve `error` and the logged stack. state.logger.error(null, err.stack || err.message || String(err)); return renderErrorFromState(state, state.request, { ...state.renderOptions, status: 500, error: err, pathname: state.pathname }); } }