Configurando Proxy no NodeJS

Configurando Proxy no NodeJS


Recentemente, um projeto exigia que o NodeJS usasse um proxy. Inicialmente pensei que seria uma configuração simples, mas acabou sendo bastante desafiador.

O NodeJS não lê os proxies do sistema por padrão, e como o NodeJS tem múltiplos métodos para enviar requisições de rede, as formas de configurar proxies também variam. Aqui está um resumo.

Pré-requisitos

Vamos assumir que os endereços do servidor proxy vêm de variáveis de ambiente, que é a prática padrão para muitos aplicativos web. Por conveniência, vamos chamá-los de HTTP_PROXY e HTTPS_PROXY.

Fetch

Versões recentes do NodeJS suportam a interface Fetch, que tenta manter consistência com o Fetch do navegador, mas se fosse completamente consistente, não haveria como configurar proxies. Felizmente, o NodeJS abriu uma porta dos fundos ao adicionar um parâmetro único dispatcher para modificar parâmetros de requisição de rede, incluindo servidores proxy.

Podemos usar o pacote undici para configurar o dispatcher:

npm install undici

Configurando proxy para fetch

Use ProxyAgent para configurar:

import { ProxyAgent } from "undici";

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

Configuração global de proxy para fetch

setGlobalDispatcher é usado para configurar proxies para todas as chamadas fetch no programa NodeJS:

import { setGlobalDispatcher, ProxyAgent } from "undici";

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

Para remover o proxy, você pode definir um Agent vazio:

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

Como funciona

setGlobalDispatcher na verdade usa um ponto de entrada oculto no NodeJS. O objeto global no NodeJS tem um Symbol para definir o dispatcher global:

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

Isso não parece estar documentado no NodeJS (pelo menos não consegui encontrar). Para mais detalhes, veja: https://github.com/nodejs/node/issues/43187

O que mais o dispatcher pode fazer além de configurar proxies

Se você importar Agent em vez de ProxyAgent, também pode configurar parâmetros de rede como keepAlive e 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);

Para mais detalhes, consulte a documentação do undici.

Módulos nativos do NodeJS http e https

Como mencionado acima, o NodeJS não tem métodos incorporados para configurar proxies, então precisamos desenvolvê-los usando http.Agent e https.Agent. Atualmente, os pacotes populares são http-proxy-agent e https-proxy-agent.

Aqui está um exemplo usando 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) => {
      // Processar resposta
    }
  );
}

Axios

O Axios é quase o padrão da indústria para JavaScript. Ele tem múltiplas formas de configurar proxies (em ordem de prioridade do menor para o maior):

  1. Variáveis de ambiente: O próprio Axios lê as variáveis de ambiente HTTP_PROXY e HTTPS_PROXY

  2. Parâmetro proxy:

    import axios from "axios";
    
    axios.get("https://example.com", {
      proxy: {
        host: "127.0.0.1",
        port: 7890,
      },
    });
  3. Parâmetros httpAgent e httpsAgent: Você pode passar os http.Agent e https.Agent incorporados do NodeJS para controlar parâmetros de rede.

    Aqui usamos os http-proxy-agent e https-proxy-agent mencionados na seção anterior:

    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,
      });
    }

Para mais detalhes, consulte a documentação do Axios.

Limitações de conexão HTTPS

Descobri que se você configurar um proxy HTTP usando os dois primeiros métodos, o Axios não consegue estabelecer conexões HTTPS. A razão é que o Axios não envia o método CONNECT, então não consegue estabelecer conexões TLS (se você não entende o método CONNECT, pode ler este artigo).