CH-J Server Manager通过 SSH 管理服务器
菜单
已发布的源代码

CH-J Server Manager

浏览特定应用版本的目录和文件。

下载源代码 ZIP
CH-J / Server Manager 377 个文件
core-20261019T111616Z-160a00bfcddc 添加时间: · 构建时间:
diagnosticsHttp.test.js 添加时间: 未记录日期 14.5 KB · 144 行
代码 下载文件
1"use strict";
2const test = require("node:test");
3const assert = require("node:assert/strict");
4const http = require("node:http");
5const http2 = require("node:http2");
6const tls = require("node:tls");
7const net = require("node:net");
8const crypto = require("node:crypto");
9const zlib = require("node:zlib");
10const fs = require("node:fs");
11const path = require("node:path");
12const { once } = require("node:events");
13const { Server } = require("ssh2");
14const { parseTarget, normalizeOptions } = require("../src/main/diagnostics/common");
15const { HttpDiagnostics, nativeRequest, http3Request, getCurlCapability, MAX_BODY, securityHeaders, decodeBody, decoderInfo } = require("../src/main/diagnostics/httpDiagnostics");
16const { validateRuntime } = require("../src/main/diagnostics/httpRuntime");
17const { tlsProbe, tcpProbe, websocketProbe, sshIdentification } = require("../src/main/diagnostics/socketDiagnostics");
18const fixtures = path.join(__dirname, "fixtures", "diagnostics");
19const key = fs.readFileSync(path.join(fixtures, "server-key.pem")), cert = fs.readFileSync(path.join(fixtures, "server.pem")), ca = fs.readFileSync(path.join(fixtures, "ca.pem"));
20const signal = () => new AbortController().signal;
21const settings = { timeoutMs: 1000 };
22async function listen(t, server, host = "127.0.0.1") {
23 const sockets = new Set(); server.on("connection", (socket) => { sockets.add(socket); socket.once("close", () => sockets.delete(socket)); });
24 server.listen(0, host); await once(server, "listening");
25 t.after(async () => { for (const socket of sockets) { if (socket.destroy) socket.destroy(); else socket.end(); } await new Promise((resolve) => server.close(resolve)); }); return server.address().port;
27const dns = { resolve: async () => [{ address: "127.0.0.1", family: 4 }] };
28test("HTTP/1.1 measures actual bytes, headers, cold/warm reuse and bounded redirect chains", async (t) => {
29 const server = http.createServer((req, res) => {
30 if (req.url === "/loop") { res.writeHead(302, { location: "/loop" }); return res.end(); }
31 if (req.url === "/redirect") { res.writeHead(301, { location: "/ok" }); return res.end(); }
32 res.writeHead(200, { "content-type": "text/plain", "set-cookie": "password=private", "content-security-policy": "frame-ancestors 'none'" }); res.end("measured body");
33 });
34 const port = await listen(t, server), service = new HttpDiagnostics(dns, { native: true }), target = parseTarget(`http://localhost:${port}/redirect`);
35 const options = normalizeOptions({ target: target.url, tools: ["http"], protocols: ["1.1"], repetitions: 3, warm: true });
36 const result = await service.run(target, "127.0.0.1", options, signal(), () => {}), samples = result.comparisons[0].samples;
37 assert.equal(samples.length, 3); assert.equal(samples[0].negotiated, "1.1"); assert.equal(samples[0].downloadedBytes, 13);
38 assert.equal(samples[0].redirectCount, 1); assert.equal(samples[0].headers["set-cookie"], "[redacted]");
39 assert.ok(samples[1].reused); assert.equal(samples[1].timings.tcpMs, null); assert.ok(samples[0].timings.totalMs > 0);
40 assert.ok(!("_body" in samples[0])); assert.equal(result.comparisons[0].errorRate, 0);
41 const resources = service.resources();
42 try { await assert.rejects(service.request(parseTarget(`http://localhost:${port}/loop`), "127.0.0.1", "1.1", options, signal(), resources), { code: "REDIRECT_LOOP" }); }
43 finally { service.dispose(resources); }
44 assert.equal(securityHeaders({ "content-security-policy": "frame-ancestors 'none'" }, true).find((h) => h.name === "x-frame-options").status, "superseded-by-csp-frame-ancestors");
45});
46test("HTTP/2 uses real h2 TLS ALPN, verifies certificates and reuses the negotiated session", async (t) => {
47 const server = http2.createSecureServer({ key, cert }); server.on("sessionError", () => {}); server.on("stream", (stream) => { stream.respond({ ":status": 200, "content-type": "text/plain" }); stream.end("h2 payload"); });
48 const port = await listen(t, server), service = new HttpDiagnostics(dns, { ca, native: true }), target = parseTarget(`https://localhost:${port}/`);
49 const options = normalizeOptions({ target: target.url, tools: ["http"], protocols: ["2"], repetitions: 3, warm: true });
50 const result = await service.run(target, "127.0.0.1", options, signal(), () => {}), samples = result.comparisons[0].samples;
51 assert.equal(samples[0].negotiated, "2"); assert.equal(samples[0].alpn, "h2"); assert.equal(samples[0].downloadedBytes, 10);
52 assert.equal(samples[0].reused, false); assert.equal(samples[1].reused, true); assert.equal(samples[2].timings.tlsMs, null);
53 await assert.rejects(nativeRequest(new URL(target.url), "127.0.0.1", "2", { ...settings, signal: signal() }));
54});
55test("HTTP timeout, cancellation and body cap terminate local requests", async (t) => {
56 const server = http.createServer((req, res) => { if (req.url === "/large") res.end(Buffer.alloc(MAX_BODY + 1)); });
57 const port = await listen(t, server);
58 await assert.rejects(nativeRequest(new URL(`http://127.0.0.1:${port}/large`), "127.0.0.1", "1.1", { ...settings, signal: signal() }), { code: "BODY_LIMIT" });
59 await assert.rejects(nativeRequest(new URL(`http://127.0.0.1:${port}/hang`), "127.0.0.1", "1.1", { timeoutMs: 30, signal: signal() }), { code: "TIMEOUT" });
60 const controller = new AbortController(), request = nativeRequest(new URL(`http://127.0.0.1:${port}/hang`), "127.0.0.1", "1.1", { ...settings, signal: controller.signal });
61 controller.abort(); await assert.rejects(request, { code: "CANCELLED" });
62});
63test("compression confirms gzip/deflate/Brotli using real decompression and rejects ignored Accept-Encoding", async (t) => {
64 const body = Buffer.from("compression test ".repeat(100));
65 const server = http.createServer((req, res) => {
66 const encoding = req.headers["accept-encoding"];
67 const compress = { gzip: zlib.gzipSync, deflate: zlib.deflateSync, br: zlib.brotliCompressSync, zstd: zlib.zstdCompressSync }[encoding];
68 if (req.url !== "/ignored" && compress) { res.setHeader("content-encoding", encoding); res.end(compress(body)); } else res.end(body);
69 });
70 const port = await listen(t, server), service = new HttpDiagnostics(dns, { native: true });
71 for (const ignored of [false, true]) {
72 const target = parseTarget(`http://localhost:${port}/${ignored ? "ignored" : "ok"}`), options = normalizeOptions({ target: target.url, tools: ["compression"] });
73 const result = await service.compression(target, "127.0.0.1", options, signal(), () => {}), br = result.results.find((r) => r.encoding === "br");
74 assert.equal(br.status, ignored ? "unsupported" : "success"); if (!ignored) { assert.equal(br.decodedBytes, body.length); assert.ok(br.ratio < 1); assert.equal(br.comparable, true); }
75 }
76});
77test("HTTP/3 requires HTTP3-enabled curl, --http3-only and an actual version 3 result; fallback is rejected", async () => {
78 const url = new URL("https://localhost/"), calls = [];
79 const metrics = { http_version: "3", response_code: 200, remote_ip: "127.0.0.1", remote_port: 443, size_download: 4, content_type: "text/plain", ssl_verify_result: 0, time_appconnect: 0.02, time_starttransfer: 0.03, time_total: 0.04, speed_download: 100 };
80 const runner = async (_exe, args) => { calls.push(args); return args.includes("--version") ? { stdout: Buffer.from("curl 8.10.0 test\nFeatures: SSL HTTP2 HTTP3\n") } : { code: 0, stdout: Buffer.from("HTTP/3 103\r\nlink: </early.css>\r\n\r\nHTTP/3 200\r\ncontent-type: text/plain\r\n\r\nbody\nCHJ_METRICS:" + JSON.stringify(metrics)), stderr: "" }; };
81 const result = await http3Request(url, "127.0.0.1", settings, signal(), runner);
82 assert.equal(result.negotiated, "3"); assert.equal(result.headers["content-type"], "text/plain"); assert.equal(result.headers.link, undefined); assert.equal(result.timings.tcpMs, null); assert.equal(result.timings.quicHandshakeMs, 20);
83 assert.ok(calls[1].includes("--http3-only")); assert.ok(!calls[1].includes("--http3")); assert.ok(!calls[1].includes("--insecure")); assert.equal(calls[1][0], "--disable");
84 metrics.http_version = "2"; assert.equal((await http3Request(url, "127.0.0.1", settings, signal(), runner)).code, "HTTP3_FALLBACK_REJECTED");
85 assert.equal((await http3Request(url, "127.0.0.1", settings, signal(), async () => ({ stdout: Buffer.from("curl 8.7.1\nFeatures: SSL HTTP2\n") }))).code, "HTTP_RUNTIME_INCOMPATIBLE");
86 metrics.http_version = "3";
87 await http3Request(new URL("https://localhost:444/"), "127.0.0.1", settings, signal(), runner);
88 assert.ok(calls.at(-1).includes("localhost:444:127.0.0.1"));
89 assert.equal((await getCurlCapability(async () => { throw Object.assign(new Error("missing"), { code: "ENOENT" }); })).available, false);
90});
91test("TLS inspection differentiates trust, hostname mismatch and expiration without changing global verification", async (t) => {
92 const server = tls.createServer({ key, cert }), expiredServer = tls.createServer({ key, cert: fs.readFileSync(path.join(fixtures, "expired.pem")) });
93 const port = await listen(t, server), expiredPort = await listen(t, expiredServer), target = parseTarget(`https://localhost:${port}/`), prior = process.env.NODE_TLS_REJECT_UNAUTHORIZED;
94 const valid = await tlsProbe(target, "127.0.0.1", settings, signal(), "TLSv1.3", ca); assert.equal(valid.valid, true); assert.equal(valid.chain[0].publicKeyBits, 2048); assert.ok(valid.chain[0].signatureAlgorithm); assert.ok(valid.chain[0].fingerprint256);
95 const mismatch = await tlsProbe({ ...target, host: "mismatch.test" }, "127.0.0.1", settings, signal(), "TLSv1.2", ca); assert.equal(mismatch.status, "invalid"); assert.ok(mismatch.errors.includes("ERR_TLS_CERT_ALTNAME_INVALID"));
96 const untrusted = await tlsProbe(target, "127.0.0.1", settings, signal(), "TLSv1.3"); assert.equal(untrusted.valid, false);
97 const expired = await tlsProbe({ ...target, port: expiredPort }, "127.0.0.1", settings, signal(), "TLSv1.3", ca); assert.equal(expired.valid, false); assert.ok(expired.errors.includes("CERT_HAS_EXPIRED")); assert.ok(expired.chain[0].daysUntilExpiration < 0);
98 assert.equal(process.env.NODE_TLS_REJECT_UNAUTHORIZED, prior);
99});
100test("TCP reports actual connected/refused states and WebSocket validates the Upgrade accept hash", async (t) => {
101 const tcp = net.createServer(), tcpPort = await listen(t, tcp); assert.equal((await tcpProbe("127.0.0.1", tcpPort, settings, signal())).status, "connected");
102 const closed = net.createServer(); closed.listen(0, "127.0.0.1"); await once(closed, "listening"); const closedPort = closed.address().port; await new Promise((r) => closed.close(r));
103 assert.equal((await tcpProbe("127.0.0.1", closedPort, settings, signal())).status, "refused");
104 const ws = http.createServer((_req, res) => res.end());
105 ws.on("upgrade", (req, socket) => { const accept = crypto.createHash("sha1").update(req.headers["sec-websocket-key"] + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11").digest("base64"); socket.write("HTTP/1.1 101 Switching Protocols\r\nConnection: Upgrade\r\nUpgrade: websocket\r\nSec-WebSocket-Accept: " + (req.url === "/bad" ? "invalid" : accept) + "\r\n\r\n"); });
106 const port = await listen(t, ws);
107 assert.equal((await websocketProbe(parseTarget(`ws://localhost:${port}/ok`), "127.0.0.1", settings, signal())).valid, true);
108 assert.equal((await websocketProbe(parseTarget(`ws://localhost:${port}/bad`), "127.0.0.1", settings, signal())).valid, false);
109});
110test("SSH fingerprint is observed from a real handshake before any authentication request", async (t) => {
111 let authentications = 0;
112 const server = new Server({ hostKeys: [crypto.createPrivateKey(key).export({ type: "pkcs1", format: "pem" })] }, (client) => { client.on("error", () => {}); client.on("authentication", (ctx) => { authentications++; ctx.reject(); }); });
113 server.on("error", () => {}); const port = await listen(t, server);
114 const result = await sshIdentification("127.0.0.1", { timeoutMs: 2000, sshPort: port }, signal());
115 assert.match(result.fingerprint, /^SHA256:/); assert.equal(result.authenticated, false); assert.equal(authentications, 0);
116});
117test("IPv6 HTTP and HTTPS are measured over a real local IPv6 socket", async (t) => {
118 const server = http.createServer((_req, res) => res.end("v6")); let port;
119 try { port = await listen(t, server, "::1"); } catch (error) { if (["EAFNOSUPPORT", "EADDRNOTAVAIL"].includes(error.code)) return t.skip("IPv6 loopback unavailable"); throw error; }
120 const response = await nativeRequest(new URL(`http://[::1]:${port}/`), "::1", "1.1", { ...settings, signal: signal() });
121 assert.equal(response.serverIp, "::1"); assert.equal(response.downloadedBytes, 2); assert.equal(response.negotiated, "1.1");
122 const secureServer = require("node:https").createServer({ key, cert }, (_req, res) => res.end("v6 TLS"));
123 const securePort = await listen(t, secureServer, "::1");
124 const secure = await nativeRequest(new URL(`https://[::1]:${securePort}/`), "::1", "1.1", { ...settings, ca, signal: signal() });
125 assert.equal(secure.status, "success"); assert.equal(secure.serverIp, "::1"); assert.ok(secure.timings.tlsMs > 0);
126});
127test("deflate accepts zlib-wrapped and raw DEFLATE; decoders come from the bundled runtime", async () => {
128 const body = Buffer.from("raw deflate ".repeat(50));
129 assert.deepEqual(await decodeBody(zlib.deflateSync(body), "deflate", signal()), body);
130 assert.deepEqual(await decodeBody(zlib.deflateRawSync(body), "deflate", signal()), body);
131 const info = decoderInfo(); assert.equal(info.runtime, "bundled"); assert.ok(info.encodings.includes("br")); assert.ok(info.brotli);
132});
133test("compression through the bundled curl runtime decodes gzip/deflate/Brotli/zstd without OS codecs", async (t) => {
134 try { validateRuntime(); } catch { t.skip("bundled HTTP runtime for this platform is not present"); return; }
135 const body = Buffer.from("bundled compression ".repeat(400));
136 const server = http.createServer((req, res) => {
137 const encoding = req.headers["accept-encoding"], compress = { gzip: zlib.gzipSync, deflate: zlib.deflateRawSync, br: zlib.brotliCompressSync, zstd: zlib.zstdCompressSync }[encoding];
138 if (compress) { res.setHeader("content-encoding", encoding); res.end(compress(body)); } else res.end(body);
139 });
140 const port = await listen(t, server), service = new HttpDiagnostics(dns);
141 const target = parseTarget(`http://127.0.0.1:${port}/`), result = await service.compression(target, "127.0.0.1", normalizeOptions({ target: target.url, tools: ["compression"] }), signal(), () => {});
142 for (const encoding of ["identity", "gzip", "deflate", "br", "zstd"]) { const entry = result.results.find((r) => r.encoding === encoding); assert.equal(entry.status, "success", encoding); assert.equal(entry.decodedBytes, body.length); assert.equal(entry.comparable, true); }
143 assert.equal(result.status, "success"); assert.equal(result.decoder.runtime, "bundled");
144});
SHA-256b0d389f5349417e68cb67d1e4adf673f9a269dc4a1d730141172773d5f9926f7

CH-J Proprietary Software License 1.14 · 源代码依据 CH-J Proprietary Software License 1.14 提供。公开源代码不会改变许可条款,也不会授予额外权利。

压缩包 SHA-256a9605c0870a223b6a8542bb0705edd51d8b04f3069582653417a8397fabcc09a
文件