253 lines
8.3 KiB
JavaScript
253 lines
8.3 KiB
JavaScript
import { removeTrailingForwardSlash } from "@astrojs/internal-helpers/path";
|
|
import { BaseApp } from "../core/app/entrypoints/index.js";
|
|
import { getFirstForwardedValue, validateForwardedHeaders } from "../core/app/validate-headers.js";
|
|
import { shouldAppendForwardSlash } from "../core/build/util.js";
|
|
import { clientLocalsSymbol } from "../core/constants.js";
|
|
import {
|
|
MiddlewareNoDataOrNextCalled,
|
|
MiddlewareNotAResponse
|
|
} from "../core/errors/errors-data.js";
|
|
import { createSafeError, isAstroError } from "../core/errors/index.js";
|
|
import { createRequest } from "../core/request.js";
|
|
import { recordServerError } from "../vite-plugin-astro-server/error.js";
|
|
import { runWithErrorHandling } from "../vite-plugin-astro-server/index.js";
|
|
import { handle500Response, writeSSRResult } from "../vite-plugin-astro-server/response.js";
|
|
import { RunnablePipeline } from "./pipeline.js";
|
|
import { getCustom404Route, getCustom500Route } from "../core/routing/helpers.js";
|
|
import { ensure404Route } from "../core/routing/astro-designed-error-pages.js";
|
|
import { matchRoute } from "../core/routing/dev.js";
|
|
import { req } from "../core/messages/runtime.js";
|
|
class AstroServerApp extends BaseApp {
|
|
settings;
|
|
logger;
|
|
loader;
|
|
manifestData;
|
|
currentRenderContext = void 0;
|
|
constructor(manifest, streaming = true, logger, manifestData, loader, settings, getDebugInfo) {
|
|
super(manifest, streaming, settings, logger, loader, manifestData, getDebugInfo);
|
|
this.settings = settings;
|
|
this.logger = logger;
|
|
this.loader = loader;
|
|
this.manifestData = manifestData;
|
|
}
|
|
isDev() {
|
|
return true;
|
|
}
|
|
/**
|
|
* Updates the routes list when files change during development.
|
|
* Called via HMR when new pages are added/removed.
|
|
*/
|
|
updateRoutes(newRoutesList) {
|
|
this.manifestData = newRoutesList;
|
|
this.pipeline.setManifestData(newRoutesList);
|
|
ensure404Route(this.manifestData);
|
|
}
|
|
/**
|
|
* Clears the route cache so that getStaticPaths() is re-evaluated.
|
|
* Called via HMR when content collection data changes.
|
|
*/
|
|
clearRouteCache() {
|
|
this.pipeline.clearRouteCache();
|
|
}
|
|
/**
|
|
* Clears the cached middleware so it is re-resolved on the next request.
|
|
* Called via HMR when middleware files change.
|
|
*/
|
|
clearMiddleware() {
|
|
this.pipeline.clearMiddleware();
|
|
}
|
|
async devMatch(pathname) {
|
|
const matchedRoute = await matchRoute(
|
|
pathname,
|
|
this.manifestData,
|
|
this.pipeline,
|
|
this.manifest
|
|
);
|
|
if (!matchedRoute) {
|
|
return void 0;
|
|
}
|
|
return {
|
|
routeData: matchedRoute.route,
|
|
resolvedPathname: matchedRoute.resolvedPathname
|
|
};
|
|
}
|
|
static async create(manifest, routesList, logger, loader, settings, getDebugInfo) {
|
|
return new AstroServerApp(manifest, true, logger, routesList, loader, settings, getDebugInfo);
|
|
}
|
|
createPipeline(_streaming, manifest, settings, logger, loader, manifestData, getDebugInfo) {
|
|
const pipeline = RunnablePipeline.create(manifestData, {
|
|
loader,
|
|
logger,
|
|
manifest,
|
|
settings,
|
|
getDebugInfo
|
|
});
|
|
return pipeline;
|
|
}
|
|
async createRenderContext(payload) {
|
|
this.currentRenderContext = await super.createRenderContext(payload);
|
|
return this.currentRenderContext;
|
|
}
|
|
async handleRequest({
|
|
controller,
|
|
incomingRequest,
|
|
incomingResponse,
|
|
isHttps
|
|
}) {
|
|
const validated = validateForwardedHeaders(
|
|
getFirstForwardedValue(incomingRequest.headers["x-forwarded-proto"]),
|
|
getFirstForwardedValue(incomingRequest.headers["x-forwarded-host"]),
|
|
getFirstForwardedValue(incomingRequest.headers["x-forwarded-port"]),
|
|
this.manifest.allowedDomains
|
|
);
|
|
const protocol = validated.protocol ?? (isHttps ? "https" : "http");
|
|
const host = validated.host ?? incomingRequest.headers[":authority"] ?? incomingRequest.headers.host;
|
|
const origin = `${protocol}://${host}`;
|
|
const url = new URL(origin + incomingRequest.url);
|
|
let pathname;
|
|
if (this.manifest.trailingSlash === "never" && !incomingRequest.url) {
|
|
pathname = "";
|
|
} else {
|
|
pathname = decodeURI(url.pathname);
|
|
}
|
|
if (this.manifest.trailingSlash === "never" && pathname === "/" && this.manifest.base !== "/") {
|
|
pathname = "";
|
|
}
|
|
url.pathname = removeTrailingForwardSlash(this.manifest.base) + url.pathname;
|
|
if (url.pathname.endsWith("/") && !shouldAppendForwardSlash(this.manifest.trailingSlash, this.manifest.buildFormat)) {
|
|
url.pathname = url.pathname.slice(0, -1);
|
|
}
|
|
let body = void 0;
|
|
if (!(incomingRequest.method === "GET" || incomingRequest.method === "HEAD")) {
|
|
let bytes = [];
|
|
await new Promise((resolve) => {
|
|
incomingRequest.on("data", (part) => {
|
|
bytes.push(part);
|
|
});
|
|
incomingRequest.on("end", resolve);
|
|
});
|
|
body = Buffer.concat(bytes);
|
|
}
|
|
const self = this;
|
|
await runWithErrorHandling({
|
|
controller,
|
|
pathname,
|
|
async run() {
|
|
const matchedRoute = await self.devMatch(pathname);
|
|
if (!matchedRoute) {
|
|
throw new Error("No route matched, and default 404 route was not found.");
|
|
}
|
|
const request = createRequest({
|
|
url,
|
|
headers: incomingRequest.headers,
|
|
method: incomingRequest.method,
|
|
body,
|
|
logger: self.logger,
|
|
isPrerendered: matchedRoute.routeData.prerender,
|
|
routePattern: matchedRoute.routeData.component
|
|
});
|
|
const locals = Reflect.get(incomingRequest, clientLocalsSymbol);
|
|
for (const [name, value] of Object.entries(self.settings.config.server.headers ?? {})) {
|
|
if (value) incomingResponse.setHeader(name, value);
|
|
}
|
|
const clientAddress = incomingRequest.socket.remoteAddress;
|
|
const response = await self.render(request, {
|
|
locals,
|
|
routeData: matchedRoute.routeData,
|
|
clientAddress
|
|
});
|
|
await writeSSRResult(request, response, incomingResponse);
|
|
},
|
|
onError(_err) {
|
|
const error = createSafeError(_err);
|
|
if (self.loader) {
|
|
const { errorWithMetadata } = recordServerError(
|
|
self.loader,
|
|
self.manifest,
|
|
self.logger,
|
|
error
|
|
);
|
|
handle500Response(self.loader, incomingResponse, errorWithMetadata);
|
|
}
|
|
return error;
|
|
}
|
|
});
|
|
}
|
|
match(request, _allowPrerenderedRoutes) {
|
|
return super.match(request, true);
|
|
}
|
|
async renderError(request, {
|
|
skipMiddleware = false,
|
|
error,
|
|
status,
|
|
response: _response,
|
|
...resolvedRenderOptions
|
|
}) {
|
|
if (isAstroError(error) && [MiddlewareNoDataOrNextCalled.name, MiddlewareNotAResponse.name].includes(error.name)) {
|
|
throw error;
|
|
}
|
|
const renderRoute = async (routeData) => {
|
|
try {
|
|
const preloadedComponent = await this.pipeline.getComponentByRoute(routeData);
|
|
const renderContext = await this.createRenderContext({
|
|
locals: resolvedRenderOptions.locals,
|
|
pipeline: this.pipeline,
|
|
pathname: this.getPathnameFromRequest(request),
|
|
skipMiddleware,
|
|
request,
|
|
routeData,
|
|
clientAddress: resolvedRenderOptions.clientAddress,
|
|
status,
|
|
shouldInjectCspMetaTags: !!this.manifest.csp
|
|
});
|
|
renderContext.props.error = error;
|
|
const response = await renderContext.render(preloadedComponent);
|
|
if (error) {
|
|
this.logger.error("router", error.stack || error.message);
|
|
}
|
|
return response;
|
|
} catch (_err) {
|
|
if (skipMiddleware === false) {
|
|
return this.renderError(request, {
|
|
...resolvedRenderOptions,
|
|
status: 500,
|
|
skipMiddleware: true,
|
|
error: _err
|
|
});
|
|
}
|
|
throw _err;
|
|
}
|
|
};
|
|
if (status === 404) {
|
|
const custom404 = getCustom404Route(this.manifestData);
|
|
if (custom404) {
|
|
return renderRoute(custom404);
|
|
}
|
|
}
|
|
const custom500 = getCustom500Route(this.manifestData);
|
|
if (!custom500) {
|
|
throw error;
|
|
} else {
|
|
return renderRoute(custom500);
|
|
}
|
|
}
|
|
logRequest({ pathname, method, statusCode, isRewrite, reqTime }) {
|
|
if (pathname === "/favicon.ico") {
|
|
return;
|
|
}
|
|
this.logger.info(
|
|
null,
|
|
req({
|
|
url: pathname,
|
|
method,
|
|
statusCode,
|
|
isRewrite,
|
|
reqTime
|
|
})
|
|
);
|
|
}
|
|
}
|
|
export {
|
|
AstroServerApp
|
|
};
|