astro
Version:
Astro is a modern site builder with web best practices, performance, and DX front-of-mind.
247 lines (245 loc) • 8.05 kB
JavaScript
import {
DEFAULT_404_COMPONENT,
NOOP_MIDDLEWARE_HEADER,
REROUTE_DIRECTIVE_HEADER,
REWRITE_DIRECTIVE_HEADER_KEY,
clientLocalsSymbol
} from "../core/constants.js";
import { AstroErrorData, isAstroError } from "../core/errors/index.js";
import { req } from "../core/messages.js";
import { loadMiddleware } from "../core/middleware/loadMiddleware.js";
import { routeIsRedirect } from "../core/redirects/index.js";
import { RenderContext } from "../core/render-context.js";
import { getProps } from "../core/render/index.js";
import { createRequest } from "../core/request.js";
import { redirectTemplate } from "../core/routing/3xx.js";
import { matchAllRoutes } from "../core/routing/index.js";
import { PERSIST_SYMBOL } from "../core/session.js";
import { getSortedPreloadedMatches } from "../prerender/routing.js";
import { writeSSRResult, writeWebResponse } from "./response.js";
function isLoggedRequest(url) {
return url !== "/favicon.ico";
}
function getCustom404Route(manifestData) {
const route404 = /^\/404\/?$/;
return manifestData.routes.find((r) => route404.test(r.route));
}
function getCustom500Route(manifestData) {
const route500 = /^\/500\/?$/;
return manifestData.routes.find((r) => route500.test(r.route));
}
async function matchRoute(pathname, manifestData, pipeline) {
const { config, logger, routeCache, serverLike, settings } = pipeline;
const matches = matchAllRoutes(pathname, manifestData);
const preloadedMatches = await getSortedPreloadedMatches({ pipeline, matches, settings });
for await (const { preloadedComponent, route: maybeRoute, filePath } of preloadedMatches) {
try {
await getProps({
mod: preloadedComponent,
routeData: maybeRoute,
routeCache,
pathname,
logger,
serverLike,
base: config.base
});
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$/, "/").replace(/\.html$/, "");
if (altPathname !== pathname) {
return await matchRoute(altPathname, manifestData, pipeline);
}
if (matches.length) {
const possibleRoutes = matches.flatMap((route) => route.component);
logger.warn(
"router",
`${AstroErrorData.NoMatchingStaticPathFound.message(
pathname
)}
${AstroErrorData.NoMatchingStaticPathFound.hint(possibleRoutes)}`
);
}
const custom404 = getCustom404Route(manifestData);
if (custom404) {
const filePath = new URL(`./${custom404.component}`, config.root);
const preloadedComponent = await pipeline.preload(custom404, filePath);
return {
route: custom404,
filePath,
resolvedPathname: pathname,
preloadedComponent,
mod: preloadedComponent
};
}
return void 0;
}
async function handleRoute({
matchedRoute,
url,
pathname,
body,
pipeline,
manifestData,
incomingRequest,
incomingResponse
}) {
const timeStart = performance.now();
const { config, loader, logger } = pipeline;
if (!matchedRoute) {
throw new Error("No route matched, and default 404 route was not found.");
}
let request;
let renderContext;
let mod = void 0;
let route;
const middleware = (await loadMiddleware(loader)).onRequest;
const locals = Reflect.get(incomingRequest, clientLocalsSymbol);
const { preloadedComponent } = matchedRoute;
route = matchedRoute.route;
request = createRequest({
url,
headers: incomingRequest.headers,
method: incomingRequest.method,
body,
logger,
isPrerendered: route.prerender,
routePattern: route.component
});
for (const [name, value] of Object.entries(config.server.headers ?? {})) {
if (value) incomingResponse.setHeader(name, value);
}
mod = preloadedComponent;
renderContext = await RenderContext.create({
locals,
pipeline,
pathname,
middleware: isDefaultPrerendered404(matchedRoute.route) ? void 0 : middleware,
request,
routeData: route,
clientAddress: incomingRequest.socket.remoteAddress
});
let response;
let statusCode = 200;
let isReroute = false;
let isRewrite = false;
try {
response = await renderContext.render(mod);
isReroute = response.headers.has(REROUTE_DIRECTIVE_HEADER);
isRewrite = response.headers.has(REWRITE_DIRECTIVE_HEADER_KEY);
const statusCodedMatched = getStatusByMatchedRoute(matchedRoute);
statusCode = isRewrite ? (
// Ignore `matchedRoute` status for rewrites
response.status
) : (
// Our internal noop middleware sets a particular header. If the header isn't present, it means that the user have
// their own middleware, so we need to return what the user returns.
!response.headers.has(NOOP_MIDDLEWARE_HEADER) && !isReroute ? response.status : statusCodedMatched ?? response.status
);
} catch (err) {
const custom500 = getCustom500Route(manifestData);
if (!custom500) {
throw err;
}
logger.error("router", err.stack || err.message);
const filePath500 = new URL(`./${custom500.component}`, config.root);
const preloaded500Component = await pipeline.preload(custom500, filePath500);
renderContext.props.error = err;
response = await renderContext.render(preloaded500Component);
statusCode = 500;
} finally {
renderContext.session?.[PERSIST_SYMBOL]();
}
if (isLoggedRequest(pathname)) {
const timeEnd = performance.now();
logger.info(
null,
req({
url: pathname,
method: incomingRequest.method,
statusCode,
isRewrite,
reqTime: timeEnd - timeStart
})
);
}
if (statusCode === 404 && response.headers.get(REROUTE_DIRECTIVE_HEADER) !== "no") {
const fourOhFourRoute = await matchRoute("/404", manifestData, pipeline);
if (fourOhFourRoute) {
renderContext = await RenderContext.create({
locals: {},
pipeline,
pathname,
middleware: isDefaultPrerendered404(fourOhFourRoute.route) ? void 0 : middleware,
request,
routeData: fourOhFourRoute.route,
clientAddress: incomingRequest.socket.remoteAddress
});
response = await renderContext.render(fourOhFourRoute.preloadedComponent);
}
}
if (isReroute) {
response.headers.delete(REROUTE_DIRECTIVE_HEADER);
}
if (isRewrite) {
response.headers.delete(REROUTE_DIRECTIVE_HEADER);
}
if (route.type === "endpoint") {
await writeWebResponse(incomingResponse, response);
return;
}
if (isRewrite) {
await writeSSRResult(request, response, incomingResponse);
return;
}
if (response.status < 400 && response.status >= 300) {
if (response.status >= 300 && response.status < 400 && routeIsRedirect(route) && !config.build.redirects && pipeline.settings.buildOutput === "static") {
response = new Response(
redirectTemplate({
status: response.status,
location: response.headers.get("location"),
from: pathname
}),
{
status: 200,
headers: {
...response.headers,
"content-type": "text/html"
}
}
);
}
await writeSSRResult(request, response, incomingResponse);
return;
}
if (response.status !== statusCode) {
response = new Response(response.body, {
status: statusCode,
headers: response.headers
});
}
await writeSSRResult(request, response, incomingResponse);
}
function getStatusByMatchedRoute(matchedRoute) {
if (matchedRoute?.route.route === "/404") return 404;
if (matchedRoute?.route.route === "/500") return 500;
return void 0;
}
function isDefaultPrerendered404(route) {
return route.route === "/404" && route.prerender && route.component === DEFAULT_404_COMPONENT;
}
export {
handleRoute,
matchRoute
};