112 lines
3.7 KiB
JavaScript
112 lines
3.7 KiB
JavaScript
import fs from "node:fs";
|
|
import path from "node:path";
|
|
import url from "node:url";
|
|
import { hasFileExtension, isInternalPath } from "@astrojs/internal-helpers/path";
|
|
import send from "send";
|
|
function createStaticHandler(app, options) {
|
|
const client = resolveClientDir(options);
|
|
return (req, res, ssr) => {
|
|
if (req.url) {
|
|
const [urlPath, urlQuery] = req.url.split("?");
|
|
const filePath = path.join(client, app.removeBase(urlPath));
|
|
let isDirectory = false;
|
|
try {
|
|
isDirectory = fs.lstatSync(filePath).isDirectory();
|
|
} catch {
|
|
}
|
|
const { trailingSlash = "ignore" } = options;
|
|
const hasSlash = urlPath.endsWith("/");
|
|
let pathname = urlPath;
|
|
if (app.headersMap && app.headersMap.length > 0) {
|
|
const routeData = app.match(req, true);
|
|
if (routeData && routeData.prerender) {
|
|
const matchedRoute = app.headersMap.find((header) => header.pathname.includes(pathname));
|
|
if (matchedRoute) {
|
|
for (const header of matchedRoute.headers) {
|
|
res.setHeader(header.key, header.value);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
switch (trailingSlash) {
|
|
case "never": {
|
|
if (isDirectory && urlPath !== "/" && hasSlash) {
|
|
pathname = urlPath.slice(0, -1) + (urlQuery ? "?" + urlQuery : "");
|
|
res.statusCode = 301;
|
|
res.setHeader("Location", pathname);
|
|
return res.end();
|
|
}
|
|
if (isDirectory && !hasSlash) {
|
|
pathname = `${urlPath}/index.html`;
|
|
}
|
|
break;
|
|
}
|
|
case "ignore": {
|
|
if (isDirectory && !hasSlash) {
|
|
pathname = `${urlPath}/index.html`;
|
|
}
|
|
break;
|
|
}
|
|
case "always": {
|
|
if (!hasSlash && !hasFileExtension(urlPath) && !isInternalPath(urlPath)) {
|
|
pathname = urlPath + "/" + (urlQuery ? "?" + urlQuery : "");
|
|
res.statusCode = 301;
|
|
res.setHeader("Location", pathname);
|
|
return res.end();
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
pathname = prependForwardSlash(app.removeBase(pathname));
|
|
const stream = send(req, pathname, {
|
|
root: client,
|
|
dotfiles: pathname.startsWith("/.well-known/") ? "allow" : "deny"
|
|
});
|
|
let forwardError = false;
|
|
stream.on("error", (err) => {
|
|
if (forwardError) {
|
|
console.error(err.toString());
|
|
res.writeHead(500);
|
|
res.end("Internal server error");
|
|
return;
|
|
}
|
|
ssr();
|
|
});
|
|
stream.on("headers", (_res) => {
|
|
if (pathname.startsWith(`/${options.assets}/`)) {
|
|
_res.setHeader("Cache-Control", "public, max-age=31536000, immutable");
|
|
}
|
|
});
|
|
stream.on("file", () => {
|
|
forwardError = true;
|
|
});
|
|
stream.pipe(res);
|
|
} else {
|
|
ssr();
|
|
}
|
|
};
|
|
}
|
|
function resolveClientDir(options) {
|
|
const clientURLRaw = new URL(options.client);
|
|
const serverURLRaw = new URL(options.server);
|
|
const rel = path.relative(url.fileURLToPath(serverURLRaw), url.fileURLToPath(clientURLRaw));
|
|
const serverFolder = path.basename(options.server);
|
|
let serverEntryFolderURL = path.dirname(import.meta.url);
|
|
while (!serverEntryFolderURL.endsWith(serverFolder)) {
|
|
serverEntryFolderURL = path.dirname(serverEntryFolderURL);
|
|
}
|
|
const serverEntryURL = serverEntryFolderURL + "/entry.mjs";
|
|
const clientURL = new URL(appendForwardSlash(rel), serverEntryURL);
|
|
const client = url.fileURLToPath(clientURL);
|
|
return client;
|
|
}
|
|
function prependForwardSlash(pth) {
|
|
return pth.startsWith("/") ? pth : "/" + pth;
|
|
}
|
|
function appendForwardSlash(pth) {
|
|
return pth.endsWith("/") ? pth : pth + "/";
|
|
}
|
|
export {
|
|
createStaticHandler
|
|
};
|