0.2.1 onlyoffice修复
This commit is contained in:
@@ -0,0 +1,173 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* 自定义 Next dev server:
|
||||
* - 解决 ONLYOFFICE 在 `/onlyoffice-server/*` 下的 WebSocket Upgrade 需求(socket.io / coauthoring)。
|
||||
* - Next App Router 的 Route Handler 无法处理 Upgrade,因此必须在 Node http server 层做透传。
|
||||
*
|
||||
* 用法(保持与 next dev 类似):
|
||||
* - pnpm dev -p 3000
|
||||
* - node scripts/dev-server.js -p 3000
|
||||
*
|
||||
* 依赖环境变量:
|
||||
* - ONLYOFFICE_INTERNAL_URL:默认 http://127.0.0.1:8081
|
||||
*/
|
||||
|
||||
const http = require("http");
|
||||
const net = require("net");
|
||||
const path = require("path");
|
||||
const next = require("next");
|
||||
const { parse: parseUrl } = require("url");
|
||||
|
||||
const ONLYOFFICE_PREFIX = "/onlyoffice-server";
|
||||
|
||||
function readArgValue(flag) {
|
||||
const idx = process.argv.findIndex((x) => x === flag);
|
||||
if (idx === -1) return null;
|
||||
const v = process.argv[idx + 1];
|
||||
if (!v || v.startsWith("-")) return null;
|
||||
return v;
|
||||
}
|
||||
|
||||
function resolvePort() {
|
||||
const fromArg = readArgValue("-p") || readArgValue("--port");
|
||||
const raw = fromArg || process.env.PORT || "3000";
|
||||
const n = Number(raw);
|
||||
return Number.isFinite(n) ? Math.max(1, Math.min(65535, Math.floor(n))) : 3000;
|
||||
}
|
||||
|
||||
function resolveHostname() {
|
||||
return readArgValue("-H") || readArgValue("--hostname") || process.env.HOSTNAME || "0.0.0.0";
|
||||
}
|
||||
|
||||
function isOnlyOfficePath(urlString) {
|
||||
try {
|
||||
const u = new URL(urlString, "http://localhost");
|
||||
return u.pathname === ONLYOFFICE_PREFIX || u.pathname.startsWith(`${ONLYOFFICE_PREFIX}/`);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function buildUpstreamRequestHead(req, targetUrl) {
|
||||
const incoming = new URL(req.url || "/", "http://localhost");
|
||||
const rawPath = incoming.pathname || "/";
|
||||
const stripped = rawPath === ONLYOFFICE_PREFIX ? "/" : rawPath.slice(ONLYOFFICE_PREFIX.length) || "/";
|
||||
|
||||
const basePath = String(targetUrl.pathname || "/").replace(/\/+$/, "") || "";
|
||||
const upstreamPath = `${basePath}${stripped}`.replace(/\/{2,}/g, "/") + (incoming.search || "");
|
||||
|
||||
const lines = [];
|
||||
lines.push(`${req.method || "GET"} ${upstreamPath} HTTP/1.1`);
|
||||
|
||||
const headers = req.headers || {};
|
||||
for (const [k, v] of Object.entries(headers)) {
|
||||
if (!v) continue;
|
||||
const key = String(k);
|
||||
if (key.toLowerCase() === "host") continue;
|
||||
if (Array.isArray(v)) {
|
||||
lines.push(`${key}: ${v.join(", ")}`);
|
||||
} else {
|
||||
lines.push(`${key}: ${String(v)}`);
|
||||
}
|
||||
}
|
||||
|
||||
// 说明:Host 必须指向 ONLYOFFICE_INTERNAL_URL,否则上游可能拒绝 Upgrade。
|
||||
lines.push(`Host: ${targetUrl.host}`);
|
||||
lines.push("");
|
||||
lines.push("");
|
||||
return lines.join("\r\n");
|
||||
}
|
||||
|
||||
function proxyOnlyOfficeUpgrade(req, socket, head) {
|
||||
const target = new URL((process.env.ONLYOFFICE_INTERNAL_URL || "http://127.0.0.1:8081").replace(/\/+$/, "") + "/");
|
||||
const port = Number(target.port) || (target.protocol === "https:" ? 443 : 80);
|
||||
|
||||
const upstream = net.connect({ host: target.hostname, port }, () => {
|
||||
try {
|
||||
const reqHead = buildUpstreamRequestHead(req, target);
|
||||
upstream.write(reqHead);
|
||||
if (head && head.length > 0) upstream.write(head);
|
||||
socket.pipe(upstream);
|
||||
upstream.pipe(socket);
|
||||
} catch (e) {
|
||||
try {
|
||||
socket.destroy();
|
||||
} catch {}
|
||||
try {
|
||||
upstream.destroy();
|
||||
} catch {}
|
||||
}
|
||||
});
|
||||
|
||||
const onError = (err) => {
|
||||
try {
|
||||
const msg = err && err.message ? String(err.message) : String(err || "");
|
||||
// eslint-disable-next-line no-console
|
||||
console.log("[dev-server][onlyoffice-ws] proxy error", msg);
|
||||
} catch {}
|
||||
try {
|
||||
socket.destroy();
|
||||
} catch {}
|
||||
try {
|
||||
upstream.destroy();
|
||||
} catch {}
|
||||
};
|
||||
|
||||
upstream.on("error", onError);
|
||||
socket.on("error", onError);
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const port = resolvePort();
|
||||
const hostname = resolveHostname();
|
||||
const dev = true;
|
||||
|
||||
const app = next({ dev, dir: path.join(__dirname, "..") });
|
||||
const handle = app.getRequestHandler();
|
||||
|
||||
await app.prepare();
|
||||
// 说明:Next dev 的 HMR 依赖 WebSocket(/_next/webpack-hmr),需要交给 Next 自己处理 upgrade。
|
||||
const handleUpgrade = typeof app.getUpgradeHandler === "function" ? app.getUpgradeHandler() : null;
|
||||
|
||||
const server = http.createServer((req, res) => {
|
||||
try {
|
||||
res.setHeader("x-mnote-dev-server", "1");
|
||||
res.setHeader("x-mnote-onlyoffice-ws-proxy", "1");
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
const parsed = parseUrl(req.url || "/", true);
|
||||
handle(req, res, parsed);
|
||||
});
|
||||
|
||||
server.on("upgrade", (req, socket, head) => {
|
||||
if (isOnlyOfficePath(req.url || "/")) {
|
||||
try {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log("[dev-server][onlyoffice-ws] upgrade", req.url);
|
||||
} catch {}
|
||||
proxyOnlyOfficeUpgrade(req, socket, head);
|
||||
return;
|
||||
}
|
||||
if (handleUpgrade) {
|
||||
handleUpgrade(req, socket, head);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
socket.destroy();
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
});
|
||||
|
||||
server.listen(port, hostname, () => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(`[dev-server] ready http://${hostname}:${port} (ONLYOFFICE ws via ${ONLYOFFICE_PREFIX} -> ${process.env.ONLYOFFICE_INTERNAL_URL || "http://127.0.0.1:8081"})`);
|
||||
});
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(err instanceof Error ? err.stack : String(err));
|
||||
process.exit(1);
|
||||
});
|
||||
Reference in New Issue
Block a user