/** * 발행 사이트 정적 서버. * * ★ python -m http.server 를 쓰지 않는 이유 * `/s/mmg` (끝 슬래시 없음)로 들어오면 404 를 준다. 사장님이 주소창에 치는 형태가 그건데 * 열리지 않으면 "발행했는데 안 나온다"가 된다. 여기서는 디렉토리면 index.html 로 넘긴다. * * ★ 이 서버는 개발용이다. 운영에서는 out/ 을 nginx·CDN 이 그대로 서빙한다 — * 그때도 같은 규칙(디렉토리 → index.html)만 맞추면 된다. */ import {createReadStream, existsSync, statSync} from 'node:fs'; import {createServer} from 'node:http'; import {dirname, extname, join, normalize, resolve} from 'node:path'; import {fileURLToPath} from 'node:url'; const HERE = dirname(fileURLToPath(import.meta.url)); const OUT = resolve(HERE, '..', 'out'); const PORT = Number(process.env.PORT ?? 3001); const MIME = { '.html': 'text/html; charset=utf-8', '.js': 'text/javascript; charset=utf-8', '.css': 'text/css; charset=utf-8', '.json': 'application/json; charset=utf-8', '.xml': 'application/xml; charset=utf-8', '.txt': 'text/plain; charset=utf-8', '.woff2': 'font/woff2', '.svg': 'image/svg+xml', '.png': 'image/png', '.jpg': 'image/jpeg', }; createServer((req, res) => { // 한글 슬러그가 퍼센트 인코딩으로 오므로 반드시 디코드한다. const raw = decodeURIComponent((req.url ?? '/').split('?')[0]); // 경로 탈출(../) 차단 — 정적 서버의 기본 안전장치다. const rel = normalize(raw).replace(/^(\.\.[/\\])+/, ''); let file = join(OUT, rel); if (existsSync(file) && statSync(file).isDirectory()) file = join(file, 'index.html'); if (!existsSync(file)) { res.writeHead(404, {'Content-Type': 'text/html; charset=utf-8'}); res.end('
발행된 사이트가 없습니다. 발행 후 자동으로 생성됩니다.
'); return; } res.writeHead(200, {'Content-Type': MIME[extname(file)] ?? 'application/octet-stream'}); createReadStream(file).pipe(res); }).listen(PORT, () => { console.log(`[serve] 발행 사이트 → http://localhost:${PORT}/s/<주소>`); });