主题
node-http-proxy 代理跨域
准备工作
- 本地启动服务
https://david.cn:1111
, 记为 A - 访问远端服务
https://localhost.meetsocial.cn:9991
, 记为 B - 代理代码
http-proxy
, 记为 P - node 启动代码
bash
yarn init -y
yarn add http-proxy
nodemon httpProxy.js
nodemon https.js
5.A 的浏览器控制台, 代码准备:
javascript
postData("/api/aa/bb/cc", { a: 1, b: 2 })
.then((data) => console.log(data))
.catch((error) => console.error(error));
function postData(url, data) {
return window
.fetch(url, {
body: JSON.stringify(data),
headers: {
"content-type": "application/json"
},
cache: "no-cache",
method: "POST",
mode: "cors"
})
.then((response) => response.text());
}
代码解读
changeOrigin: true,
为了让传递给 B 的 host 为localhost.meetsocial.cn:9991
, 而不是david.cn:1111
。所以得配合加上参数 cookieDomainRewrite, 让传回的 cookie 的 domain 变成david.cn
。- secure true 是否验证证书, 如果设置为 true, 则必须传递参数 agent。
- 为了能让 https 能够转发不会报下面的错, 需要传递 agent
unable to verify the first certificate
- B 代码返回头必须的设置 cookie 的 path, 否则 P 里的 cookiePathRewrite 不起作用。
javascript
res.setHeader("Set-Cookie", [`a=1; path=xxx`]);
- 另外, http-proxy 还可以增加参数
selfHandleResponse: true
, 则可以自定义返回的内容。
代码
A 和 P 的代码
javascript
const httpProxy = require("http-proxy");
const fs = require("fs");
const https = require("https");
const agent = new https.Agent();
agent.options.ca = fs.readFileSync("./cert/rootCA.pem", "utf8");
const options = {
key: fs.readFileSync("./cert/local.key", "utf8"),
cert: fs.readFileSync("./cert/local.crt", "utf8")
};
const proxy = new httpProxy.createProxyServer({
target: "https://localhost.meetsocial.cn:9991",
cookiePathRewrite: {
"/api/aa/bb/cc": "/"
},
cookieDomainRewrite: {
"localhost.meetsocial.cn": "david.cn"
},
changeOrigin: true,
ssl: options,
agent,
secure: true
});
proxy.on("error", function (err, req, res) {
res.writeHead(500, {
"Content-Type": "text/plain"
});
res.end(err.toString());
});
https
.createServer(options)
.on("request", function (req, res) {
const { url } = req;
if (url.indexOf("/api/") > -1) {
proxy.web(req, res);
} else {
res.end("proxy proxy get get");
}
})
.listen(1111);
B 的代码
javascript
if (method === "POST") {
// myUrl.hostname 为 localhost.meetsocial.cn
// url 为 /api/aa/bb/cc
res.setHeader("Set-Cookie", [
`a=1; domain=${myUrl.hostname}; path=${url}`,
`b=2; domain=${myUrl.hostname}; path=${url}`
]);
handlePOST(req, res);
return;
}
B 打印的 header
method: POST
url: /api/aa/bb/cc
headers: {
'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8',
'accept-encoding': 'gzip, deflate, br',
referer: 'https://david.cn:1111/',
'sec-fetch-mode': 'cors',
'sec-fetch-site': 'same-origin',
origin: 'https://david.cn:1111',
accept: '*/*',
'content-type': 'application/json',
'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36',
'sec-fetch-dest': 'empty',
'cache-control': 'no-cache',
pragma: 'no-cache',
'content-length': '13',
connection: 'keep-alive',
host: 'localhost.meetsocial.cn:9991'
}
若 A 的控制台直接请求 B, postData('https://localhost.meetsocial.cn:9991/api/aa/bb/cc')
, 返回头如下, 和代理转发的返回头唯一的区别是 sec-fetch-site
字段。
method: POST
url: /api/aa/bb/cc
headers: {
host: 'localhost.meetsocial.cn:9991',
connection: 'keep-alive',
'content-length': '13',
pragma: 'no-cache',
'cache-control': 'no-cache',
'sec-fetch-dest': 'empty',
'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36',
'content-type': 'application/json',
accept: '*/*',
origin: 'https://david.cn:1111',
'sec-fetch-site': 'cross-site',
'sec-fetch-mode': 'cors',
referer: 'https://david.cn:1111/',
'accept-encoding': 'gzip, deflate, br',
'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8'
}