Files
jiu/scripts/dev-proxy.js
T
wangjia 75e3b934bc
Deploy / build-windows (push) Failing after 9s
Deploy / build-linux-web (push) Successful in 51s
Deploy / release-deploy (push) Has been skipped
chore: release v1.0.3
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 22:39:04 +08:00

99 lines
3.2 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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(/(<base\s+href=")[^"]*(")/i, '$1/app/$2');
const headers = { ...upstream.headers };
headers['content-length'] = Buffer.byteLength(body);
delete headers['transfer-encoding'];
res.writeHead(upstream.statusCode, headers);
res.end(body);
});
} else {
res.writeHead(upstream.statusCode, upstream.headers);
upstream.pipe(res, { end: true });
}
});
pr.on('error', () => 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}`);
});