# 如何在 Node.js 中通过 fetch、undici、axios 和 got 使用代理

Node 默认没有全局代理设置。每个客户端都需要接收 agent 或 dispatcher；确定该用哪一个后，其余工作交给 ProxShift URL 即可。


## 内置 fetch 与 undici

Node 的全局 `fetch` 基于 undici。可以通过 `dispatcher` 选项为每次请求传入 `ProxyAgent`，也可以调用一次 `setGlobalDispatcher` 完成全局设置。

```javascript
import { fetch, ProxyAgent, setGlobalDispatcher } from "undici";

const proxy = new ProxyAgent("http://USER-cc-us:PASS@res.proxshift.com:9000");

// 按请求设置
const res = await fetch("https://api.ipify.org", { dispatcher: proxy });
console.log(await res.text());

// 或进行全局设置，应用于该进程中的所有 fetch
setGlobalDispatcher(proxy);
```

`ProxyAgent` 会建立连接池，因此连续请求可能复用同一连接，进而沿用同一出口。若希望每次请求都使用新出口，请以 `pipelining: 0` 和 `connections: 1` 创建 agent；也可以直接为每个逻辑会话使用不同的 `-sid-`。


## axios

axios 自带的 `proxy` 选项无法可靠地为 HTTPS 建立隧道；标准做法是使用 `https-proxy-agent` 提供的 agent，并设置 `proxy: false`。

```javascript
import axios from "axios";
import { HttpsProxyAgent } from "https-proxy-agent";

const agent = new HttpsProxyAgent("http://USER-cc-de-city-berlin-sid-cart42-ttl-15m:PASS@res.proxshift.com:9000");
const client = axios.create({ httpsAgent: agent, httpAgent: agent, proxy: false, timeout: 30000 });

const { data } = await client.get("https://api.ipify.org");
console.log(data);
```


## got

```javascript
import got from "got";
import { HttpsProxyAgent } from "https-proxy-agent";

const agent = new HttpsProxyAgent("http://USER-cc-us:PASS@res.proxshift.com:9000");
const body = await got("https://api.ipify.org", { agent: { https: agent, http: agent }, timeout: { request: 30000 } }).text();
console.log(body);
```


## SOCKS5

```javascript
import { SocksProxyAgent } from "socks-proxy-agent";

// socks5h 在出口端解析目标地址
const agent = new SocksProxyAgent("socks5h://USER-cc-us:PASS@res.proxshift.com:9001");
const res = await fetch("https://api.ipify.org", { dispatcher: undefined, agent }); // node-fetch 风格的客户端接收 `agent`
```

> Info：内置 fetch 不接受 `agent`；HTTP 代理请使用 undici 的 `ProxyAgent`，SOCKS 则可使用 `fetch-socks` 等兼容 SOCKS 的 dispatcher。对大多数任务而言，使用 9000 端口的 HTTP 代理更简单。


## 环境变量

undici 默认不读取 `HTTPS_PROXY`。较新的 undici 版本提供了能够读取它的 `EnvHttpProxyAgent`；将其设为全局 dispatcher 后，整个进程都会通过代理，包括基于 fetch 构建的第三方库。

```javascript
import { EnvHttpProxyAgent, setGlobalDispatcher } from "undici";
// HTTPS_PROXY=http://USER-cc-us:PASS@res.proxshift.com:9000 NO_PROXY=localhost node app.js
setGlobalDispatcher(new EnvHttpProxyAgent());
```


## 并发与轮换

- 每个粘性会话使用一个 `ProxyAgent`（各自采用不同的 `-sid-`），并由该会话的所有请求共享。
- 如需大范围轮换，可以保留一个 agent 但禁用连接复用；也可以运行 N 个 agent，分别使用 N 个会话 ID，再以轮询方式分配请求。
- 留意 `UND_ERR_SOCKET` 和 `ECONNRESET`：它们表示住宅出口已经失效。重试一次，下一个连接会选择其他出口。


## 问题

**为什么 axios 在访问 HTTPS 时会忽略我的代理？**

其内置代理功能对 CONNECT 隧道的支持有限。请将 https-proxy-agent 用作 httpsAgent，并设置 proxy: false，避免 axios 自行处理代理。

**可以只让部分请求走代理吗？**

可以。只需在需要代理的请求中传入 dispatcher 或 agent，其他请求保持不变；除非调用 setGlobalDispatcher，否则任何设置都不是全局的。

来源：https://proxshift.com/zh/guides/nodejs-fetch-undici-axios
