ProxyError во Requests на приватен API? Прво проверете го опкружувањето
Requests ги спојува поставките за прокси, автентикација и CA од опкружувањето. Прочитајте што гледа процесот, исклучете го приватниот домаќин и оставете TLS-верификацијата вклучена.
VPN е вклучен, внатрешната табла се отвора во прелистувач, а Python-скриптата сѐ уште фрла грешка за прокси. На таа слика со тунелот ништо не е погрешно: интерпретаторот чита адреса на прокси од сопственото опкружување и го праќа барањето таму. Ова упатство го чита опкружувањето што процесот навистина го гледа, применува најтесното исклучување и само потоа разгледува изречна политика за сесија. Секој чекор завршува со команда чиј излез одлучува дали продолжувате.
Прочитајте го опкружувањето што процесот навистина го гледа
Поставките за прокси на работната површина, извозот во shell и линиите Environment= на systemd-единица се три различни извори, а Requests ја чита само последната што ја наследува. Пуштете ја дијагностиката подолу во истиот shell, контејнер или единица што ја извршува скриптата што не успева, а не во вашиот интерактивен терминал. Променливата извезена во вашиот 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-пакетот се важни и овде, бидејќи менуваат во што се разрешува verify без да се појават никаде во кодот. Ако излезот е празен, опкружувањето не е вашиот проблем и можете да престанете да го читате овој дел. Ако наведува прокси што не го препознавате, пронајдете ја датотеката или единицата што го поставува пред да промените нешто.
Прашајте го 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-пакет барањето би му верувало. Ако двете изгледаат точно, дефектот е подолу во стекот и треба да преминете на делот за верификација. Не претпоставувајте од кодот: спојувањето се случува за секое барање, затоа една сесија може да се однесува различно за два домаќини.
Исклучете го приватниот домаќин пред да исклучите било што
Кога проксито навистина е потребно за други одредишта, тесното решение е исклучување за овој домаќин. Додајте на постојната листа наместо да ја замените, бидејќи бришењето на 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= на ниво на единица наместо извоз што живее само во историјата на вашиот терминал. Двете промени се видливи во околината на процесот, така што рестартирањето обично ве враќа на старото однесување.
Завршете со потврдување на четири работи: споените поставки за приватниот URL не покажуваат прокси, сертификатот се валидира против предвидениот CA, барањето го враќа статусот што го очекувате, а промената е зачувана на место што може да се репродуцира. Зелена скрипта со необјаснета околина е поправка што повторно се крши на следната машина. Командите во ова упатство се тие што треба да ги залепите во тикетот.