97 lines
2.8 KiB
JavaScript
97 lines
2.8 KiB
JavaScript
import { createReadStream } from "node:fs";
|
|
import { stat } from "node:fs/promises";
|
|
import { createServer } from "node:http";
|
|
import { extname, join, normalize } from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
import startServer from "./dist/server/server.js";
|
|
|
|
const root = fileURLToPath(new URL(".", import.meta.url));
|
|
const clientDir = join(root, "dist", "client");
|
|
const host = process.env.HOST ?? process.env.WEBSITE_LISTEN_ADDRESS ?? "127.0.0.1";
|
|
const port = Number(process.env.PORT ?? process.env.WEBSITE_LISTEN_PORT ?? "3002");
|
|
|
|
const contentTypes = new Map([
|
|
[".css", "text/css; charset=utf-8"],
|
|
[".html", "text/html; charset=utf-8"],
|
|
[".ico", "image/x-icon"],
|
|
[".js", "text/javascript; charset=utf-8"],
|
|
[".json", "application/json; charset=utf-8"],
|
|
[".png", "image/png"],
|
|
[".svg", "image/svg+xml"],
|
|
[".txt", "text/plain; charset=utf-8"],
|
|
[".woff", "font/woff"],
|
|
]);
|
|
|
|
function writeResponse(res, response) {
|
|
res.writeHead(response.status, Object.fromEntries(response.headers));
|
|
if (!response.body) {
|
|
res.end();
|
|
return;
|
|
}
|
|
response.body.pipeTo(
|
|
new WritableStream({
|
|
write(chunk) {
|
|
res.write(chunk);
|
|
},
|
|
close() {
|
|
res.end();
|
|
},
|
|
abort(error) {
|
|
res.destroy(error);
|
|
},
|
|
}),
|
|
);
|
|
}
|
|
|
|
async function tryServeStatic(req, res, pathname) {
|
|
if (pathname === "/" || !extname(pathname)) return false;
|
|
|
|
const decoded = decodeURIComponent(pathname);
|
|
const normalized = normalize(decoded).replace(/^\.\.(\/|$)/, "");
|
|
const filePath = join(clientDir, normalized);
|
|
|
|
if (!filePath.startsWith(clientDir)) return false;
|
|
|
|
try {
|
|
const fileStat = await stat(filePath);
|
|
if (!fileStat.isFile()) return false;
|
|
} catch {
|
|
return false;
|
|
}
|
|
|
|
res.writeHead(200, {
|
|
"Content-Type": contentTypes.get(extname(filePath)) ?? "application/octet-stream",
|
|
"Cache-Control": filePath.includes("/assets/")
|
|
? "public, max-age=31536000, immutable"
|
|
: "no-store",
|
|
});
|
|
createReadStream(filePath).pipe(res);
|
|
return true;
|
|
}
|
|
|
|
createServer(async (req, res) => {
|
|
try {
|
|
const origin = `http://${req.headers.host ?? `${host}:${port}`}`;
|
|
const url = new URL(req.url ?? "/", origin);
|
|
|
|
if (req.method === "GET" || req.method === "HEAD") {
|
|
const served = await tryServeStatic(req, res, url.pathname);
|
|
if (served) return;
|
|
}
|
|
|
|
const request = new Request(url, {
|
|
method: req.method,
|
|
headers: req.headers,
|
|
body: req.method === "GET" || req.method === "HEAD" ? undefined : req,
|
|
duplex: "half",
|
|
});
|
|
const response = await startServer.fetch(request);
|
|
writeResponse(res, response);
|
|
} catch (error) {
|
|
console.error(error);
|
|
res.writeHead(500, { "Content-Type": "text/plain; charset=utf-8" });
|
|
res.end("Internal Server Error");
|
|
}
|
|
}).listen(port, host, () => {
|
|
console.log(`Listening on http://${host}:${port}`);
|
|
});
|