愿灾区人民平安
0x01 简介
大家好,我们是 NOP Team ,今天和大家讨论一下关于 node.js 和 Electron App中 axios 代理的问题
Axios 是一个基于 promise 的网络请求库,可以用于浏览器和 node.js ,是 Node.js 中使用范围最广泛的网络库之一,甚至没有之一,我们今天针对 axios 的代理问题来进行一些测试,解决一些长期以来的困惑
本篇文章部分配置在 MacOS 下进行,Windows、Linux 可能有些不同
0x02 代理情况分类
我们抛开代理层级以及外部程序因素,仅从我们自己的程序使用代理的情况来说,分为以下四种情况
-
无代理 -
环境变量代理 -
系统代理 -
自定义代理
1. 无代理
这里说的无代理是指不使用任何代理,包括环境变量代理、系统代理,但是如果遇到类似 Proxychains 这类的辅助代理程序,那还是会遵从辅助代理程序的结果
2. 环境变量代理
环境变量代理是指通过设置以下环境变量设置的代理,主要针对通过终端执行的程序,当然部分程序也会手动读取环境变量后使用相关代理
export HTTP_PROXY=http://username:password@host:port
export HTTPS_PROXY=http://username:password@host:port
export no_proxy=localhost,127.0.0.*
3. 系统代理
系统代理是指系统层面的代理,默认大部分程序都会遵从系统代理,当然也会有一些特例,例如 Chrome
4. 自定义代理
这个不需要过多解释,大家都理解
0x03 测试代理使用情况
接下来我们测试一下 Node.js 环境下使用 axios 使用上述代理的情况
以以下两个网站为例,代理以本地 http://127.0.0.1:1080
为例
https://api.ipify.org?format=json // 无法直接访问
https://myip.ipip.net/ // 可直接访问
1. 无代理
const axios = require("axios");
// IP APIs
const IPIFY_URL = "https://api.ipify.org?format=json"; // ipify API
const IPIP_URL = "https://myip.ipip.net/"; // ipip.net API
// Function to fetch IP using Axios
const fetchIP = async (url, config = {}) => {
try {
const response = await axios.get(url, config);
console.log("Response:", response.data);
} catch (error) {
console.error("Error:", error.code);
}
}
const testProxy = async () => {
let response;
console.log('1. 测试无代理情况下 ipify 访问情况')
response = await fetchIP(IPIFY_URL, {
proxy: false
})
console.log('2. 测试无代理情况下 ipip.net 访问情况')
response = await fetchIP(IPIP_URL, {
proxy: false
})
}
testProxy()
需要注意的是,不使用桌面代理的情况下,需要像上面一样设置 proxy: false
从这里可以看出一个问题,在默认情况下,axios 是不走系统代理的,我们看一下当前系统代理设置情况
系统代理配置没有问题,但是默认 axios 就是不走,即使我们把 proxy: false
取消也是一样的
这也意味着一会儿我们的系统代理无法测试,因为它根本就不走
2. 环境变量代理
我们在终端中设置以下内容
export HTTP_PROXY=http://127.0.0.1:1080
export HTTPS_PROXY=http://127.0.0.1:1080
测试代码如下
const axios = require("axios");
// IP APIs
const IPIFY_URL = "https://api.ipify.org?format=json"; // ipify API
const IPIP_URL = "https://myip.ipip.net/"; // ipip.net API
// Function to fetch IP using Axios
const fetchIP = async (url, config = {}) => {
try {
const response = await axios.get(url, config);
console.log("Response:", response.data);
} catch (error) {
console.error("Error:", error.code);
}
}
const testProxy = async () => {
let response;
console.log('1. 测试环境变量代理情况下 ipify 访问情况')
response = await fetchIP(IPIFY_URL, {
// proxy: false
})
console.log('2. 测试环境变量代理情况下 ipip.net 访问情况')
response = await fetchIP(IPIP_URL, {
// proxy: false
})
}
testProxy()
1) MacOS
可以看到,此时访问 ipify 成功了,之所以两个网站返回的内容不一样是因为做了分流
这里有一个疑问,我们开发的程序是 App ,它是直接点击执行的,默认会走环境变量代理吗?
正好可以用我们的 IP-Recoder 来进行测试,因为 IP-Recorder 中标记的系统代理其实就是环境变量代理
我们看一下 src/utils/proxyWapper.mjs
文件
import { HttpProxyAgent } from 'http-proxy-agent'
import { HttpsProxyAgent } from 'https-proxy-agent'
import { SocksProxyAgent } from 'socks-proxy-agent'
export function createAxiosConfig(proxyType, customProxyConfig = null) {
const baseConfig = {
timeout: 5000,
maxRedirects: 5
}
switch (proxyType) {
case 0: // 无代理
return {
...baseConfig,
proxy: false,
httpAgent: null,
httpsAgent: null
}
case 1: // 系统代理
return {
...baseConfig
}
case 2: // 自定义代理
if (!customProxyConfig) {
throw new Error('Custom proxy config is required')
}
const { protocol, host, port, auth } = customProxyConfig
let agent
// 构建代理URL
const getProxyUrl = () => {
const proto = protocol === 0 ? 'socks5' : protocol === 1 ? 'http' : 'https'
if (auth && auth.username && auth.password) {
return `${proto}://${auth.username}:${auth.password}@${host}:${port}`
}
return `${proto}://${host}:${port}`
}
// 创建对应的agent
if (protocol === 0) {
agent = new SocksProxyAgent(getProxyUrl())
} else if (protocol === 1) {
agent = new HttpProxyAgent(getProxyUrl())
} else {
agent = new HttpsProxyAgent(getProxyUrl())
}
return {
...baseConfig,
httpAgent: agent,
httpsAgent: agent,
proxy: undefined // 关键:使用agent时不设置proxy
}
default:
throw new Error('Invalid proxy type')
}
}
其中的 case 1
,也就是系统代理处直接返回了默认配置,从上面的测试我们知道,这并不是系统代理,很多文章说的都是错的,这就是环境变量代理,所以正好我们来测试
我们直接在shell的配置文件中放入
export HTTP_PROXY=http://127.0.0.1:1080
export HTTPS_PROXY=http://127.0.0.1:1080
由于测试环境使用 fish 为默认的shell,所以还在 fish 的配置文件中写入了
set -x HTTP_PROXY http://127.0.0.1:1080
set -x HTTPS_PROXY http://127.0.0.1:1080
退出登录后,打开终端验证代理情况,打开 IP-Recorder,测试系统代理是否可用
这说明在 MacOS 中,通过图标点击启动的 App 是不走通过 export HTTP(S)_PROXY
设置的环境变量代理的
但是,MacOS 中有 launchctl
的设置方式,可以让 GUI 走环境变量代理,在此之前,我们得清除掉之前设置的代理,退出登录,再次登录进行配置
launchctl setenv HTTP_PROXY http://127.0.0.1:1080
launchctl setenv HTTPS_PROXY http://127.0.0.1:1080
launchctl setenv NO_PROXY localhost,127.0.0.1
可以看到,通过 launchctl setenv
这种方式设置的环境变量代理默认App是可以显式地使用的
2) Linux
接下来测试一下在 Linux 桌面环境中的情况,以 Ubuntu Desktop 24.04 为例
可以看到,在 Ubuntu Desktop 24.04 中,当使用默认配置时,App 程序中使用的 axios 是会走环境变量代理的
使用之前的脚本测试一下
配置环境变量代理后
可以看到,配置了环境变量代理后,脚本中的 axios 默认会使用环境变量代理
3) Windows
接下来我们测试一下 Windows,以 Windows 11 为例
还是先使用之前的脚本
未配置环境变量代理时
接下来配置环境变量
配置环境变量后,默认情况下脚本可以使用环境变量代理
在测试命令行启动的应用后,还使用打包好的 App 程序进行了测试
配置环境变量代理前
配置环境变量代理后
可以看到, App 程序中的 axios 默认配置会使用环境变量代理
3. 自定义代理
这部分就不测试了,比较简单
4. 系统代理
系统代理区别于环境变量代理的是,它是系统级的,配置以后,绝大多数软件都会使用该代理,当然也有例外,例如 Chrome
部分资料显示, axios 是不支持系统代理的,我们挨个测试一下,清除之前设置的环境变量代理,参数保持默认和 {proxy: false}
,看看这两种情况下,会不会响应系统代理
1) MacOS
可以看到,默认情况下,无论是命令行执行的 axios 还是 App 点击执行中的 axios 都是不会使用系统代理的
如果想获取系统代理,只能通过执行系统命令来获取了(这部分系统命令还挺闹心的,复杂的点在于用户可能更改服务名字,比如我就改了,导致过程变得复杂,但为了兼容性考虑,必须这么做)
const { execSync } = require('child_process');
function getMacNetworkServices() {
try {
// 获取网络服务顺序
const serviceOrder = execSync('networksetup -listnetworkserviceorder | grep -v "An asteris"')
.toString()
.split('nn')
.filter(Boolean);
// 获取硬件端口列表用于匹配
const hardwarePorts = execSync('networksetup -listallhardwareports')
.toString()
.split('nn')
.filter(Boolean);
// 过滤掉说明行和VLAN配置
const connectedPorts = hardwarePorts.filter(port =>
!port.includes('VLAN') &&
!port.startsWith('An asterisk') &&
port.includes('Device:')
);
const activeServices = [];
// 遍历服务顺序
for (const service of serviceOrder) {
// 跳过说明行
if (service.startsWith('An asterisk')) continue;
// 解析服务信息
const deviceMatch = service.match(/Device: (.+))/);
if (!deviceMatch) continue;
const deviceName = deviceMatch[1];
const serviceMatch = service.match(/(d+) (.+)n/);
if (!serviceMatch) continue;
const serviceName = serviceMatch[1];
// 跳过串口
if (serviceName.includes('Serial Port')) continue;
// 检查设备是否在已连接列表中
const isConnected = connectedPorts.some(port =>
port.includes(`Device: ${deviceName}`)
);
if (!isConnected) continue;
try {
// 获取代理信息
const httpProxy = execSync(`networksetup -getwebproxy "${serviceName}"`).toString();
const httpsProxy = execSync(`networksetup -getsecurewebproxy "${serviceName}"`).toString();
const proxyConfig = {
service: serviceName,
device: deviceName,
httpProxy: parseProxyConfig(httpProxy),
httpsProxy: parseProxyConfig(httpsProxy)
};
activeServices.push(proxyConfig);
} catch (err) {
console.error(`Error getting proxy for service ${serviceName}:`, err.message);
continue;
}
}
return activeServices;
} catch (err) {
console.error('Error getting network services:', err);
return [];
}
}
function parseProxyConfig(configStr) {
const lines = configStr.split('n');
return {
enabled: lines[0].includes('Yes'),
server: lines[1].split(': ')[1],
port: parseInt(lines[2].split(': ')[1]),
authenticated: lines[3].includes('Yes')
};
}
// 使用示例
console.log(getMacNetworkServices());
可以获取到目前可用的服务的系统代理配置,之后按照自定义代理的方式获取IP信息
2) Linux
还是以 Ubuntu Desktop 24.04 为例
未设置系统代理时
设置系统代理后
可以看到,在 Ubuntu Desktop 24.04 中,设置系统代理后,会自动设置环境变量代理,只有在我们使用环境变量代理时(即默认配置)才有效
接下来测试一下 App 的情况
未开启系统代理时
开启系统代理后
可以看到,配置并开启系统代理后,会自动配置环境变量代理
比较奇怪的是,之前设置了环境变量代理后,axios 默认配置是可以使用环境变量代理的,但是在配置系统代理后,系统代理自动配置环境变量代理时,竟然不能用了
于是,又经过了一系列重启、重新测试环境变量代理
结果还是一样,我们再换 Deepin 23 进行测试
未开启系统代理时
开启系统代理后
系统代理情况与 Ubuntu Desktop 24.04 一致,我们测试一下直接设置环境变量代理
测试结果和 Ubuntu Desktop 24.04 一致
对于这个结果,我们肯定是感觉比较奇怪的,为什么会这样呢?
看来系统代理自动配置环境变量代理的方式和我们配置环境变量代理的方式不一样,之前我们为了保证配置环境变量对所有程序生效,是直接将配置信息写在了 /etc/profile
或者 /etc/environment
中的,如果我们把配置写入到默认shell的个人用户配置文件呢?例如 fish 的个人配置文件位置为 ~/.config/fish/config.fish
可以看到,我们通过 shell 默认的个人配置文件配置环境变量代理时是不起作用的,所以环境变量代理还挺复杂的
综上,在 Linux 中,系统代理会自动配置环境变量代理,终端执行的脚本中的 axios 默认配置会自动使用环境变量代理,而直接点击启动的 App 中的 axios 则不会使用系统代理
3) Windows
在 Windows 中设置环境变量后,并不会自动设置环境变量代理
此时测试脚本中 axios 默认配置以及 { proxy: false }
配置下代理使用情况
可以看到,默认情况下,脚本中的 axios 是不会使用系统代理的,我们看一下 App
可以看到,默认情况下,直接通过图标点击进入的 Electron App 中的 axios 同样不使用系统代理,此时例如 edge 浏览器是默认使用系统代理的
0x04 环境变量代理的进一步测试
更新关于环境变量的结论,通过 /etc/profile
和 /etc/environment
配置的代理对 App 中的 axios 默认配置有效,通过 shell 的默认配置文件 .bashrc
等配置的环境变量代理对 App 中的 axios 默认配置无效
这是为什么呢?猜测是 electron 只能获取到系统环境变量文件中的配置
系统级环境变量文件
/etc/environment # 系统级环境变量,所有用户生效
/etc/profile # 全局配置文件
/etc/profile.d/*.sh # profile 的子配置文件
用户级环境变量文件
~/.profile # 用户级环境变量
~/.bashrc # bash 配置文件
~/.bash_profile # bash 登录配置
~/.bash_login # bash 登录配置的替代文件
~/.zshrc # zsh 配置文件
~/.config/fish/config.fish # fish 配置文件
为此,我们使用 Electron-vite 开发一个程序来测试,主要代码如下
ipcMain.handle('get-proxy', () => {
const httpProxy = process.env.http_proxy || process.env.HTTP_PROXY;
const httpsProxy = process.env.https_proxy || process.env.HTTPS_PROXY;
const noProxy = process.env.no_proxy || process.env.NO_PROXY;
return {
http: httpProxy,
https: httpsProxy,
no_proxy: noProxy
};
})
点击按钮后,就显示出代理信息,使用 npm run build:linux
编译成 deb ,并安装
首先测试用户级环境变量配置文件
export http_proxy=http://192.168.1.127:1080
export https_proxy=http://192.168.1.127:1080
export no_proxy=localhost.127.0.0.0/8,::1
可以看到,我们虽然已经在用户级环境变量文件中都配置了代理信息,但 Electron 打包后的程序完全没有获取到,也就是 process.env.xxx
没有获取到
接下来测试系统级环境变量
/etc/environment
成功获取到环境变量代理信息
/etc/profile
由于 Deepin 23 上似乎并不使用这个配置文件,所以采用 Ubuntu 来进行测试,结果如上
/etc/profile.d/*.sh
成功获取到环境变量代理信息
0x05 总结
通过有限的测试,最终得到如下结论,结论均为 axios 的行为,所以简略写
0x06
原文始发于微信公众号(NOP Team):Axios 的代理问题探究
原文始发于微信公众号(NOP Team):Axios 的代理问题探究
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论