You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

122 lines
4.2 KiB
JavaScript

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

/**
* 企业信息查询接口调试代理零依赖Node >= 14
*
* 用途:
* 1. 托管同目录下的 index.html访问 http://localhost:5500
* 2. 将其余请求原样转发到后端 127.0.0.1:8081并在响应上附加 CORS 头,
* 规避浏览器跨域限制,方便前端页面直接联调。
*
* 启动: node proxy.js
* 自定义PORT=5501 TARGET_PORT=9090 node proxy.js
*/
"use strict";
const http = require("http");
const https = require("https");
const fs = require("fs");
const path = require("path");
const PORT = Number(process.env.PORT) || 9527;
const TARGET_HOST = process.env.TARGET_HOST || "127.0.0.1";
const TARGET_PORT = Number(process.env.TARGET_PORT) || 8081;
const CX_HOST = process.env.CX_HOST || "api-cx.dingtax.cn";
const PUB_HOST = process.env.PUB_HOST || "apit-dsb.dingtax.cn";
const MIME = {
".html": "text/html; charset=utf-8",
".js": "text/javascript; charset=utf-8",
".css": "text/css; charset=utf-8",
".json": "application/json; charset=utf-8",
".png": "image/png",
".svg": "image/svg+xml",
".ico": "image/x-icon"
};
function corsHeaders() {
return {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET,POST,PUT,DELETE,PATCH,OPTIONS",
"Access-Control-Allow-Headers": "Content-Type, Authorization, X-Requested-With"
};
}
const server = http.createServer((req, res) => {
const cors = corsHeaders();
if (req.method === "OPTIONS") {
res.writeHead(204, cors);
return res.end();
}
// 静态文件:/ 及 /index.html其余静态路径按扩展名尝试读取
const urlPath = decodeURIComponent((req.url || "/").split("?")[0]);
if (req.method === "GET" && !urlPath.startsWith("/dsb/") && !urlPath.startsWith("/cx/") && !urlPath.startsWith("/pub/")) {
let file = path.join(__dirname, urlPath === "/" ? "index.html" : urlPath);
if (!file.startsWith(__dirname)) file = path.join(__dirname, "index.html");
fs.readFile(file, (err, buf) => {
if (err) {
res.writeHead(404, { ...cors, "Content-Type": "text/plain; charset=utf-8" });
return res.end("404 Not Found: " + urlPath);
}
res.writeHead(200, { ...cors, "Content-Type": MIME[path.extname(file)] || "application/octet-stream" });
res.end(buf);
});
return;
}
// /cx/* → 公网 api-cx.dingtax.cn (HTTPS)/dsb/* → 本机后端 (HTTP)
const isCx = urlPath.startsWith("/cx/");
const isPub = urlPath.startsWith("/pub/");
const qs = (req.url || "").split("?").slice(1).join("?");
// apit-dsb 后端会校验 Origin/RefererInvalid CORS request / Rap csrf Verify转发前剥离浏览器来源头
const fwdHeaders = { ...req.headers };
["origin", "referer", "sec-fetch-site", "sec-fetch-mode", "sec-fetch-dest", "sec-fetch-user", "accept-language"].forEach(h => delete fwdHeaders[h]);
const options = isCx
? {
host: CX_HOST,
port: 443,
path: req.url,
method: req.method,
headers: { ...fwdHeaders, host: CX_HOST },
rejectUnauthorized: false
}
: isPub
? {
host: PUB_HOST,
port: 443,
path: "/dsb" + urlPath.slice(4) + (qs ? "?" + qs : ""),
method: req.method,
headers: { ...fwdHeaders, host: PUB_HOST },
rejectUnauthorized: false
}
: {
host: TARGET_HOST,
port: TARGET_PORT,
path: req.url,
method: req.method,
headers: { ...fwdHeaders, host: `${TARGET_HOST}:${TARGET_PORT}` }
};
const upstream = (isCx || isPub ? https : http).request(options, (proxyRes) => {
res.writeHead(proxyRes.statusCode || 502, { ...proxyRes.headers, ...cors });
proxyRes.pipe(res);
});
upstream.on("error", (e) => {
res.writeHead(502, { ...cors, "Content-Type": "application/json; charset=utf-8" });
res.end(JSON.stringify({
code: "-1",
message: `代理转发失败: ${e.message}(后端 ${TARGET_HOST}:${TARGET_PORT} 不可达?)`
}));
});
req.pipe(upstream);
});
server.listen(PORT, () => {
console.log("企业信息接口调试代理已启动");
console.log(` 测试页面: http://localhost:${PORT}`);
console.log(` 转发目标: http://${TARGET_HOST}:${TARGET_PORT}`);
console.log(" 按 Ctrl+C 停止");
});