Erro ProxyError do Requests numa API privada? Verifique primeiro o ambiente
O Requests combina proxy, autenticação e definições de CA a partir do ambiente. Leia o que o processo vê, exclua o host privado e mantenha a verificação TLS ativa.
A VPN está ativa, o painel interno abre no navegador e o script Python continua lançando um erro de proxy. Nessa imagem não há nada de errado com o túnel: o interpretador lê um endereço de proxy do próprio ambiente e envia o pedido para lá. Este manual lê o ambiente que o processo realmente vê, aplica a exclusão mais estreita e só depois considera uma política de sessão explícita. Cada passo termina com um comando cuja saída decide se você continua.
Leia o ambiente que o processo realmente vê
As definições de proxy do ambiente de trabalho, as exportações da shell e as linhas Environment= de uma unidade systemd são três fontes diferentes, e o Requests só lê a última que herda. Execute o diagnóstico abaixo na mesma shell, contentor ou unidade que executa o script que falha, não no seu terminal interativo. Uma variável exportada na sua shell de início de sessão é invisível para um processo de contentor, e essa lacuna explica muitos relatos de um proxy que aparece do nada. Os dois comandos imprimem os mesmos dados em níveis de detalhe diferentes.
env | grep -i -E 'proxy|requests_ca_bundle|curl_ca_bundle'
python3 -c "import os;[print(k,'=',v) for k,v in sorted(os.environ.items()) if 'proxy' in k.lower()]"
Ambas as grafias de cada variável são lidas, e as entradas duplicadas são aplicadas pela ordem em que o processo as recebeu, pelo que um HTTP_PROXY desatualizado pode sobrepor-se ao valor que acabou de exportar. As variáveis do pacote de CA também contam aqui, porque alteram aquilo em que verify se resolve sem aparecer em lado nenhum do código. Se a saída estiver vazia, o ambiente não é o seu problema e pode parar de ler esta secção. Se listar um proxy que não reconhece, encontre o ficheiro ou a unidade que o define antes de alterar o que quer que seja.
Pergunte ao Requests o que faria com um URL específico. Esta chamada mostra os valores combinados de proxy, verify e cert para esse pedido sem enviar um pacote.
python3 -c "import requests;s=requests.Session();print(s.merge_environment_settings('https://api.corp.example/status',{},None,None,None))"
Uma entrada proxies não vazia para o host privado é a sua resposta, e o valor verify impresso indica em que pacote de CA o pedido confiaria. Se ambos parecerem corretos, a falha está mais adiante na pilha e deve saltar para a secção de verificação. Não adivinhe a partir do código: a combinação ocorre por pedido, e por isso uma sessão pode comportar-se de forma diferente para dois hosts.
Exclua o host privado antes de desativar qualquer coisa
Quando o proxy é realmente necessário para outros destinos, a correção estreita é uma exclusão para este host. Acrescente à lista existente em vez de a substituir, porque apagar NO_PROXY quebra qualquer outro destino interno que dependesse dele. Os comandos abaixo acrescentam a entrada e depois perguntam ao Requests se a derivação se aplica.
export NO_PROXY="${NO_PROXY:+$NO_PROXY,}api.corp.example,.corp.example"
python3 -c "import requests;print(requests.utils.should_bypass_proxies('https://api.corp.example/status',None))"
A correspondência é por sufixo do host, pelo que .corp.example cobre o domínio inteiro enquanto api.corp.example cobre um único nome. Um asterisco isolado desativa o proxy para todos os hosts e quase nunca é o que uma tarefa de produção quer. Se uma entrada CIDR é respeitada depende das versões de Requests e urllib3 na imagem, por isso teste a entrada exata em vez de assumir que funciona. Verifique o resultado de should_bypass_proxies para o URL real, não para um nome de host que escreveu de memória.
Mantenha as duas camadas separadas na cabeça. Contornar o proxy HTTP não contorna a VPN, porque a decisão do proxy acontece na biblioteca e a decisão do túnel na tabela de encaminhamento do núcleo.
Desative o trust_env apenas como política de sessão deliberada
Se a tarefa inteira deve ignorar a configuração ambiente, construa a sessão explicitamente e registe a decisão no código. Isso é uma política, não um contorno, e pertence a um diff que um revisor possa ver. O exemplo abaixo mantém um tempo limite, um caminho de CA explícito e uma verificação de estado.
import requests
with requests.Session() as session:
session.trust_env = False
response = session.get(
"https://api.corp.example/status",
timeout=(5, 20),
verify="/etc/ssl/certs/corp-ca.pem",
)
response.raise_for_status()
Desativar trust_env altera três comportamentos ao mesmo tempo, e apenas um deles diz respeito a proxies. O Requests deixa de ler credenciais de .netrc, e as variáveis REQUESTS_CA_BUNDLE e CURL_CA_BUNDLE deixam de afetar verify, que então recorre ao armazenamento de certificados incluído. Se a sua organização termina o TLS com a sua própria CA, esse recuo produz um erro de verificação que parece não ter relação com a alteração que acabou de fazer. É a passagem explícita de verify que mantém os dois efeitos separados.
O que não deve fazer é definir verify=False para silenciar o erro resultante. Isso troca uma configuração de proxy incorreta por uma ligação não verificada, e esconde o problema de certificado que a exclusão devia revelar.
Verifique a camada que falha e mantenha uma reversão
Três falhas parecem iguais num registo e não têm nada em comum: um proxy que não alcança o destino lança ProxyError ou devolve 502, um problema de confiança lança SSLError, e uma rejeição da aplicação devolve 401 ou 403. Reproduza o pedido fora do Requests para ver qual tem. Os comandos abaixo removem o proxy, testam a cadeia de certificados e depois repetem a chamada através da biblioteca.
curl -sv --noproxy '*' https://api.corp.example/status -o /dev/null
openssl s_client -connect api.corp.example:443 -servername api.corp.example </dev/null
python3 -c "import requests;r=requests.get('https://api.corp.example/status',timeout=10);print(r.status_code)"
Se a chamada curl tiver êxito e a chamada Python falhar, o ambiente continua a ser a diferença. Se ambas falharem na fase TLS, o proxy nunca foi o problema e é a cadeia de certificados. Se ambas devolverem 401, o pedido chegou à aplicação e o trabalho restante é a autenticação. Registar qual das três observou poupa uma hora à pessoa seguinte.
A reversão é uma linha em qualquer direção: remova a exclusão ou reponha trust_env. Guarde o valor anterior de NO_PROXY antes de acrescentar algo, e prefira um script de envolvimento ou uma entrada Environment= ao nível da unidade a uma exportação que só vive no histórico do seu terminal. Ambas as alterações são visíveis no ambiente do processo, pelo que um reinício costuma devolvê-lo ao comportamento antigo.
Termine confirmando quatro coisas: as definições combinadas para o URL privado não mostram qualquer proxy, o certificado valida contra a CA pretendida, o pedido devolve o estado que espera, e a alteração está consolidada num local reproduzível. Um script verde com um ambiente inexplicado é uma correção que volta a quebrar na máquina seguinte. Os comandos deste manual são os que deve colar no ticket.