Wyle.Gong-巩文昕 7a1aae1e2f ui
2025-04-23 11:21:08 +08:00

415 lines
10 KiB
JavaScript

/*
* Copyright 2020 The NATS Authors
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
const path = require("path");
const { check } = require("./delay");
const {
deferred,
delay,
timeout,
nuid,
} = require("../../lib/nats-base-client/internal_mod");
const { spawn } = require("child_process");
const fs = require("fs");
const os = require("os");
const net = require("net");
const ServerSignals = new Map();
ServerSignals.set("KILL", "SIGKILL");
ServerSignals.set("QUIT", "SIGQUIT");
ServerSignals.set("STOP", "SIGSTOP");
ServerSignals.set("REOPEN", "SIGUSR1");
ServerSignals.set("RELOAD", "SIGHUP");
ServerSignals.set("LDM", "SIGUSR2");
exports.tlsConfig = () => {
const dir = process.cwd();
return {
cert_file: path.resolve(path.join(dir, "./test/certs/localhost.crt")),
key_file: path.resolve(path.join(dir, "./test/certs/localhost.key")),
ca_file: path.resolve(path.join(dir, "./test/certs/ca.crt")),
};
};
exports.wsConfig = () => {
return {
websocket: {
no_tls: true,
port: -1,
},
};
};
function parseHostport(s) {
if (!s) {
return;
}
const idx = s.indexOf("://");
if (idx) {
s = s.slice(idx + 3);
}
const [hostname, ps] = s.split(":");
const port = parseInt(ps, 10);
return { hostname, port };
}
function parsePorts(ports) {
ports.monitoring = ports.monitoring || [];
ports.cluster = ports.cluster || [];
ports.websocket = ports.websocket || [];
const listen = parseHostport(ports.nats[0]);
const p = {};
if (listen) {
p.hostname = listen.hostname;
p.port = listen.port;
}
const cluster = ports.cluster.map((v) => {
if (v) {
return parseHostport(v)?.port;
}
return undefined;
});
p.cluster = cluster[0];
const monitoring = ports.monitoring.map((v) => {
if (v) {
return parseHostport(v)?.port;
}
return undefined;
});
p.monitoring = monitoring[0];
const websocket = ports.websocket.map((v) => {
if (v) {
return parseHostport(v)?.port;
}
return undefined;
});
p.websocket = websocket[0];
return p;
}
exports.NatsServer = class NatsServer {
hostname;
clusterName;
port;
cluster;
monitoring;
websocket;
process;
logBuffer = [];
stopped = false;
done;
debug;
config;
constructor(opts = {
info: {
hostname: "",
port: 0,
cluster: 0,
monitoring: 0,
websocket: 0,
clusterName: "",
},
process: undefined,
debug: false,
config: {},
}) {
const { info, process, debug, config } = opts;
this.hostname = info.hostname;
this.port = info.port;
this.cluster = info.cluster;
this.monitoring = info.monitoring;
this.websocket = info.websocket;
this.clusterName = info.clusterName;
this.process = process;
this.done = deferred();
this.config = config;
this.process.stderr.on("data", (data) => {
data = data.toString();
this.logBuffer.push(data);
if (debug) {
debug.log(data);
}
});
this.process.on("exit", () => {
this.done.resolve();
});
}
restart() {
const conf = JSON.parse(JSON.stringify(this.config));
conf.port = this.port;
if (this.websocket) {
conf.websocket.port = this.websocket;
}
return NatsServer.start(conf, this.debug);
}
getLog() {
return this.logBuffer.join("");
}
static stopAll(cluster) {
const buf = [];
cluster.forEach((s) => {
buf.push(s.stop());
});
return Promise.all(buf);
}
async stop() {
if (!this.stopped) {
this.stopped = true;
await this.signal("SIGTERM");
}
await this.done;
}
signal(signal) {
const sn = ServerSignals.get(signal);
this.process.kill(sn ? sn : signal);
}
async varz() {
if (!this.monitoring) {
return Promise.reject(new Error("server is not monitoring"));
}
const resp = await fetch(`http://127.0.0.1:${this.monitoring}/varz`);
return await resp.json();
}
static async cluster(
count = 2,
conf = {},
debug = false,
) {
conf = conf || {};
conf = Object.assign({}, conf);
conf.cluster = conf.cluster || {};
conf.cluster.name = nuid.next();
conf.cluster.listen = conf.cluster.listen || "127.0.0.1:-1";
const ns = await NatsServer.start(conf, debug);
const cluster = [ns];
for (let i = 1; i < count; i++) {
const s = await NatsServer.addClusterMember(ns, conf, debug);
cluster.push(s);
}
return cluster;
}
static async start(conf = {}, debug = undefined) {
const exe = process.env.CI
? "/home/runner/work/nats.ws/nats.ws/nats-server/nats-server"
: "nats-server";
const tmp = path.resolve(process.env.TMPDIR || ".");
let srv;
return new Promise(async (resolve, reject) => {
try {
conf = conf || {};
conf.ports_file_dir = tmp;
conf.host = conf.host || "127.0.0.1";
conf.port = conf.port || -1;
conf.http = conf.http || "127.0.0.1:-1";
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "nats-"));
const confFile = path.join(dir, "server.conf");
fs.writeFileSync(confFile, toConf(conf));
if (debug) {
debug.log(`${exe} -c ${confFile}`);
}
srv = await spawn(exe, ["-c", confFile]);
if (debug) {
debug.log(`[${srv.pid}] - launched`);
}
const portsFile = path.resolve(
path.join(tmp, `nats-server_${srv.pid}.ports`),
);
const pi = await check(
() => {
try {
const data = fs.readFileSync(portsFile);
const txt = new TextDecoder().decode(data);
const d = JSON.parse(txt);
if (d) {
return d;
}
} catch (_) {
}
},
1000,
{ name: "read ports file" },
);
if (debug) {
debug.log(`[${srv.pid}] - ports file found`);
}
const ports = parsePorts(pi);
if (conf.cluster?.name) {
ports.clusterName = conf.cluster.name;
}
await check(
async () => {
const d = deferred();
try {
if (debug) {
debug.log(`[${srv.pid}] - attempting to connect`);
}
const tc = net.createConnection(ports.port, ports.hostname);
tc.on("connect", () => {
tc.destroy();
d.resolve(ports.port);
});
} catch (err) {
return d.reject(err);
}
return d;
},
5000,
{ name: "wait for server" },
);
resolve(
new NatsServer(
{ info: ports, process: srv, debug: debug, config: conf },
),
);
} catch (err) {
if (srv) {
try {
debug.log(srv.stderrOutput);
} catch (err) {
debug.log("unable to read server output:", err);
}
}
reject(err);
}
});
}
static async addClusterMember(
ns,
conf = {},
debug = false,
) {
if (ns.cluster === undefined) {
return Promise.reject(new Error("no cluster port on server"));
}
conf = conf || {};
conf = Object.assign({}, conf);
conf.port = -1;
conf.cluster = conf.cluster || {};
conf.cluster.name = ns.clusterName;
conf.cluster.listen = conf.cluster.listen || "127.0.0.1:-1";
conf.cluster.routes = [`nats://${ns.hostname}:${ns.cluster}`];
return NatsServer.start(conf, debug);
}
static async localClusterFormed(servers) {
const ports = servers.map((s) => s.port);
const fn = async function (s) {
const dp = deferred();
const to = timeout(5000);
let done = false;
to.catch((err) => {
done = true;
dp.reject(
new Error(
`${s.hostname}:${s.port} failed to resolve peers: ${err.toString}`,
),
);
});
while (!done) {
const data = await s.varz();
if (data) {
const urls = data.connect_urls;
const others = urls.map((s) => {
return parseHostport(s)?.port;
});
if (others.every((v) => ports.includes(v))) {
dp.resolve();
to.cancel();
break;
}
}
await delay(100);
}
return dp;
};
const proms = servers.map((s) => fn(s));
return Promise.all(proms);
}
};
function toConf(o = {}, indent = "") {
const pad = indent !== undefined ? indent + " " : "";
const buf = [];
for (const k in o) {
if (Object.prototype.hasOwnProperty.call(o, k)) {
//@ts-ignore: tsc,
const v = o[k];
if (Array.isArray(v)) {
buf.push(`${pad}${k} [`);
buf.push(toConf(v, pad));
buf.push(`${pad} ]`);
} else if (typeof v === "object") {
// don't print a key if it is an array and it is an index
const kn = Array.isArray(o) ? "" : k;
buf.push(`${pad}${kn} {`);
buf.push(toConf(v, pad));
buf.push(`${pad} }`);
} else {
if (!Array.isArray(o)) {
if (
typeof v === "string" && v.startsWith("$JS.")
) {
buf.push(`${pad}${k}: "${v}"`);
} else if (
typeof v === "string" && v.charAt(0) >= "0" && v.charAt(0) <= "9"
) {
buf.push(`${pad}${k}: "${v}"`);
} else {
buf.push(`${pad}${k}: ${v}`);
}
} else {
buf.push(pad + v);
}
}
}
}
return buf.join("\n");
}
exports.toConf = toConf;