curl подключается, а Python API — нет: прокси HTTPX, переменные окружения и NO_PROXY
Проверьте приоритет прокси окружения, явного proxy и NO_PROXY в HTTPX, различайте протокол цели, протокол прокси и источник CA; локальные сервисы с маркерами помогут проверить маршрут без платных API.
Если на одном компьютере curl получает HTTP-ответ, а Python сообщает об отказе в соединении, ошибке прокси или проверке сертификата, сначала сравните фактический маршрут запросов. Браузер, терминал, IDE, фоновая служба и контейнер могут использовать разные настройки. Доступная главная страница также не доказывает работоспособность вызова модели.
Статья относится к стандартной конфигурации транспорта Python HTTPX 0.28.1; источники проверены 6 ноября 2026 года. Собственные transport, mounts и обёртки SDK могут изменить маршрутизацию: проверяйте место создания реального клиента. Эксперимент ниже обращается только к локальному адресу обратной связи, не передаёт API Key и не вызывает платные модели. Его результаты не являются оценкой провайдера.
1. Сначала сравните условия проверки
Запишите для curl и Python протокол, хост, порт, путь и HTTP-метод. Добавьте место запуска, версии, способ запуска процесса, настройку перехода по редиректам и источник прокси: окружение или код. Сравнивайте один и тот же заранее проверенный, разрешённый диагностический адрес без учётных данных. Не используйте повторные генерации для поиска сетевой ошибки.
| Наблюдение | Что оно показывает | Чего оно не доказывает |
|---|---|---|
| curl получает 401 или 404, HTTPX не получает HTTP-статус | Запросы дошли до разных этапов | Что curl проверил модель или проблема именно в API Key |
| HTTPX получает отказ в соединении, в настройках есть локальный порт прокси | Нужно проверить адрес и слушающий порт локального прокси | Что провайдер недоступен |
| HTTPX сообщает ProxyError или при взаимодействии с прокси появляется 407 | Нужно проверить установление соединения, аутентификацию и правила прокси | Что поможет замена ключа API модели |
| HTTPX не может проверить сертификат | Нужно проверить фактического собеседника, доверенные CA и корпоративные настройки | Что следует отключить TLS-проверку |
| Из терминала работает, из службы — нет | Окружение при запуске может различаться | Что все процессы машины видят последние настройки |
Сохраняйте тип исключения после удаления чувствительных данных и время события. Не копируйте целиком исключения и отладочные журналы: они могут содержать URL прокси с паролем. Ошибка описывает отдельную попытку и не доказывает, что задача целиком не повторялась или не будет оплачена. Для обращения в поддержку пригодится шаблон минимального воспроизведения и обезличенного обращения.
2. Разделяйте протокол цели и протокол прокси
HTTPX по умолчанию использует настройки окружения. HTTP_PROXY задаёт прокси для HTTP-целей, HTTPS_PROXY — для HTTPS-целей, ALL_PROXY может задавать общий прокси, а NO_PROXY определяет цели, которые не должны проходить через прокси из окружения.[1]
Значение HTTPS_PROXY не обязано начинаться с https://. HTTPS в адресе API описывает защищённое соединение с целевым сервером, а схема URL прокси — способ соединения с самим прокси. При обычном HTTP-прокси доступ к HTTPS-цели обычно начинается с создания туннеля CONNECT; затем клиент выполняет TLS-рукопожатие с целью внутри туннеля. Поэтому адрес прокси часто имеет вид http://хост-прокси:порт.[2] Используйте протокол, подтверждённый администратором или документацией прокси, а не меняйте схему только из-за HTTPS у цели.
Проверьте и сетевое окружение адреса. В контейнере 127.0.0.1 обозначает сам контейнер, а на удалённом сервере — этот сервер. Ни один из этих адресов не указывает автоматически на прокси вашего ноутбука. Популярный номер порта не означает, что нужная служба действительно там запущена.
Учётные данные прокси и API Key — разные секреты. URL прокси может включать имя пользователя и пароль: не публикуйте полное значение в обращениях, снимках экрана или репозитории. Локально проверьте наличие настройки, протокол и обезличенный адрес, а способ аутентификации уточните через установленный безопасный канал.
3. Наличие переменной ещё не означает, что клиент её использует
Для обычного Client в HTTPX 0.28.1 исходный код задаёт следующие отношения между настройками:[3]
| Способ создания или настройка | Как это понимать |
|---|---|
| proxy не передан, используется стандартный trust_env=True | Читаются прокси окружения и их правила NO_PROXY |
| proxy=None при стандартном trust_env=True | Прокси окружения всё ещё может использоваться; это не принудительное прямое соединение |
| proxy явно содержит адрес | Используется явный прокси; исключения NO_PROXY из окружения автоматически не объединяются с ним |
| trust_env=False без явного прокси и собственной маршрутизации | Прокси окружения не используются |
| trust_env=False вместе с явным proxy | Явный прокси остаётся действующим |
| Собственный transport или mounts | Нужна отдельная проверка: таблица стандартной маршрутизации недостаточна |
HTTPX строит маршруты прокси при создании клиента. После изменения NO_PROXY в том же процессе не следует ожидать, что существующий Client автоматически перечитает настройки. Завершите старые запросы, закройте старый клиент и создайте новый с проверкой конфигурации. Изменение файла для службы или контейнера также не означает, что настройки уже работающего процесса изменились. Не закрывайте общий клиент с производственными запросами только ради диагностики.
Кроме того, HTTPX 0.28.1 получает сведения о прокси через urllib.request.getproxies из Python. При определённых условиях в Windows и macOS возможен переход к системным настройкам.[4] Поэтому отсутствие перечисленных переменных ещё не доказывает прямое соединение. И наоборот, само наличие переменной не подтверждает итоговый маршрут: сопоставляйте явные параметры и фактические записи прокси.
У curl собственные правила и источники конфигурации. Например, официальная документация задаёт строчное имя http_proxy и отдельно описывает правила доменов и CIDR для NO_PROXY.[6] В Windows имена переменных окружения обычно нечувствительны к регистру: не переносите туда эксперименты с регистром из Unix. Также не считайте, что шаблоны, начальная точка, CIDR или запись порта имеют одинаковое значение во всех клиентах. Сначала сравнивайте узкие исключения с точным именем хоста или IP; отдельно проверяйте localhost и 127.0.0.1.
4. Проверьте четыре маршрута на локальных сервисах
Для скрипта нужен установленный HTTPX; фактически проверена версия 0.28.1. Сохраните код как proxy_demo.py и запустите интерпретатором Python с этой версией. Скрипт создаёт два сервиса на случайных портах только интерфейса 127.0.0.1. Первый возвращает origin. Второй играет роль входа прокси и возвращает proxy, ничего никуда не пересылая.
Скрипт временно заменяет связанные настройки только в собственном отдельном процессе и восстанавливает их при завершении или исключении. Не встраивайте такой эксперимент с изменением окружения в многопоточную производственную службу. У запросов установлен тайм-аут 2 секунды для отдельных этапов, переходы по редиректам отключены, принимается не более 32 байт ответа, после чего ответ закрывается. Этот тайм-аут не является строгим общим бюджетом запроса, а предел байт не ограничивает память низкоуровневых сетевых буферов.
import os
from contextlib import contextmanager
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from threading import Thread
import httpx
NAMES = {"http_proxy", "https_proxy", "all_proxy", "no_proxy",
"ssl_cert_file", "ssl_cert_dir", "request_method"}
@contextmanager
def isolated_env(proxy):
saved = {k: v for k, v in os.environ.items() if k.lower() in NAMES}
try:
for k in list(os.environ):
if k.lower() in NAMES:
del os.environ[k]
for k in ("HTTP_PROXY", "HTTPS_PROXY", "ALL_PROXY"):
os.environ[k] = proxy
os.environ["NO_PROXY"] = ""
yield
finally:
for k in list(os.environ):
if k.lower() in NAMES:
del os.environ[k]
os.environ.update(saved)
@contextmanager
def endpoint(marker):
class Handler(BaseHTTPRequestHandler):
def setup(self):
super().setup()
self.connection.settimeout(2)
def do_GET(self):
body = marker.encode("ascii")
self.send_response(200)
self.send_header("Content-Length", str(len(body)))
self.send_header("Connection", "close")
self.end_headers()
self.wfile.write(body)
def log_message(self, *_):
pass
server = ThreadingHTTPServer(("127.0.0.1", 0), Handler)
thread = Thread(target=server.serve_forever, daemon=True)
thread.start()
try:
yield f"http://127.0.0.1:{server.server_port}"
finally:
server.shutdown()
server.server_close()
thread.join(timeout=3)
def read_marker(client, url):
with client.stream("GET", url) as response:
response.raise_for_status()
data = bytearray()
for chunk in response.iter_raw():
if len(data) + len(chunk) > 32:
raise ValueError("Unexpected response size")
data.extend(chunk)
return data.decode("ascii")
def main():
with endpoint("origin") as origin, endpoint("proxy") as proxy:
with isolated_env(proxy):
cases = [
("env", "", {}, "proxy"),
("no_proxy", "127.0.0.1", {}, "origin"),
("explicit_proxy", "127.0.0.1", {"proxy": proxy}, "proxy"),
("ignore_env", "", {"trust_env": False}, "origin"),
]
for label, bypass, options, expected in cases:
os.environ["NO_PROXY"] = bypass
with httpx.Client(timeout=2, follow_redirects=False,
**options) as client:
actual = read_marker(client, origin)
if actual != expected:
raise AssertionError(f"{label}: {actual} != {expected}")
print(label, actual)
if __name__ == "__main__":
main()
Ожидаются четыре строки: env proxy, no_proxy origin, explicit_proxy proxy, ignore_env origin. Особенно важна третья: при явно переданном proxy переменная NO_PROXY не переводит цель на прямое соединение автоматически.
Дополнительные локальные проверки подтвердили сохранение старого маршрута существующим клиентом и чтение новых настроек новым клиентом; использование окружения при proxy=None; сохранение явного прокси при trust_env=False; границу 32/33 байта и восстановление окружения после исключения. Всего прошли 13 проверок. Проверка сертификатов касалась только чтения несуществующего пути CA при создании клиента.
Эксперимент не реализует HTTPS CONNECT, TLS-рукопожатие, SOCKS, аутентификацию прокси или реальную пересылку. Он также не проверяет сетевой маршрут curl. Результат показывает лишь выбор локального входа данной версией HTTPX в контролируемых условиях и не доказывает исправление корпоративного прокси или реального API.
5. trust_env=False меняет не только использование прокси
В исходном коде create_ssl_context для HTTPX 0.28.1 стандартная ветка verify=True при trust_env=True сначала проверяет SSL_CERT_FILE, затем SSL_CERT_DIR. При trust_env=False эти источники окружения пропускаются и используется стандартный набор CA из certifi.[5] Таким образом, изменение trust_env может одновременно изменить прокси и источник доверенных CA. Нельзя объяснять исчезнувшую или появившуюся ошибку сертификата исключительно сменой маршрута.
Это не отключение проверки сертификата. verify=False — отдельное опасное изменение, которое в этой инструкции не требуется. Если корпоративная среда использует управляемые CA, сопровождающий должен подтвердить доверенный источник сертификатов и при необходимости явно передать корректный SSLContext, сохранив проверку имени хоста и цепочки. Поведение уже переданного SSLContext нельзя выводить из стандартной ветки.
При проверке обнаружилось расхождение: текст старой SSL-документации под тегом 0.28.1 об автоматическом чтении переменных сертификатов не соответствует исходному коду под тем же тегом. Вывод этого раздела ограничен проверенным кодом и локальным результатом создания клиента; он не переносится на другие версии. Истёкшие сертификаты, несовпадение имени и отсутствие CA разобраны в инструкции по ошибкам SSL.
6. Завершите проверку в исходном окружении сбоя
- Подтвердите разрешённый организацией способ выхода в сеть и ответственный источник прокси: окружение запуска, настройки приложения или явный код. Меняйте только необходимые параметры, не заменяя одновременно прокси, сертификаты, модель и ключ.
- Пусть сопровождающий проверит настройки реального процесса. Не выгружайте всё окружение: достаточно наличия нужных переменных, обезличенных адресов, совпадения цели с NO_PROXY, версии клиента и времени вступления настроек в силу.
- Для внутренних целей, которым требуется прямой доступ, задавайте узкие исключения и проверяйте их. NO_PROXY=* резко расширяет обход прокси и не подходит как универсальное производственное исправление. Для явного прокси исключения нужно настраивать средствами маршрутизации приложения, а не только добавлением переменной окружения.
- Примените настройки по процедуре развёртывания и пересоздайте клиент в исходной службе или контейнере. Проверьте тот же адрес без учётных данных, тем же методом и без автоматических редиректов. Запишите HTTP-статус или этап сбоя; в разрешённых пределах сопоставьте результат с журналом прокси.
- После восстановления сети и проверки сертификатов ответственная за приложение сторона должна проверить необходимые задачи модели с ограниченными тестовыми учётными данными и бюджетом. HTTP-ответ без авторизации и локальный маркер не заменяют проверку прав, завершения потока и начислений.
Если маршрут подтверждён, но при параллельной работе остаётся PoolTimeout, перейдите к проверке пула HTTPX и освобождения потоковых ответов. Дальнейшая смена переменных прокси не устраняет ожидание свободного соединения в пуле.
7. Источники и границы применимости
Первоисточники проверены 6 ноября 2026 года. Материалы с фиксированной версией описывают HTTPX 0.28.1. Документация curl приведена для пояснения различий клиентов; её правила сопоставления не перенесены на HTTPX.