import mime from "mime"; import { attachToResponse } from "../core/cookies/index.js"; import { call as callEndpoint } from "../core/endpoint/dev/index.js"; import { throwIfRedirectNotAllowed } from "../core/endpoint/index.js"; import { AstroErrorData, isAstroError } from "../core/errors/index.js"; import { warn } from "../core/logger/core.js"; import { loadMiddleware } from "../core/middleware/loadMiddleware.js"; import { preload, renderPage } from "../core/render/dev/index.js"; import { getParamsAndProps } from "../core/render/index.js"; import { createRequest } from "../core/request.js"; import { matchAllRoutes } from "../core/routing/index.js"; import { getSortedPreloadedMatches } from "../prerender/routing.js"; import { isServerLikeOutput } from "../prerender/utils.js"; import { log404 } from "./common.js"; import { handle404Response, writeSSRResult, writeWebResponse } from "./response.js"; const clientLocalsSymbol = Symbol.for("astro.locals"); function getCustom404Route(manifest) { const route404 = /^\/404\/?$/; return manifest.routes.find((r) => route404.test(r.route)); } async function matchRoute(pathname, env, manifest) { const { logging, settings, routeCache } = env; const matches = matchAllRoutes(pathname, manifest); const preloadedMatches = await getSortedPreloadedMatches({ env, matches, settings }); for await (const { preloadedComponent, route: maybeRoute, filePath } of preloadedMatches) { try { await getParamsAndProps({ mod: preloadedComponent, route: maybeRoute, routeCache, pathname, logging, ssr: isServerLikeOutput(settings.config) }); return { route: maybeRoute, filePath, resolvedPathname: pathname, preloadedComponent, mod: preloadedComponent }; } catch (e) { if (isAstroError(e) && e.title === AstroErrorData.NoMatchingStaticPathFound.title) { continue; } throw e; } } const altPathname = pathname.replace(/(index)?\.html$/, ""); if (altPathname !== pathname) { return await matchRoute(altPathname, env, manifest); } if (matches.length) { const possibleRoutes = matches.flatMap((route) => route.component); warn( logging, "getStaticPaths", `${AstroErrorData.NoMatchingStaticPathFound.message( pathname )} ${AstroErrorData.NoMatchingStaticPathFound.hint(possibleRoutes)}` ); } log404(logging, pathname); const custom404 = getCustom404Route(manifest); if (custom404) { const filePath = new URL(`./${custom404.component}`, settings.config.root); const preloadedComponent = await preload({ env, filePath }); return { route: custom404, filePath, resolvedPathname: pathname, preloadedComponent, mod: preloadedComponent }; } return void 0; } async function handleRoute({ matchedRoute, url, pathname, status = getStatus(matchedRoute), body, origin, env, manifestData, incomingRequest, incomingResponse, manifest }) { const { logging, settings } = env; if (!matchedRoute) { return handle404Response(origin, incomingRequest, incomingResponse); } if (matchedRoute.route.type === "redirect" && !settings.config.experimental.redirects) { writeWebResponse( incomingResponse, new Response(`To enable redirect set experimental.redirects to \`true\`.`, { status: 400 }) ); return; } const { config } = settings; const filePath = matchedRoute.filePath; const { route, preloadedComponent } = matchedRoute; const buildingToSSR = isServerLikeOutput(config); const request = createRequest({ url, headers: buildingToSSR ? incomingRequest.headers : new Headers(), method: incomingRequest.method, body, logging, ssr: buildingToSSR, clientAddress: buildingToSSR ? incomingRequest.socket.remoteAddress : void 0, locals: Reflect.get(incomingRequest, clientLocalsSymbol) // Allows adapters to pass in locals in dev mode. }); for (const [name, value] of Object.entries(config.server.headers ?? {})) { if (value) incomingResponse.setHeader(name, value); } const options = { env, filePath, preload: preloadedComponent, pathname, request, route }; const middleware = await loadMiddleware(env.loader, env.settings.config.srcDir); if (middleware) { options.middleware = middleware; } if (route.type === "endpoint") { const result = await callEndpoint(options); if (result.type === "response") { if (result.response.headers.get("X-Astro-Response") === "Not-Found") { const fourOhFourRoute = await matchRoute("/404", env, manifestData); return handleRoute({ matchedRoute: fourOhFourRoute, url: new URL("/404", url), pathname: "/404", status: 404, body, origin, env, manifestData, incomingRequest, incomingResponse, manifest }); } throwIfRedirectNotAllowed(result.response, config); await writeWebResponse(incomingResponse, result.response); } else { let contentType = "text/plain"; const filepath = route.pathname || route.segments.map((segment) => segment.map((p) => p.content).join("")).join("/"); const computedMimeType = mime.getType(filepath); if (computedMimeType) { contentType = computedMimeType; } const response = new Response(Buffer.from(result.body, result.encoding), { status: 200, headers: { "Content-Type": `${contentType};charset=utf-8` } }); attachToResponse(response, result.cookies); await writeWebResponse(incomingResponse, response); } } else { const result = await renderPage(options); if (result.status === 404) { const fourOhFourRoute = await matchRoute("/404", env, manifestData); return handleRoute({ ...options, matchedRoute: fourOhFourRoute, url: new URL(pathname, url), status: 404, body, origin, env, manifestData, incomingRequest, incomingResponse, manifest }); } throwIfRedirectNotAllowed(result, config); let response = result; if (status && response.status !== status) { response = new Response(result.body, { ...result, status }); } return await writeSSRResult(request, response, incomingResponse); } } function getStatus(matchedRoute) { if (!matchedRoute) return 404; if (matchedRoute.route.route === "/404") return 404; if (matchedRoute.route.route === "/500") return 500; } export { handleRoute, matchRoute };