UNPKG

astro

Version:

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

116 lines (115 loc) • 4.88 kB
import { ASTRO_ERROR_HEADER } from "../constants.js"; import { attachCookiesToResponse } from "../cookies/index.js"; import { getEnvironment } from "../environment/index.js"; import { renderErrorFromState } from "../errors/handler.js"; import { markFeatureUsed, FetchFeatures } from "../fetch/features.js"; import { applyRewriteToState } from "../rewrites/handler.js"; import { rejectInvalidEncoding } from "../routing/invalid-encoding.js"; import { callMiddleware } from "./callMiddleware.js"; import { getMiddleware } from "./load.js"; import { sequence } from "./index.js"; /** * Runs Astro's middleware chain (origin check + user `onRequest`) for a * single render, reading the composed middleware from the manifest and * per-request data (componentInstance, slots, props, API contexts) off the * supplied `FetchState`. The actual route dispatch (endpoint / redirect / * page / fallback) is supplied by the caller as `renderRouteCallback` — * typically `handlePages`. */ export async function handleMiddleware(state, renderRouteCallback) { markFeatureUsed(state.manifest, FetchFeatures.middleware); // Resolve props first (the async bit) so downstream consumers can // call `state.getAPIContext()` synchronously on the hot path. await state.getProps(); const apiContext = state.getAPIContext(); state.counter++; if (state.counter === 4) { return new Response("Loop Detected", { // https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/508 status: 508, statusText: "Astro detected a loop where you tried to call the rewriting logic more than four times." }); } const next = async (ctx, payload) => { if (payload) { state.logger.debug("router", "Called rewriting to:", payload); const result = await getEnvironment(state.manifest).tryRewrite(state.manifest, payload, state.request); applyRewriteToState(state, payload, result); } return renderRouteCallback(state, ctx); }; let response; if (state.skipMiddleware) { response = await next(apiContext); } else { const middleware = await getMiddleware(state.manifest); // The `sequence` wrapper is kept (not unwrapped to a direct call) so // `callMiddleware` semantics stay bit-identical to the previous // `sequence(...internalMiddleware, middleware)` composition (the // internal middleware list was always empty). const composed = sequence(middleware); response = await callMiddleware(composed, apiContext, next); } // LEGACY: we put cookies on the response object, // where the adapter might be expecting to read it. // New code should be using `app.render({ addCookieHeader: true })` instead. attachCookiesToResponse(response, state.cookies); state.response = response; return response; } /** * Like `handleMiddleware`, but mirrors the app-level error handling that * `handleRequest` provides on the standard path, the same way * `handlePagesWithErrorFallback` does for `pages()`. When no route matched * it returns a 404 marked with `X-Astro-Error` for the app's post-check; * when Astro's own middleware chain throws it logs the error and renders * the custom `500.astro`. * * Errors surfaced through `renderRouteCallback` (the host framework's * `next`, e.g. host middleware mounted below `middleware()`) are re-thrown * instead, so the host's own error handling still runs rather than being * swallowed into Astro's 500 page. A sentinel tells the two apart. * * Used by the composable `astro/fetch` `middleware()` entry point, where * there is no surrounding `handleRequest` to supply this fallback. */ export async function handleMiddlewareWithErrorFallback(state, renderRouteCallback) { 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). Returning a marked 404 lets the app's `X-Astro-Error` // post-check render the 404 page a level up, mirroring // `handlePagesWithErrorFallback`; running user middleware here // would throw on the missing route (no component to load). if (!state.routeData) { return new Response(null, { status: 404, headers: { [ASTRO_ERROR_HEADER]: "true" } }); } let nextError; try { return await handleMiddleware(state, async (s, ctx) => { try { return await renderRouteCallback(s, ctx); } catch (err) { nextError = err; throw err; } }); } catch (err) { if (err === nextError) throw err; // User middleware threw: log the stack and render the custom 500 // page, the same way `handleRequest` does on the standard path. state.logger.error(null, err.stack || err.message || String(err)); return renderErrorFromState(state, state.request, { ...state.renderOptions, status: 500, error: err, pathname: state.pathname }); } }