场景
在electron中集成axios后,发现axios不能读取到set-cookie响应头,且axios也无法写入Cookie响应头。
原因
Chromium中不支持跨站访问HttpOnly的Cookie,electron有两种进程一个是主进程,一个是渲染进程,它的渲染进程就是Chromium的进程,主进程是是NodeJS的进程。electron是使用Chromium的进程来渲染界面的,故axios不能修改和读取Chromium中Cookie,因为服务器端设置HttpOnly。
思路
利用electron的IPC消息机制,在渲染进程(Chromium进程)中触发网络请求事件,将该事件发送给electron的主进程(NodeJS进程),在主进程中进行axios网络请求调用远程服务器API,因为主进程是NodeJS环境,不是Chromium浏览器环境,不受CORS限制。最后,处理好远程服务器API数据后,将再次利用electron的IPC消息机制,将数据发回渲染进程进行UI界面渲染。
electron
这里假设已经完成了electron快速教程,即:
https://www.electronjs.org/docs/latest/tutorial/quick-start
并且,已经安装好axios库,即:
npm install axios
在Electro基础项目代码上面,主要做如下修改:
main.js
const {
app,
BrowserWindow,
BrowserView,
session,
ipcMain,
WebContents,
} = require("electron");
const axios = require("axios").default;
// include the Node.js 'path' module at the top of your file
const path = require("path");
const baseURL = "https://www.xxxx.com";
var myCookie;
const axiosClient = axios.create({
baseURL,
headers: {
"Session-Access-Origin": "xxx",
"Content-Type": "application/json",
},
});
axiosClient.interceptors.request.use(
(config) => {
if (myCookie !== undefined) {
// 添加Cookie请求头
config.headers["Cookie"] = myCookie;
}
return config;
},
(error) => Promise.reject(error.request.data.err)
);
function createWindow() {
const win = new BrowserWindow({
width: 1920,
height: 1080,
webPreferences: {
preload: path.join(__dirname, "preload.js"),
nodeIntegration: true,
contextIsolation: false,
},
});
win.webContents.openDevTools();
win.loadFile("index.html");
}
app.whenReady().then(() => {
createWindow();
app.on("activate", function () {
if (BrowserWindow.getAllWindows().length === 0) createWindow();
});
});
app.on("window-all-closed", function () {
if (process.platform !== "darwin") app.quit();
});
//ipcMain.on 将接受 从渲染进程发过来的“btnclick”事件
ipcMain.on("btnclick", function (event, arg) {
var data = JSON.stringify({
account: "xxx",
password: "xxxx",
});
axiosClient
.post("/api/user/userLogin", data)
.then((res) => {
// 登录成功后,获取set-cookie
myCookie = res.headers["set-cookie"][0].split(";")[0];
console.log(myCookie);
// 发送login-task-finished事件
// 将数据发生给渲染进程,进行刷新UI界面
event.sender.send("login-task-finished", myCookie);
})
.catch((err) => console.log(err));
});
//ipcMain.on 将接受 从渲染进程发过来的“getTime”事件
ipcMain.on("getTime", function (event, arg) {
// 再次测试是否登录成功,这个接口只有登录成功才能够正常调用
axiosClient
.get("/api/common/getNow")
.then((res) => {
const data = res.data;
console.log(data);
// 发送getTime-task-finished事件
// 将数据发生给渲染进程,进行刷新UI界面
event.sender.send("getTime-task-finished", data.data);
})
.catch((err) => console.log(err));
});
renderer.js
// 引入electron的IPC消息模块
const ipcRenderer = require("electron").ipcRenderer;
const btnclick = document.getElementById("loadnewwindow");
btnclick.addEventListener("click", function () {
var arg = "secondparam";
//发送btnclick事件给主线程
ipcRenderer.send("btnclick", arg); // ipcRender.send will pass the information to main process
});
const getTimeclick = document.getElementById("get_time");
getTimeclick.addEventListener("click", function () {
var arg = "gettime";
//发送getTime事件给主线程
ipcRenderer.send("getTime", arg); // ipcRender.send will pass the information to main process
});
// 接收主线程的login-task-finished事件
ipcRenderer.on("login-task-finished", (event, param) => {
var div = document.getElementById("session");
div.innerHTML = param;
});
// 接收主线程的getTime-task-finished事件
ipcRenderer.on("getTime-task-finished", (event, param) => {
var div = document.getElementById("time");
div.innerHTML = param;
});
index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
<meta
http-equiv="Content-Security-Policy"
content="default-src 'self'; script-src 'self'"
/>
<meta
http-equiv="X-Content-Security-Policy"
content="default-src 'self'; script-src 'self'"
/>
<title>Hello World!</title>
</head>
<body>
<h1>Hello World!</h1>
We are using Node.js <span id="node-version"></span>, Chromium
<span id="chrome-version"></span>, and Electron
<span id="electron-version"></span>.
<button id="loadnewwindow">login</button>
<br />
SESSION:
<label id="session"></label>
<br />
<button id="get_time">get time</button>
<br />
Time:
<label id="time"></label>
<br />
<!-- 渲染进程脚本 -->
<script src="./renderer.js"></script>
</body>
</html>
peload.js
window.addEventListener('DOMContentLoaded', () => {
const replaceText = (selector, text) => {
const element = document.getElementById(selector)
if (element) element.innerText = text
}
for (const dependency of ['chrome', 'node', 'electron']) {
replaceText(`${dependency}-version`, process.versions[dependency])
}
})
总结
electron主进程是NodeJS进程,不是Chromium,不受CORS限制;渲染进程是Chromium进程,受CORS限制。让axios在主进程中运行,就可以突破CORS,改读取和修改Cookie了。这里的主进程相当于electron的后台主进程,渲染先进就是用来渲染UI用的,某种程度上面在前端的前后分离。electron的主进程概念,与Android主线程作用不同。Android的主线程是不能干耗时的任务的,electron主进程是可以干耗时的操作,而不影响渲染进程。
还有,大能提出使用axios-cookiejar-support库,来增强axios,这里没有尝试过,就到此为止了。
最后,给出源代码参考。
https://github.com/fxtxz2/electron_axios_cors_cookie