这篇文章对谁有用?

  • Electron.js 开发者

这篇文章讲什么?

Electron.js 如何实现代理功能。
比如:

解释

  • 用户可以输入代理地址到 HTTP_PROXY。
  • 右侧有个开关,"已开启"|"已关闭"
  • 代理设置完了,下方可以"测试代理连通性"

背景信息:

我做了一个 Electron 桌面应用,由于需要和谷歌 API 直接交互。翻墙是个问题。
我希望 Electron 能直接用我本地已经在运行的代理 (监听 http://127.0.0.1:1087)

注:本文写于2019年12月27号,我用的 Electron.js 版本是 6.1.5
(我知道最新版已经到7了,但是我安装一直碰到问题,所以没用7)

项目技术栈

  • Electron.js 6
  • Vue

我用 vue-cli 3 新建项目,然后往里加 Electron.js,
代码用到了 vue-router 和 vue-i18n,没用到 vuex。
目录结构长这样:

package.json 长这样:

就是一个典型的 Vue 项目的结构:
各种 .vue 文件

本文结构

  1. 先讲结论
  2. 给出可复制粘贴的代码
  3. 最后才讲过程,读者可以看到我犯了什么错,也可以跳过这个部分不看。

先讲结论

实现代理的方法是

BrowserWindow.webContents.session.setProxy

简单说就是:用 setProxy

文档: https://electronjs.org/docs/api/session

给出可复制粘贴的代码

由于官方这个文档没有代码示例,所以我来提供一点:

我的 index.js (Main process) 代码如下:

const {
  app,
  BrowserWindow,
  ipcMain,
  Menu
} = require('electron')

let win

function createWindow() {
  win = new BrowserWindow({
    width: 820,
    height: 840,
    titleBarStyle: 'hidden',
    frame: false,
    webPreferences: {
      nodeIntegration: true,
    }
  })
}

这一段只是前置条件,帮助理解后面的代码。
总之只要知道, 这里有个变量叫 win, 类型是 BrowserWindow,这就行了

重点来了,index.js 文件的下半部分还有这两段


// 设置代理
ipcMain.on('set_proxy', (event, arg) => {
  console.log(arg);
  var { http_proxy } = arg
  win.webContents.session.setProxy({
    proxyRules: http_proxy
  }, function () {
    console.log('代理设置完毕')
  });
})

// 去掉代理
ipcMain.on('remove_proxy', (event, arg) => {
  win.webContents.session.setProxy({});
})

可以看到这里是监听 ipc,如果收到对应名字的事件就运行代码

如果想了解更多 ipcMainipcRender,以下是文档:

https://electronjs.org/docs/api/ipc-main

https://electronjs.org/docs/api/ipc-renderer

那么,怎么使用呢?

在 Render Process 的代码里:

// 设置代理
set_proxy() {
  window.ipcRenderer.send("set_proxy", {
    http_proxy: this.setting_http_proxy
  });
},

// 取消代理
remove_proxy() {
  window.ipcRenderer.send("remove_proxy");
},

这样就可以了。

这2个函数是写在 methods:

<template>
  
</template>

<script>
export default {
  methods: {
    // 这里
  }
}
</script>

<style>

</style>

还有一件事
前面代码里 window.ipcRenderer 可能看起来很奇怪。
干嘛要带个 window. 在前面?

这是因为我另外有一段:

const {
  ipcRenderer,
} = window.require("electron")

window.ipcRenderer = ipcRenderer;

这就保存到了 window 变量里,render process 里的代码可以很方便的拿到。

因为 webpack 和 node 有干扰(具体原理我也不清楚,没关心)
所以要用 window.require

代码部分到此结束。

以下进入本文第三部分,也是最后一部分:犯过的错

犯过的错

错误1:直接用 axios/request 的 proxy 配置
错误2:环境变量

错误1:直接用 axios/request 的 proxy 配置

一开始我的思路还没转过来,忘记这是桌面应用了。
就去看 Node.js 网络库怎么配置代理。
测试了 axios, requests, superagent, 原生 fetch 和 got.
好像还测了几个其他的名气比较小的库,无一成功。
一开始我以为是自己哪里弄错了。不太理解。后来才想起来一切请求最终都是通过 Chrome 发出去的。这是个桌面应用,我搞错方向了。

错误2:环境变量

我以为设置环境变量 HTTP_PROXY 和 HTTPS_PROXY,
axios, requests 等等库发出去的请求就会尊重这个环境变量。我错了。

简而言之,在这2个错误上浪费了2天时间。

换了个思路,看 Electron 本身这个请求是怎么出去的,应该是在外面这一层,
然后找到了 setProxy,一试,可以。这才解决了问题。

补几个有用的链接:

https://github.com/electron/electron/issues/21050

全文完

感谢阅读