/** * HF Mirror Proxy - 重写版 v2 * 改进点: * 1. LRU 缓存替代无界 Map,防止内存泄漏 * 2. 精确域名匹配,避免误替换(修复 xethub/cas-bridge CDN 问题) * 3. 缓存 key 包含 host,支持多域名部署 * 4. 结构化日志,便于排查问题 * 5. 请求限流,防止滥用 * 6. 更好的错误处理和重试机制 * 7. 流式响应优化,大文件下载更稳定 */ import * as http from "node:http"; import * as https from "node:https"; import * as crypto from "node:crypto"; import * as zlib from "node:zlib"; import { URL } from "node:url"; // ========== 配置 ========== const CONFIG = { UPSTREAM: process.env.UPSTREAM || "https://huggingface.co", PORT: parseInt(process.env.PORT) || 7860, CACHE_TTL: parseInt(process.env.CACHE_TTL) || 0, CACHE_MAX_SIZE: parseInt(process.env.CACHE_MAX_SIZE) || 5000, CACHE_MAX_BYTES: parseInt(process.env.CACHE_MAX_BYTES) || 100 * 1024 * 1024, // 100MB MAX_RETRIES: parseInt(process.env.MAX_RETRIES) || 3, PUBLIC_HOST: process.env.PUBLIC_HOST || "", UPLOAD_TOKEN_TTL: parseInt(process.env.UPLOAD_TOKEN_TTL) || 3600_000, // 1小时 UPLOAD_TOKEN_MAX: parseInt(process.env.UPLOAD_TOKEN_MAX) || 2000, RATE_LIMIT_WINDOW: parseInt(process.env.RATE_LIMIT_WINDOW) || 60_000, // 1分钟 RATE_LIMIT_MAX: parseInt(process.env.RATE_LIMIT_MAX) || 9999999, // 每分钟最大请求数 REQUEST_TIMEOUT: parseInt(process.env.REQUEST_TIMEOUT) || 300_000, // 5分钟 LOG_LEVEL: process.env.LOG_LEVEL || "info", // debug, info, warn, error }; // ========== 精确域名判断 ========== // HF 核心域名 - 可以安全替换为代理域名 const HF_HOSTS = new Set([ "huggingface.co", "www.huggingface.co", "hf.co", "www.hf.co", ]); const HF_SUFFIXES = [".huggingface.co", ".hf.co"]; // 外部 CDN / S3 桥接域名 - 虽然是 .hf.co 但不能直接替换域名 // 这些域名提供的是带签名的临时 URL,必须通过 token 代理转发 const EXTERNAL_CDN_HOSTS = new Set([ "cas-bridge.xethub.hf.co", "xethub.hf.co", "cdn.hf.co", ]); // 判断是否为外部 CDN(需要 token 代理) function isExternalCDN(hostname) { if (EXTERNAL_CDN_HOSTS.has(hostname)) return true; // 通配匹配 xethub 相关域名 if (hostname.includes("xethub") || hostname.includes("cas-bridge")) return true; return false; } // 判断是否为 HuggingFace 内部域名(可以直接替换为代理域名) function isHuggingFaceHost(hostname) { // 先排除外部 CDN if (isExternalCDN(hostname)) return false; if (HF_HOSTS.has(hostname)) return true; return HF_SUFFIXES.some((suffix) => hostname.endsWith(suffix)); } // ========== 日志系统 ========== const LOG_COLORS = { debug: "\x1b[36m", // cyan info: "\x1b[32m", // green warn: "\x1b[33m", // yellow error: "\x1b[31m", // red reset: "\x1b[0m", }; const LOG_LEVELS = { debug: 0, info: 1, warn: 2, error: 3 }; function log(level, message, meta = {}) { if (LOG_LEVELS[level] < LOG_LEVELS[CONFIG.LOG_LEVEL]) return; const timestamp = new Date().toISOString(); const color = LOG_COLORS[level] || ""; const reset = LOG_COLORS.reset; const metaStr = Object.keys(meta).length ? " " + JSON.stringify(meta) : ""; console.log(`${color}[${timestamp}] [${level.toUpperCase()}]${reset} ${message}${metaStr}`); } // ========== LRU 缓存实现 ========== class LRUCache { constructor(options) { this.max = options.max || 1000; this.maxBytes = options.maxBytes || Infinity; this.ttl = options.ttl || 0; this.map = new Map(); this.currentBytes = 0; } _makeKey(method, url, host) { return `${host}:${method}:${url}`; } get(method, url, host) { const key = this._makeKey(method, url, host); const entry = this.map.get(key); if (!entry) return null; // TTL 过期检查 if (this.ttl > 0 && Date.now() - entry.time > entry.ttl) { this._deleteEntry(key, entry); return null; } // 移动到末尾(最近使用) this.map.delete(key); this.map.set(key, entry); return entry; } set(method, url, host, status, headers, body, customTtl) { const ttl = customTtl || this.ttl; if (ttl <= 0) return false; const key = this._makeKey(method, url, host); const entry = { time: Date.now(), ttl, status, headers: { ...headers }, body, size: body?.length || 0, }; // 如果已存在,先删除旧条目 if (this.map.has(key)) { this._deleteEntry(key, this.map.get(key)); } // 空间不足时淘汰最旧的 while ( this.map.size >= this.max || (this.maxBytes !== Infinity && this.currentBytes + entry.size > this.maxBytes) ) { const firstKey = this.map.keys().next().value; if (!firstKey) break; this._deleteEntry(firstKey, this.map.get(firstKey)); } this.map.set(key, entry); this.currentBytes += entry.size; return true; } _deleteEntry(key, entry) { this.currentBytes -= entry?.size || 0; this.map.delete(key); } clear() { this.map.clear(); this.currentBytes = 0; } get stats() { return { size: this.map.size, bytes: this.currentBytes, max: this.max, maxBytes: this.maxBytes, }; } } // ========== 限流器 ========== class RateLimiter { constructor(windowMs, maxRequests) { this.windowMs = windowMs; this.maxRequests = maxRequests; this.clients = new Map(); } isAllowed(clientId) { const now = Date.now(); const record = this.clients.get(clientId); if (!record) { this.clients.set(clientId, { count: 1, resetTime: now + this.windowMs }); return { allowed: true, remaining: this.maxRequests - 1 }; } if (now > record.resetTime) { record.count = 1; record.resetTime = now + this.windowMs; return { allowed: true, remaining: this.maxRequests - 1 }; } if (record.count >= this.maxRequests) { return { allowed: false, remaining: 0, retryAfter: Math.ceil((record.resetTime - now) / 1000), }; } record.count++; return { allowed: true, remaining: this.maxRequests - record.count }; } // 定期清理过期记录 cleanup() { const now = Date.now(); for (const [id, record] of this.clients) { if (now > record.resetTime + this.windowMs) { this.clients.delete(id); } } } get stats() { return { clients: this.clients.size }; } } // ========== 上传 Token 管理(LRU) ========== class UploadTokenManager { constructor(maxSize, ttl) { this.max = maxSize; this.ttl = ttl; this.map = new Map(); } create(originalUrl, host) { // 空间不足时淘汰最旧的 while (this.map.size >= this.max) { const firstKey = this.map.keys().next().value; this.map.delete(firstKey); } const token = crypto.randomBytes(16).toString("hex"); // 32字符,更安全 this.map.set(token, { url: originalUrl, time: Date.now() }); return `https://${host}/proxy-upload/${token}`; } get(token) { const entry = this.map.get(token); if (!entry) return null; if (Date.now() - entry.time > this.ttl) { this.map.delete(token); return null; } // 更新使用时间(LRU) this.map.delete(token); this.map.set(token, entry); return entry; } clear() { this.map.clear(); } get stats() { return { size: this.map.size, max: this.max }; } } // ========== 初始化 ========== const cache = new LRUCache({ max: CONFIG.CACHE_MAX_SIZE, maxBytes: CONFIG.CACHE_MAX_BYTES, ttl: CONFIG.CACHE_TTL, }); const urlMap = new UploadTokenManager(CONFIG.UPLOAD_TOKEN_MAX, CONFIG.UPLOAD_TOKEN_TTL); const rateLimiter = new RateLimiter(CONFIG.RATE_LIMIT_WINDOW, CONFIG.RATE_LIMIT_MAX); // 定期清理限流器 setInterval(() => rateLimiter.cleanup(), 60_000); // ========== 主代理 ========== function proxyRequest(clientReq, clientRes) { const method = clientReq.method; const url = clientReq.url; const clientIp = getClientIP(clientReq); // 限流检查 const rateCheck = rateLimiter.isAllowed(clientIp); if (!rateCheck.allowed) { log("warn", "Rate limit exceeded", { ip: clientIp, url }); clientRes.writeHead(429, { "content-type": "application/json", "access-control-allow-origin": "*", "x-ratelimit-limit": String(CONFIG.RATE_LIMIT_MAX), "x-ratelimit-remaining": "0", "retry-after": String(rateCheck.retryAfter), }); clientRes.end( JSON.stringify({ error: "rate_limited", message: `Too many requests. Retry after ${rateCheck.retryAfter}s`, }) ); return; } // 上传代理路由 const uploadMatch = url.match(/^\/proxy-upload\/([a-f0-9]{32})(\/.*)?$/); if (uploadMatch) { handleUploadProxy(clientReq, clientRes, uploadMatch[1], uploadMatch[2] || ""); return; } const bodyChunks = []; clientReq.on("data", (chunk) => bodyChunks.push(chunk)); clientReq.on("end", () => { const body = Buffer.concat(bodyChunks); const host = getHost(clientReq); // 缓存命中(仅 GET 非文件下载) if (method === "GET" && !isFileDownloadUrl(url)) { const cached = cache.get(method, url, host); if (cached) { log("debug", "Cache HIT", { url, host }); clientRes.writeHead(cached.status, { ...cached.headers, "x-cache": "HIT", "access-control-allow-origin": "*", "x-ratelimit-remaining": String(rateCheck.remaining), }); clientRes.end(cached.body); return; } } doProxy(method, url, clientReq.headers, body, 0, clientRes, host); }); } function doProxy(method, url, reqHeaders, body, attempt, clientRes, host) { const upstreamUrl = new URL(url, CONFIG.UPSTREAM); const options = { method, headers: filterHeaders(reqHeaders), timeout: CONFIG.REQUEST_TIMEOUT, }; const startTime = Date.now(); const proxyReq = https.request(upstreamUrl, options, (proxyRes) => { const statusCode = proxyRes.statusCode; const respHeaders = filterProxyHeaders(proxyRes.headers); const contentType = respHeaders["content-type"] || ""; log("debug", "Upstream response", { method, url, status: statusCode, attempt: attempt + 1, }); // --- 处理重定向 --- if ([301, 302, 307, 308].includes(statusCode) && respHeaders["location"]) { handleRedirect(respHeaders["location"], upstreamUrl, host, clientRes, proxyRes, statusCode); return; } // --- 文件下载 / 流式传输 --- if (isFileDownloadUrl(url) || statusCode === 206) { clientRes.writeHead(statusCode, { ...respHeaders, "access-control-allow-origin": "*", "x-cache": "MISS", }); proxyRes.pipe(clientRes); return; } // --- 响应体需改写 --- const needsBodyRewrite = shouldRewriteBody(contentType, url); if (!needsBodyRewrite) { clientRes.writeHead(statusCode, { ...respHeaders, "access-control-allow-origin": "*", "x-cache": "MISS", }); proxyRes.pipe(clientRes); return; } // --- 缓冲后改写响应体 --- const chunks = []; proxyRes.on("data", (chunk) => chunks.push(chunk)); proxyRes.on("end", () => { let fullBody = Buffer.concat(chunks); const enc = proxyRes.headers["content-encoding"]; try { if (enc === "gzip") fullBody = zlib.gunzipSync(fullBody); else if (enc === "deflate") fullBody = zlib.inflateSync(fullBody); else if (enc === "br") { // brotli 解压 fullBody = zlib.brotliDecompressSync(fullBody); } } catch (decompressErr) { log("warn", "Decompression failed", { url, error: decompressErr.message }); // 继续使用原始 body } let newBody; const outHeaders = { ...respHeaders }; delete outHeaders["content-encoding"]; delete outHeaders["content-length"]; try { if (contentType.includes("application/json") || contentType.includes("application/vnd.git-lfs+json")) { const json = JSON.parse(fullBody.toString("utf-8")); const rewritten = rewriteBody(json, host); newBody = Buffer.from(JSON.stringify(rewritten), "utf-8"); } else if (contentType.includes("text/html")) { let html = fullBody.toString("utf-8"); html = rewriteHtml(html, host); newBody = Buffer.from(html, "utf-8"); } else { newBody = fullBody; } outHeaders["content-length"] = newBody.length; outHeaders["access-control-allow-origin"] = "*"; outHeaders["x-cache"] = "MISS"; // 设置缓存(仅 GET 200 成功响应) if (method === "GET" && statusCode === 200) { const customTtl = url.includes("/tree?") ? 60_000 : CONFIG.CACHE_TTL; cache.set(method, url, host, statusCode, outHeaders, newBody, customTtl); } clientRes.writeHead(statusCode, outHeaders); clientRes.end(newBody); } catch (err) { log("error", "Body rewrite failed", { url, error: err.message }); clientRes.writeHead(statusCode, { ...respHeaders, "access-control-allow-origin": "*", "x-cache": "MISS", }); clientRes.end(fullBody); } }); }); // --- 错误处理 --- proxyReq.on("error", (err) => { const elapsed = Date.now() - startTime; if (attempt < CONFIG.MAX_RETRIES - 1) { const wait = Math.min(1000 * 2 ** attempt, 10_000); log("warn", "Retrying request", { method, url, attempt: attempt + 1, maxRetries: CONFIG.MAX_RETRIES, error: err.message, elapsedMs: elapsed, }); setTimeout(() => doProxy(method, url, reqHeaders, body, attempt + 1, clientRes, host), wait); } else { log("error", "Upstream unreachable", { method, url, error: err.message, elapsedMs: elapsed, }); if (!clientRes.headersSent) { clientRes.writeHead(502, { "content-type": "application/json", "access-control-allow-origin": "*", }); clientRes.end( JSON.stringify({ error: "upstream_unreachable", message: err.message, }) ); } } }); proxyReq.on("timeout", () => { proxyReq.destroy(); const elapsed = Date.now() - startTime; if (attempt < CONFIG.MAX_RETRIES - 1) { log("warn", "Request timeout, retrying", { method, url, attempt: attempt + 1, elapsedMs: elapsed }); setTimeout(() => doProxy(method, url, reqHeaders, body, attempt + 1, clientRes, host), 1000); } else { log("error", "Upstream timeout", { method, url, elapsedMs: elapsed }); if (!clientRes.headersSent) { clientRes.writeHead(504, { "content-type": "application/json", "access-control-allow-origin": "*", }); clientRes.end(JSON.stringify({ error: "upstream_timeout" })); } } }); if (body.length > 0) proxyReq.write(body); proxyReq.end(); } // ========== 重定向处理 ========== function handleRedirect(location, upstreamUrl, host, clientRes, proxyRes, statusCode) { try { const loc = new URL(location, upstreamUrl); const locHostname = loc.hostname; log("debug", "Processing redirect", { original: location, hostname: locHostname, isHF: isHuggingFaceHost(locHostname), isExternalCDN: isExternalCDN(locHostname), }); if (isHuggingFaceHost(locHostname)) { // HF 内部重定向 → 替换域名 loc.hostname = host; loc.protocol = "https:"; log("info", "HF redirect rewritten", { status: statusCode, original: location, rewritten: loc.href, }); } else { // 外部 URL (S3/Xet Bridge/CDN等) → token 代理 loc.href = urlMap.create(loc.href, host); log("info", "External redirect tokenized", { status: statusCode, original: location, rewritten: loc.href, cdn: locHostname, }); } const respHeaders = filterProxyHeaders(proxyRes.headers); respHeaders["location"] = loc.href; clientRes.writeHead(statusCode, { ...respHeaders, "access-control-allow-origin": "*", }); proxyRes.pipe(clientRes); } catch (err) { log("error", "Redirect handling failed", { location, error: err.message }); clientRes.writeHead(502, { "content-type": "application/json", "access-control-allow-origin": "*", }); clientRes.end(JSON.stringify({ error: "redirect_handling_failed", message: err.message })); } } // ========== 响应体改写 ========== function rewriteBody(obj, host) { if (!obj || typeof obj !== "object") return obj; if (Array.isArray(obj)) { return obj.map((item) => rewriteBody(item, host)); } const result = {}; for (const [key, value] of Object.entries(obj)) { if (typeof value === "string") { let v = value; v = rewriteUrl(v, host); if (isExternalUploadField(key, v, host)) { v = urlMap.create(v, host); } result[key] = v; } else if (typeof value === "object" && value !== null) { result[key] = rewriteBody(value, host); } else { result[key] = value; } } return result; } function rewriteUrl(value, host) { if (typeof value !== "string") return value; // 精确替换 huggingface.co 域名 value = value.replace(/https?:\/\/huggingface\.co/g, `https://${host}`); // 精确替换已知的 HF CDN 域名(这些是可以直接替换的内部域名) value = value.replace(/https?:\/\/cdn-lfs\.hf\.co/g, `https://${host}`); value = value.replace(/https?:\/\/cdn-lfs-us-1\.hf\.co/g, `https://${host}`); value = value.replace(/https?:\/\/cdn-lfs-eu-1\.hf\.co/g, `https://${host}`); value = value.replace(/https?:\/\/cdn-lfs-ap-1\.hf\.co/g, `https://${host}`); // 注意:xethub / cas-bridge 域名不在这里替换 // 它们会在 isExternalUploadField 中通过 token 代理处理 return value; } function rewriteHtml(html, host) { if (typeof html !== "string") return html; return rewriteUrl(html, host); } function isExternalUploadField(key, value, host) { if (!value.startsWith("https://")) return false; if (value.startsWith(`https://${host}`)) return false; // 检查是否为外部 CDN URL try { const url = new URL(value); if (isExternalCDN(url.hostname)) return true; } catch { // 无效 URL,忽略 } const extFields = [ "href", "uploadUrl", "casUrl", "refreshWriteTokenUrl", "reconstructionUrl", "refreshUrl", "downloadUrl", "lfs", ]; return extFields.includes(key); } function shouldRewriteBody(contentType, url) { if (contentType.includes("application/json")) return true; if (contentType.includes("application/vnd.git-lfs+json")) return true; if (contentType.includes("text/html")) return true; if (url.includes("/preupload/")) return true; if (url.includes("/xet-write-token")) return true; return false; } function isFileDownloadUrl(url) { return url.includes("/resolve/") || url.includes("/raw/"); } // ========== 上传代理 ========== function handleUploadProxy(clientReq, clientRes, token, extraPath) { const entry = urlMap.get(token); if (!entry) { log("warn", "Unknown upload token", { token: token.slice(0, 8) + "..." }); clientRes.writeHead(404, { "content-type": "application/json", "access-control-allow-origin": "*", }); clientRes.end(JSON.stringify({ error: "unknown_upload_token" })); return; } const targetUrl = new URL(entry.url + extraPath); const bodyChunks = []; clientReq.on("data", (chunk) => bodyChunks.push(chunk)); clientReq.on("end", () => { const body = Buffer.concat(bodyChunks); const options = { method: clientReq.method, headers: filterHeaders(clientReq.headers), timeout: CONFIG.REQUEST_TIMEOUT, }; const req2 = https.request(targetUrl, options, (res2) => { const respHeaders = filterProxyHeaders(res2.headers); if ([301, 302, 307, 308].includes(res2.statusCode) && respHeaders["location"]) { try { const loc = new URL(respHeaders["location"], targetUrl); if (isHuggingFaceHost(loc.hostname)) { const host = getHost(clientReq); loc.hostname = host; loc.protocol = "https:"; respHeaders["location"] = loc.href; } } catch (err) { log("warn", "Upload redirect rewrite failed", { error: err.message }); } } clientRes.writeHead(res2.statusCode, { ...respHeaders, "access-control-allow-origin": "*", }); res2.pipe(clientRes); }); req2.on("error", (err) => { log("error", "Upload forward failed", { target: targetUrl.href, error: err.message, }); if (!clientRes.headersSent) { clientRes.writeHead(502, { "content-type": "application/json", "access-control-allow-origin": "*", }); clientRes.end( JSON.stringify({ error: "upload_forward_failed", message: err.message, }) ); } }); req2.on("timeout", () => { req2.destroy(); log("error", "Upload timeout", { target: targetUrl.href }); if (!clientRes.headersSent) { clientRes.writeHead(504, { "access-control-allow-origin": "*" }); clientRes.end(); } }); if (body.length > 0) req2.write(body); req2.end(); }); } // ========== 工具函数 ========== function getHost(req) { return ( CONFIG.PUBLIC_HOST || req.headers["x-forwarded-host"] || req.headers["host"] || `localhost:${CONFIG.PORT}` ); } function getClientIP(req) { return ( req.headers["x-forwarded-for"]?.split(",")[0]?.trim() || req.headers["x-real-ip"] || req.socket?.remoteAddress || "unknown" ); } function filterHeaders(headers) { const h = {}; const skip = new Set(["host", "connection", "x-cache", "x-ratelimit-limit", "x-ratelimit-remaining"]); for (const [k, v] of Object.entries(headers)) { if (skip.has(k.toLowerCase())) continue; h[k] = v; } if (!h["accept-encoding"]) h["accept-encoding"] = "gzip, deflate, br"; return h; } function filterProxyHeaders(headers) { const h = {}; const skip = new Set(["transfer-encoding", "connection", "keep-alive"]); for (const [k, v] of Object.entries(headers)) { if (skip.has(k.toLowerCase())) continue; h[k] = v; } return h; } // ========== HTTP 服务器 ========== const server = http.createServer((req, res) => { const startTime = Date.now(); // 响应完成后记录日志 res.on("finish", () => { const duration = Date.now() - startTime; log("info", "Request completed", { method: req.method, url: req.url, status: res.statusCode, durationMs: duration, ip: getClientIP(req), }); }); if (req.method === "OPTIONS") { res.writeHead(204, { "access-control-allow-origin": "*", "access-control-allow-methods": "GET,POST,PUT,DELETE,PATCH,OPTIONS", "access-control-allow-headers": "*", "access-control-max-age": "86400", }); res.end(); return; } if (req.url === "/" || req.url === "") { res.writeHead(200, { "content-type": "text/html; charset=utf-8" }); res.end(` HF Mirror

🤗 HF Mirror Proxy

上游: ${CONFIG.UPSTREAM}
缓存: ${cache.stats.size} 条 / ${(cache.stats.bytes / 1024 / 1024).toFixed(2)} MB
URL映射: ${urlMap.stats.size} 条
限流客户端: ${rateLimiter.stats.clients} 个
外部CDN代理: ${Array.from(EXTERNAL_CDN_HOSTS).join(", ")} (及含 xethub/cas-bridge 的域名)
`); return; } if (req.url === "/health") { res.writeHead(200, { "content-type": "application/json", "access-control-allow-origin": "*", }); res.end( JSON.stringify({ status: "ok", upstream: CONFIG.UPSTREAM, cache: cache.stats, urlMap: urlMap.stats, rateLimiter: rateLimiter.stats, uptime: process.uptime(), memory: process.memoryUsage(), }) ); return; } if (req.url === "/metrics") { res.writeHead(200, { "content-type": "application/json", "access-control-allow-origin": "*", }); res.end( JSON.stringify({ cache: cache.stats, urlMap: urlMap.stats, rateLimiter: rateLimiter.stats, uptime: process.uptime(), memory: { heapUsed: `${(process.memoryUsage().heapUsed / 1024 / 1024).toFixed(2)} MB`, heapTotal: `${(process.memoryUsage().heapTotal / 1024 / 1024).toFixed(2)} MB`, rss: `${(process.memoryUsage().rss / 1024 / 1024).toFixed(2)} MB`, external: `${(process.memoryUsage().external / 1024 / 1024).toFixed(2)} MB`, }, }) ); return; } if (req.url === "/cache/clear") { cache.clear(); urlMap.clear(); log("info", "Cache cleared manually"); res.writeHead(200, { "content-type": "application/json", "access-control-allow-origin": "*", }); res.end(JSON.stringify({ cleared: true, timestamp: new Date().toISOString() })); return; } proxyRequest(req, res); }); server.listen(CONFIG.PORT, "0.0.0.0", () => { console.log(` 🤗 HF Mirror Proxy 已启动 ───────────────────────────── → 上游: ${CONFIG.UPSTREAM} → 端口: ${CONFIG.PORT} → 公网域名: ${CONFIG.PUBLIC_HOST || "(自动检测)"} → 日志级别: ${CONFIG.LOG_LEVEL} → 缓存上限: ${CONFIG.CACHE_MAX_SIZE} 条 / ${(CONFIG.CACHE_MAX_BYTES / 1024 / 1024).toFixed(0)} MB → 限流: ${CONFIG.RATE_LIMIT_MAX} 请求/${CONFIG.RATE_LIMIT_WINDOW / 1000}s → 外部CDN: ${Array.from(EXTERNAL_CDN_HOSTS).join(", ")} → 健康检查: http://localhost:${CONFIG.PORT}/health → 指标: http://localhost:${CONFIG.PORT}/metrics → 清缓存: http://localhost:${CONFIG.PORT}/cache/clear `); }); // 优雅关闭 process.on("SIGTERM", () => { log("info", "Received SIGTERM, shutting down gracefully"); server.close(() => { log("info", "Server closed"); process.exit(0); }); }); process.on("SIGINT", () => { log("info", "Received SIGINT, shutting down gracefully"); server.close(() => { log("info", "Server closed"); process.exit(0); }); });