Electron 应用如何获取系统代理配置

January 19, 2022

macOS System Proxy

操作系统默认都有上图这种系统级别的代理配置,但是像 Electron 应用,主进程的网络请求默认并不会走这个系统代理,这个默认行为很容易给用户来带不便从而惹恼用户。如果开发者要让主进程里的网络请求走系统代理,需要用一些技巧。

读取系统代理配置

方法一:session.resolveProxy

找到正确读取系统代理的方法并不是一帆风顺的,着实让我花了一点时间,也踩了不少可坑,最终我测试可行的方案如下,这个方案同时支持 macOS 和 windows

以下代码需要放在主线程执行

import HttpsProxyAgent from 'https-proxy-agent'

const mainWindow = new BrowserWindow({
  show: false,
  width: 1024,
  height: 768,
})

// 读取浏览器的 session
const session = mainWindow.webContents.session

// 下面的代码会尝试解析代理配置,如果用户配置了系统代理,
// 并且代理规则没有排除 www.google.com,那我们就可以读取到代理信息
session.resolveProxy('https://www.google.com').then((proxyUrl) => {
  // DIRECT 表示没有配置代理
  if (proxyUrl !== 'DIRECT') {
    // proxyUrl 是这种格式: 'PROXY 127.0.0.1:6152'
    const hostAndPort = proxyUrl.split(' ')[1]
    const [proxyHost, proxyPort] = hostAndPort.split(':')

    // 到这里就拿到代理服务器的信息了,这里我用 https-proxy-agent 这个 npm 包让 Axios 的默认请求强制走系统代理
    // @ts-ignore
    const agent = new HttpsProxyAgent({
      host: proxyHost,
      port: proxyPort,
    })
    Axios.defaults.httpsAgent = agent
  }
})

上述代码在最新的 Electron 16 版本测试通过

方法二:系统命令

在 Electron 应用的主进程中,通过 Node.js 的 child_process 模块执行系统命令获取系统代理信息。不同操作系统的获取方式不同,并且随着系统升级可能出现命令不可用或者输出格式变化等不稳定情况。

在 macOS 系统上:

const { execSync } = require('child_process')

function getMacWebProxy() {
  // 这种方法需要考虑到用户使用不同网卡的情况,Wi-Fi 是网络设备名称,对于使用有线网卡的用户,可能需要替换成 Ethernet 等适当的名称
  const cmd = 'networksetup -getwebproxy "Wi-Fi"'
  const output = execSync(cmd).toString()
  const result = output.match(/(?:Enabled:\s)(\w+)\n(?:Server:\s)([^\n]+)\n(?:Port:\s)(\d+)/)

  if (result) {
    const [_, enabled, server, port] = result
    if (enabled === 'Yes') {
      return `http://${server}:${port}`
    }
  }

  return null
}

const proxyConfig = parseProxyConfig(rawProxyInfo)
console.log(proxyConfig) // 输出代理配置字符串,如 "http://127.0.0.1:6152"

在 Windows 系统上(未验证):

const { execSync } = require('child_process')
const proxyConfig = execSync(
  'reg query "HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings" /v ProxyServer',
)
  .toString()
  .split('\r\n')
  .filter((line) => line.trim())
  .pop()
  .split('    ')
  .pop()

console.log(proxyConfig) // 输出代理配置字符串,如 "http://proxy.example.com:8080"

优化空间

上面的代码只是拿到了系统代理配置,但是在用户手动改了系统代理配置的情况下,我们程序里的配置信息还是旧的,所以想要极致的用户体验,我们是需要想办法监听这个变动的。不然网络请求就会出错了。

如果你没有特殊需求,可以将网络请求放在 renderer 进程中用 xhr 或者 fetch 执行,这样网络代理会由 chromium 自动处理,开发者不需要关心如何获取和监测系统代理变化,这些复杂的部分都由 chromium 在底层帮你处理好了,就像浏览器一样。

引用

如果你喜欢我的内容,请考虑请我喝杯咖啡☕吧,非常感谢🥰 。

If you like my contents, please support me via BuyMeCoffee, Thanks a lot.