| import { createServer } from "node:http"; |
| import { createReadStream, existsSync, statSync } from "node:fs"; |
| import { extname, normalize, resolve } from "node:path"; |
| import { Readable } from "node:stream"; |
| import serverEntry from "./dist/server/server.js"; |
|
|
| const clientDir = resolve(process.cwd(), "dist/client"); |
| const port = Number(process.env.PORT || 3000); |
| const host = process.env.HOSTNAME || "0.0.0.0"; |
|
|
| const MIME_TYPES = { |
| ".css": "text/css; charset=utf-8", |
| ".gif": "image/gif", |
| ".html": "text/html; charset=utf-8", |
| ".ico": "image/x-icon", |
| ".jpeg": "image/jpeg", |
| ".jpg": "image/jpeg", |
| ".js": "text/javascript; charset=utf-8", |
| ".json": "application/json; charset=utf-8", |
| ".map": "application/json; charset=utf-8", |
| ".png": "image/png", |
| ".svg": "image/svg+xml", |
| ".txt": "text/plain; charset=utf-8", |
| ".ttf": "font/ttf", |
| ".webp": "image/webp", |
| ".woff": "font/woff", |
| ".woff2": "font/woff2", |
| ".xml": "application/xml; charset=utf-8" |
| }; |
|
|
| function getContentType(filePath) { |
| const extension = extname(filePath).toLowerCase(); |
| return MIME_TYPES[extension] || "application/octet-stream"; |
| } |
|
|
| function toHeaders(nodeHeaders) { |
| const headers = new Headers(); |
| for (const [key, value] of Object.entries(nodeHeaders)) { |
| if (typeof value === "undefined") continue; |
| if (Array.isArray(value)) { |
| for (const item of value) headers.append(key, item); |
| } else { |
| headers.set(key, value); |
| } |
| } |
| return headers; |
| } |
|
|
| function resolveStaticFile(pathname) { |
| const decoded = decodeURIComponent(pathname); |
| const normalized = normalize(decoded).replace(/^[/\\]+/, ""); |
| const absolutePath = resolve(clientDir, normalized); |
| if (!absolutePath.startsWith(clientDir)) return null; |
| if (!existsSync(absolutePath)) return null; |
| const stats = statSync(absolutePath); |
| if (!stats.isFile()) return null; |
| return absolutePath; |
| } |
|
|
| function tryServeStatic(req, res, url) { |
| if (!url.pathname || url.pathname.endsWith("/")) return false; |
| const filePath = resolveStaticFile(url.pathname); |
| if (!filePath) return false; |
|
|
| res.statusCode = 200; |
| res.setHeader("Content-Type", getContentType(filePath)); |
| if (url.pathname.startsWith("/assets/")) { |
| res.setHeader("Cache-Control", "public, max-age=31536000, immutable"); |
| } else { |
| res.setHeader("Cache-Control", "public, max-age=3600"); |
| } |
|
|
| if (req.method === "HEAD") { |
| res.end(); |
| return true; |
| } |
|
|
| createReadStream(filePath).pipe(res); |
| return true; |
| } |
|
|
| function appendSetCookie(res, value) { |
| const existing = res.getHeader("set-cookie"); |
| if (!existing) { |
| res.setHeader("set-cookie", value); |
| return; |
| } |
| if (Array.isArray(existing)) { |
| res.setHeader("set-cookie", [...existing, value]); |
| return; |
| } |
| res.setHeader("set-cookie", [String(existing), value]); |
| } |
|
|
| createServer(async (req, res) => { |
| try { |
| const hostHeader = req.headers.host || `localhost:${port}`; |
| const protocol = (req.headers["x-forwarded-proto"] || "http").toString().split(",")[0].trim(); |
| const url = new URL(req.url || "/", `${protocol}://${hostHeader}`); |
|
|
| if (tryServeStatic(req, res, url)) return; |
|
|
| const method = (req.method || "GET").toUpperCase(); |
| const hasBody = method !== "GET" && method !== "HEAD"; |
| const init = { |
| method, |
| headers: toHeaders(req.headers) |
| }; |
|
|
| if (hasBody) { |
| init.body = Readable.toWeb(req); |
| init.duplex = "half"; |
| } |
|
|
| const request = new Request(url, init); |
| const response = await serverEntry.fetch(request); |
|
|
| res.statusCode = response.status; |
| response.headers.forEach((value, key) => { |
| if (key.toLowerCase() === "set-cookie") { |
| appendSetCookie(res, value); |
| } else { |
| res.setHeader(key, value); |
| } |
| }); |
|
|
| if (method === "HEAD" || !response.body) { |
| res.end(); |
| return; |
| } |
|
|
| Readable.fromWeb(response.body).pipe(res); |
| } catch (error) { |
| console.error("Server error:", error); |
| if (!res.headersSent) { |
| res.statusCode = 500; |
| res.setHeader("Content-Type", "text/plain; charset=utf-8"); |
| } |
| res.end("Internal Server Error"); |
| } |
| }).listen(port, host, () => { |
| console.log(`Server running at http://${host}:${port}`); |
| }); |
|
|