在NodeJS中设置代理

在NodeJS中设置代理


最近一个项目需要让 NodeJS 程序走代理,本来我以为只是简单的配置一下就行,没想到颇费了一番功夫,

NodeJS 默认不读取系统代理,又因为 NodeJS 发送网络请求的方法有多种,所以设置代理的方法也各不相同,在这里总结一下。

前提

我们先假设代理服务器的地址来自环境变量,这也是很多 web app 的标准做法。为了方便起见,就把它们叫做 HTTP_PROXYHTTPS_PROXY

Fetch

最近的 NodeJS 支持 Fetch 接口了,它尽量和浏览器里的 Fetch保持一致,但如果真一致的话就没法设置代理了。幸好 NodeJS 开了个口子,添加了一个特有的参数dispatcher,用来修改网络请求的参数,包括代理服务器。

我们可以借助 undici 包来设置dispatcher

npm install undici

设置 fetch 代理

用 ProxyAgent 来设置:

import { ProxyAgent } from "undici";

const proxyUrl = process.env.HTTPS_PROXY || process.env.HTTP_PROXY;
if (proxyUrl) {
  await fetch(url, { dispatcher: new ProxyAgent(proxyUrl) });
}

全局设置 fetch 代理

setGlobalDispatcher 用来对整个 nodejs 程序中的 fetch 设置代理:

import { setGlobalDispatcher, ProxyAgent } from "undici";

const proxyUrl = process.env.HTTPS_PROXY || process.env.HTTP_PROXY;
if (proxyUrl) {
  setGlobalDispatcher(new ProxyAgent(proxyUrl));
}

如果想取消代理,可以设置空的 Agent:

import { Agent, setGlobalDispatcher } from "undici";
setGlobalDispatcher(new Agent());

原理

setGlobalDispatcher 其实利用了 nodejs 的一个隐秘入口。NodeJS 的全局对象有一个 Symbol 设置全局的 dispatcher:

global[Symbol.for("undici.globalDispatcher.1")] = yourDispatcher;

这似乎没有写在 NodeJS 文档里(反正我没找到),想要围观详情的请移步:https://github.com/nodejs/node/issues/43187

除了设置代理,dispatcher 还能干什么

如果你导入 Agent 而不是 ProxyAgent,那么还可以设置 keepAlive, timeout 等网络参数:

import { Agent } from "undici";

const res = await fetch("https://example.com", {
  dispatcher: new Agent({
    keepAliveTimeout: 10,
    keepAliveMaxTimeout: 10,
  }),
});
const json = await res.json();
console.log(json);

详情可以查看 undici 的文档

NodeJS 原生模块 http 和 https

上面也提到了,NodeJS 不自带设置代理的方法,需要利用 http.Agenthttps.Agent 动手开发。目前比较流行的软件包分别是 http-proxy-agenthttps-proxy-agent

这是 https-proxy-agent 的一个例子:

import https from "https";
import { HttpsProxyAgent } from "https-proxy-agent";

const proxyUrl = process.env.HTTPS_PROXY || process.env.HTTP_PROXY;
if (proxyUrl) {
  const agent = new HttpsProxyAgent(proxyUrl);

  https.get(
    {
      host: "example.com",
      path: "/",
      agent,
    },
    (res) => {
      // 处理响应
    }
  );
}

Axios

Axios 几乎算是 JavaScript 的行业标准了。它设置代理的方式有多种(优先级由低到高):

  1. 环境变量:Axios 本身就会读取环境变量 HTTP_PROXYHTTPS_PROXY

  2. proxy参数:

    import axios from "axios";
    
    axios.get("https://example.com", {
      proxy: {
        host: "127.0.0.1",
        port: 7890,
      },
    });
  3. httpAgenthttpsAgent参数:你可以传入 NodeJS 自带的 http.Agenthttps.Agent 来控制网络参数。

    我们这里用上一章提到的 http-proxy-agenthttps-proxy-agent

    import axios from "axios";
    import { HttpsProxyAgent } from "https-proxy-agent";
    import { HttpProxyAgent } from "http-proxy-agent";
    
    const proxyUrl = process.env.HTTPS_PROXY || process.env.HTTP_PROXY;
    if (proxyUrl) {
      const httpsAgent = new HttpsProxyAgent(proxyUrl);
      const httpAgent = new HttpProxyAgent(proxyUrl);
    
      const instance = axios.create({
        httpAgent,
        httpsAgent,
      });
    }

具体可以参考 Axios 文档

HTTPS 连接的限制

我发现如果用前两种方法设置 HTTP 代理,axios 无法建立 HTTPS 连接。原因是 axios 不会发送 CONNECT 方法,因此没法建立 TLS 连接(不了解 CONNECT 方法的可以看我写的这篇文章)。