"use strict"; const fs = require("node:fs"); const http = require("node:http"); const net = require("node:net"); const path = require("node:path"); const os = require("node:os"); const ROOT = __dirname; const TYPES = { ".html": "text/html; charset=utf-8", ".css": "text/css; charset=utf-8", ".js": "application/javascript; charset=utf-8" }; const APP_VERSION = "2.1.0"; function json(response, status, value) { response.writeHead(status, { "Content-Type": "application/json; charset=utf-8", "Cache-Control": "no-store" }); response.end(JSON.stringify(value)); } function clientAddress(request) { const real = String(request.headers["x-real-ip"] || "").trim(); if (net.isIP(real)) return real; const forwarded = String(request.headers["x-forwarded-for"] || "") .split(",") .map(value => value.trim()) .filter(Boolean); const last = forwarded.at(-1) || ""; if (net.isIP(last)) return last; return String(request.socket.remoteAddress || "unknown").slice(0, 64); } function createFixedWindowLimiter({ max, windowMs = 60000, now = Date.now }) { const entries = new Map(); return key => { const timestamp = now(); let entry = entries.get(key); if (!entry || timestamp >= entry.resetAt) { entry = { count: 0, resetAt: timestamp + windowMs }; entries.set(key, entry); } entry.count += 1; if (entries.size > 4096) { for (const [storedKey, stored] of entries) { if (timestamp >= stored.resetAt) entries.delete(storedKey); } if (entries.size > 4096) entries.delete(entries.keys().next().value); } return { allowed: entry.count <= max, remaining: Math.max(0, max - entry.count), retryAfter: Math.max(1, Math.ceil((entry.resetAt - timestamp) / 1000)) }; }; } function proxyCodeChannel(request, response, options = {}) { const internalPath = request.url.replace(/^\/code(?=\/|$)/, "") || "/"; const upstream = http.request({ host: options.codeChannelHost || process.env.CODE_CHANNEL_HOST || "127.0.0.1", port: Number(options.codeChannelPort || process.env.CODE_CHANNEL_PORT || 3340), method: request.method, path: internalPath, headers: { ...request.headers, host: request.headers.host || "guanghulab.com", "x-forwarded-host": request.headers["x-forwarded-host"] || request.headers.host || "guanghulab.com", "x-forwarded-proto": request.headers["x-forwarded-proto"] || "https" }, timeout: 300000 }, upstreamResponse => { response.writeHead(upstreamResponse.statusCode || 502, upstreamResponse.headers); upstreamResponse.pipe(response); }); upstream.on("timeout", () => upstream.destroy(new Error("upstream_timeout"))); upstream.on("error", () => { if (!response.headersSent) return json(response, 502, { ok: false, error: "code_channel_unavailable" }); response.destroy(); }); request.pipe(upstream); } function candidateFailureCategory(message) { const value = String(message || "").toLowerCase(); if (/urlopen|timed out|http error|temporary failure|name or service/.test(value)) return "relay_download_failed"; if (/no such file.*gpg|gpg.*no such file/.test(value)) return "signature_verifier_unavailable"; if (/fingerprint|signature|gpg/.test(value)) return "signature_verification_failed"; if (/candidate exited|candidate stopped/.test(value)) return "candidate_process_exited"; if (/readiness timeout/.test(value)) return "candidate_readiness_timeout"; return value ? "bootstrap_failed" : ""; } function readCandidateStatus(response, options = {}) { const request = http.get({ host: options.codeChannelHealthHost || "127.0.0.1", port: Number(options.codeChannelHealthPort || 3341), path: "/health", timeout: 3000 }, upstreamResponse => { let raw = ""; upstreamResponse.setEncoding("utf8"); upstreamResponse.on("data", chunk => { raw += chunk; if (raw.length > 8192) upstreamResponse.destroy(new Error("status_too_large")); }); upstreamResponse.on("end", () => { try { const source = JSON.parse(raw); return json(response, 200, { ok: source.ok === true, version: String(source.version || ""), code: String(source.code || ""), ready: source.ready === true, stage: String(source.stage || "unknown"), failure_category: source.ok === true ? "" : candidateFailureCategory(source.error) }); } catch { return json(response, 502, { ok: false, ready: false, error: "invalid_candidate_status" }); } }); }); request.on("timeout", () => request.destroy(new Error("status_timeout"))); request.on("error", () => json(response, 502, { ok: false, ready: false, error: "candidate_status_unavailable" })); } function createApp(options = {}) { const codeLimiter = createFixedWindowLimiter({ max: Number(options.codeRateLimitMax || process.env.CODE_RATE_LIMIT_MAX || 600), windowMs: Number(options.codeRateLimitWindowMs || 60000), now: options.now || Date.now }); const browseLimiter = createFixedWindowLimiter({ max: Number(options.codeBrowseRateLimitMax || process.env.CODE_BROWSE_RATE_LIMIT_MAX || 120), windowMs: Number(options.codeRateLimitWindowMs || 60000), now: options.now || Date.now }); return http.createServer((request, response) => { const url = new URL(request.url, "http://localhost"); if (url.pathname === "/code") { response.writeHead(308, { Location: "/code/", "Cache-Control": "no-store" }); return response.end(); } if (url.pathname.startsWith("/code/")) { const address = clientAddress(request); const general = codeLimiter(address); const expensive = /\/(?:archive|src\/commit|raw\/commit|blame)(?:\/|$)/.test(url.pathname) ? browseLimiter(address) : { allowed: true, remaining: general.remaining, retryAfter: general.retryAfter }; if (!general.allowed || !expensive.allowed) { response.setHeader("Retry-After", String(Math.max(general.retryAfter, expensive.retryAfter))); return json(response, 429, { ok: false, error: "code_channel_rate_limited" }); } return proxyCodeChannel(request, response, options); } if (request.method !== "GET" && request.method !== "HEAD") return json(response, 405, { ok: false, error: "method_not_allowed" }); if (url.pathname === "/api/code-channel-status") return readCandidateStatus(response, options); if (url.pathname === "/api/status") { return json(response, 200, { ok: true, node_id: process.env.GUANGHU_NODE_ID || "JD-FD-PRIMARY", role: "fifth-domain-primary", service: "jd-app-hub", version: APP_VERSION, code_channel_proxy: "loopback-only", uptime_seconds: Math.floor(process.uptime()), load_1m: Number(os.loadavg()[0].toFixed(2)), memory_used_percent: Number((((os.totalmem() - os.freemem()) / os.totalmem()) * 100).toFixed(1)), timestamp: new Date().toISOString() }); } const relative = url.pathname === "/" ? "index.html" : url.pathname.replace(/^\/+/, ""); if (!/^(index\.html|styles\.css|app\.js)$/.test(relative)) return json(response, 404, { ok: false, error: "not_found" }); const file = path.join(ROOT, relative); response.writeHead(200, { "Content-Type": TYPES[path.extname(file)] || "application/octet-stream", "Cache-Control": relative === "index.html" ? "no-cache" : "public, max-age=3600" }); fs.createReadStream(file).pipe(response); }); } if (require.main === module) { const host = process.env.HUB_HOST || "127.0.0.1"; const port = Number(process.env.HUB_PORT || 8088); createApp().listen(port, host, () => console.log(`JD app hub listening on ${host}:${port}`)); } module.exports = { createApp };