#!/usr/bin/env node // dev-proxy.js — 本地开发反向代理 // localhost:3000 → 营销站 :3001 / Flutter Web :8888 // /app/* 路由到 Flutter,其余路由到 Eleventy // 自动把 Flutter index.html 里的 base href="/" 改写为 /app/,使静态资源请求正确走代理 const http = require('http'); const net = require('net'); const PROXY_PORT = parseInt(process.env.PROXY_PORT || '3000', 10); const ELEVENTY_PORT = parseInt(process.env.ELEVENTY_PORT || '3001', 10); const FLUTTER_PORT = parseInt(process.env.FLUTTER_PORT || '9090', 10); const BACKEND_PORT = parseInt(process.env.BACKEND_PORT || '8080', 10); function isFlutter(url) { return url === '/app' || url.startsWith('/app/'); } function isBackend(url) { return url.startsWith('/api/') || url === '/health' || url.startsWith('/images/'); } function stripApp(url) { return url.slice(4) || '/'; } function proxyHttp(req, res, port, path) { const opts = { hostname: 'localhost', port, path, method: req.method, headers: { ...req.headers, host: `localhost:${port}` }, }; const pr = http.request(opts, (upstream) => { const ct = upstream.headers['content-type'] || ''; if (port === FLUTTER_PORT && ct.includes('text/html')) { let body = ''; upstream.setEncoding('utf8'); upstream.on('data', (c) => { body += c; }); upstream.on('end', () => { // rewrite base href so Flutter asset requests stay under /app/ body = body.replace(/( res.writeHead(502).end('upstream error')); req.pipe(pr, { end: true }); } const server = http.createServer((req, res) => { if (isFlutter(req.url)) { proxyHttp(req, res, FLUTTER_PORT, stripApp(req.url)); } else if (isBackend(req.url)) { proxyHttp(req, res, BACKEND_PORT, req.url); } else { proxyHttp(req, res, ELEVENTY_PORT, req.url); } }); // WebSocket 透传(Eleventy 热重载 / Flutter DevTools) server.on('upgrade', (req, socket, head) => { const port = isFlutter(req.url) ? FLUTTER_PORT : isBackend(req.url) ? BACKEND_PORT : ELEVENTY_PORT; const path = isFlutter(req.url) ? stripApp(req.url) : req.url; const conn = net.createConnection(port, 'localhost', () => { conn.write(`${req.method} ${path} HTTP/1.1\r\n`); for (const [k, v] of Object.entries(req.headers)) { conn.write(`${k}: ${v}\r\n`); } conn.write('\r\n'); if (head && head.length) conn.write(head); socket.pipe(conn); conn.pipe(socket); }); conn.on('error', () => socket.destroy()); socket.on('error', () => conn.destroy()); }); server.listen(PROXY_PORT, () => { console.log(`[proxy] http://localhost:${PROXY_PORT}`); console.log(` / → Eleventy :${ELEVENTY_PORT}`); console.log(` /app/ → Flutter Web :${FLUTTER_PORT}`); });