Files
OpenFlare/openflare_server/web/scripts/dev-server.mjs
T
2026-05-30 13:15:20 +08:00

178 lines
4.0 KiB
JavaScript

import http from 'node:http';
import https from 'node:https';
import next from 'next';
const port = Number.parseInt(process.env.PORT ?? '3001', 10);
const hostname = process.env.HOSTNAME || '127.0.0.1';
const backendBaseUrl = new URL(
(process.env.NEXT_DEV_BACKEND_URL || 'http://127.0.0.1:3000').replace(
/\/+$/,
'',
),
);
const app = next({
dev: true,
hostname,
port,
});
let handle;
let handleUpgrade;
function isApiRequest(url = '') {
return url.startsWith('/api/');
}
function createProxyRequestOptions(req, targetUrl) {
const headers = {
...req.headers,
host: targetUrl.host,
};
if (headers.origin) {
headers.origin = targetUrl.origin;
}
return {
protocol: targetUrl.protocol,
hostname: targetUrl.hostname,
port: targetUrl.port || (targetUrl.protocol === 'https:' ? 443 : 80),
method: req.method,
path: `${targetUrl.pathname}${targetUrl.search}`,
headers,
};
}
function writeProxyError(res, error) {
res.statusCode = 502;
res.setHeader('Content-Type', 'application/json; charset=utf-8');
res.end(
JSON.stringify({
success: false,
message: `开发代理连接后端失败: ${error.message}`,
}),
);
}
function proxyHttpRequest(req, res) {
const targetUrl = new URL(req.url || '/', backendBaseUrl);
const requestImpl =
targetUrl.protocol === 'https:' ? https.request : http.request;
const proxyReq = requestImpl(
createProxyRequestOptions(req, targetUrl),
(proxyRes) => {
res.writeHead(
proxyRes.statusCode || 502,
proxyRes.statusMessage,
proxyRes.headers,
);
proxyRes.pipe(res);
},
);
proxyReq.on('error', (error) => {
if (!res.headersSent) {
writeProxyError(res, error);
return;
}
res.destroy(error);
});
req.pipe(proxyReq);
}
function formatUpgradeResponseHeaders(statusCode, statusMessage, headers) {
const lines = [`HTTP/1.1 ${statusCode} ${statusMessage}`];
for (const [key, value] of Object.entries(headers)) {
if (value === undefined) {
continue;
}
if (Array.isArray(value)) {
for (const item of value) {
lines.push(`${key}: ${item}`);
}
continue;
}
lines.push(`${key}: ${value}`);
}
lines.push('', '');
return lines.join('\r\n');
}
function proxyWebSocketUpgrade(req, socket, head) {
const targetUrl = new URL(req.url || '/', backendBaseUrl);
const requestImpl =
targetUrl.protocol === 'https:' ? https.request : http.request;
const proxyReq = requestImpl(createProxyRequestOptions(req, targetUrl));
proxyReq.on('upgrade', (proxyRes, proxySocket, proxyHead) => {
socket.write(
formatUpgradeResponseHeaders(
proxyRes.statusCode || 101,
proxyRes.statusMessage || 'Switching Protocols',
proxyRes.headers,
),
);
if (head.length > 0) {
proxySocket.write(head);
}
if (proxyHead.length > 0) {
socket.write(proxyHead);
}
proxySocket.pipe(socket).pipe(proxySocket);
proxySocket.on('error', () => socket.destroy());
socket.on('error', () => proxySocket.destroy());
});
proxyReq.on('response', (proxyRes) => {
socket.write(
formatUpgradeResponseHeaders(
proxyRes.statusCode || 502,
proxyRes.statusMessage || 'Bad Gateway',
proxyRes.headers,
),
);
proxyRes.pipe(socket);
});
proxyReq.on('error', () => {
socket.destroy();
});
proxyReq.end();
}
await app.prepare();
handle = app.getRequestHandler();
handleUpgrade = app.getUpgradeHandler();
const server = http.createServer((req, res) => {
if (isApiRequest(req.url)) {
proxyHttpRequest(req, res);
return;
}
handle(req, res);
});
server.on('upgrade', (req, socket, head) => {
if (isApiRequest(req.url)) {
proxyWebSocketUpgrade(req, socket, head);
return;
}
handleUpgrade(req, socket, head);
});
server.listen(port, hostname, () => {
console.log(
`OpenFlare web dev server listening on http://${hostname}:${port}`,
);
console.log(
`Proxying /api/* and websocket upgrades to ${backendBaseUrl.href}`,
);
});