Грешка ProxyError в Requests към частен API? Първо проверете средата
Requests обединява прокси, удостоверяване и CA настройки от средата. Вижте какво вижда процесът, изключете частния хост и запазете проверката на TLS включена.
VPN е включен, вътрешният панел се отваря в браузъра, а Python скриптът все още дава прокси грешка. В тази картина с тунела няма нищо нередно: интерпретаторът чете прокси адрес от собствената си среда и изпраща заявката там. Това ръководство чете средата, която процесът действително вижда, прилага най-тясното изключване и само след това разглежда изрична политика за сесията. Всяка стъпка завършва с команда, чийто изход решава дали да продължите.
Прочетете средата, която процесът действително вижда
Настройките на прокси в работния плот, експортирането в shell и редовете Environment= на systemd unit са три различни източника и Requests чете само последния, който наследява. Изпълнете диагностиката по-долу в същия shell, контейнер или unit, който стартира неуспешния скрипт, а не в интерактивния си терминал. Променлива, експортирана в login shell, е невидима за процес в контейнер и тази разлика обяснява много сигнали за прокси, което се появява от нищото. Двете команди отпечатват едни и същи данни с различна степен на подробност.
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()]"
Четат се и двете изписвания на всяка променлива, а дублираните записи се прилагат в реда, в който процесът ги е получил, така че остаряла HTTP_PROXY може да отмени стойността, която току-що сте експортирали. Променливите за CA bundle имат значение и тук, защото променят към какво се разрешава verify, без да се появяват никъде в кода. Ако изходът е празен, средата не е проблемът ви и можете да спрете да четете този раздел. Ако в него е изброено прокси, което не разпознавате, намерете файла или unit, който го задава, преди да промените нещо.
Попитайте Requests какво би направил с конкретен URL. Това извикване показва обединените стойности за прокси, verify и cert за тази заявка, без да изпраща пакет.
python3 -c "import requests;s=requests.Session();print(s.merge_environment_settings('https://api.corp.example/status',{},None,None,None))"
Непразен запис proxies за частния хост е вашият отговор, а отпечатаната стойност verify казва на кое CA bundle заявката би се доверила. Ако и двете изглеждат правилни, повредата е по-нататък в стека и трябва да преминете към раздела за проверка. Не гадайте от кода: обединяването става за всяка заявка, затова една сесия може да се държи различно за два хоста.
Изключете частния хост, преди да изключите каквото и да е
Когато проксито наистина е нужно за други дестинации, тясното решение е изключване за този хост. Добавяйте към съществуващия списък, вместо да го заменяте, защото изтриването на NO_PROXY чупи всяка друга вътрешна дестинация, която е разчитала на него. Командите по-долу добавят записа и след това питат Requests дали обходът важи.
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))"
Съвпадението е по наставка на хоста, така че .corp.example покрива целия домейн, а api.corp.example — едно име. Гола звездичка изключва проксито за всеки хост и почти никога не е това, което иска производствена задача. Дали запис с CIDR ще бъде уважен, зависи от версиите на Requests и urllib3 в образа, затова тествайте точния запис, вместо да предполагате, че работи. Проверете резултата от should_bypass_proxies за реалния URL, а не за име на хост, което сте написали по памет.
Дръжте двата слоя отделно в главата си. Заобикалянето на HTTP прокси не заобикаля VPN, защото решението за проксито се взема в библиотеката, а решението за тунела — в таблицата за маршрутизация на ядрото.
Изключвайте trust_env само като съзнателна политика за сесията
Ако цялата задача трябва да игнорира околната конфигурация, изградете сесията изрично и запишете решението в кода. Това е политика, а не заобиколно решение, и мястото ѝ е в diff, който рецензентът вижда. Примерът по-долу запазва таймаут, изричен път до CA и проверка на статуса.
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()
Изключването на trust_env променя три поведения наведнъж и само едно от тях е за проксито. Requests спира да чете идентификационни данни от .netrc, а променливите REQUESTS_CA_BUNDLE и CURL_CA_BUNDLE спират да влияят на verify, което след това се връща към вграденото хранилище за сертификати. Ако организацията ви прекратява TLS със собствен CA, това връщане дава грешка при проверка, която изглежда несвързана с промяната, която току-що направихте. Изричното подаване на verify е това, което държи двата ефекта отделни.
Това, което не бива да правите, е да задавате verify=False, за да заглушите получената грешка. Така разменяте грешна конфигурация на проксито за непроверена връзка и скривате проблема със сертификата, който изключването е трябвало да разкрие.
Проверете повредения слой и запазете възможност за връщане назад
Три повреди изглеждат еднакво в журнала и нямат нищо общо: прокси, което не достига дестинацията, вдига ProxyError или връща 502, проблем с доверието вдига SSLError, а отказ на приложението връща 401 или 403. Възпроизведете заявката извън Requests, за да видите коя от трите имате. Командите по-долу премахват проксито, тестват веригата от сертификати и след това повтарят извикването през библиотеката.
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)"
Ако извикването с curl успее, а това с Python се провали, средата все още е разликата. Ако и двете се провалят на етапа TLS, проксито никога не е било проблемът и проблемът е веригата от сертификати. Ако и двете върнат 401, заявката е достигнала приложението и останалата работа е удостоверяване. Записването кое от трите сте наблюдавали спестява на следващия човек един час.
Връщането назад е един ред в едната или другата посока: премахнете изключването или върнете trust_env обратно. Запазете предишната стойност на NO_PROXY, преди да добавите към нея, и предпочитайте скрипт-обвивка или запис Environment= на ниво unit пред експорт, който живее само в историята на терминала ви. И двете промени се виждат в средата на процеса, така че рестартът обикновено ви връща към старото поведение.
Завършете, като потвърдите четири неща: обединените настройки за частния URL не показват прокси, сертификатът се валидира срещу предвидения CA, заявката връща статуса, който очаквате, и промяната е ангажирана на място, което може да се възпроизведе. Зелен скрипт с необяснена среда е поправка, която отново се чупи на следващата машина. Командите в това ръководство са тези, които да поставите в тикета.