Интеграция7 мин чтения

Прокси в aiohttp 3.14: авторизация, trust_env, ошибки

Настройка прокси в aiohttp 3.14: чем заменить устаревшие BasicAuth и proxy_auth, как включить trust_env и что означают ошибки 407 и Bad status line. Проверено на 3.14.4.

На этой странице

В aiohttp 3.14.0 (1 июня 2026 года) класс BasicAuth и параметр proxy_auth объявлены устаревшими (список изменений, #12499). Замена — заголовок Proxy-Authorization, собранный функцией aiohttp.encode_basic_auth():

python
import asyncio

import aiohttp

PROXY = "http://proxy.example.com:8080"
PROXY_HEADERS = {"Proxy-Authorization": aiohttp.encode_basic_auth("user", "pass")}


async def main() -> None:
    async with aiohttp.ClientSession(proxy=PROXY) as session:
        async with session.get("https://example.com/", proxy_headers=PROXY_HEADERS) as response:
            print(response.status)


asyncio.run(main())

В нашей лаборатории так авторизовался каждый запрос к https://, и DeprecationWarning не появлялся. Обычные запросы к http:// не авторизовались: aiohttp 3.14.4 отправлял proxy_headers только с запросом CONNECT, который открывает HTTPS-туннель, поэтому для URL с http:// прокси не получал учётных данных и отвечал 407.

Как переданы учётные данныеURL с https://URL с http://Предупреждения об устаревании
proxy_headers={"Proxy-Authorization": aiohttp.encode_basic_auth(...)}200407нет
proxy="http://user:pass@host:port"200200нет
proxy_auth=aiohttp.BasicAuth(...)200200два на каждый вызов

Эти результаты записаны 6 октября 2026 года с aiohttp 3.14.4 и aiohttp-socks 0.12.0 на Python 3.14.4, с запуском через python -W always и локальным прокси, который требует Basic-авторизацию. aiohttp 3.14.0 и 3.14.3 дали те же результаты.

Переход с proxy_auth=aiohttp.BasicAuth

Старый вызов в 3.14.4 по-прежнему работает для обеих схем URL:

python
async with session.get(url, proxy=PROXY, proxy_auth=aiohttp.BasicAuth("user", "pass")) as response:
    print(response.status)

Каждый вызов печатал два предупреждения:

code
DeprecationWarning: BasicAuth is deprecated and will be removed in aiohttp 4.0; use aiohttp.encode_basic_auth() with headers={'Authorization': ...} instead
DeprecationWarning: The 'proxy_auth' parameter is deprecated and will be removed in v4; pass proxy_headers={'Proxy-Authorization': aiohttp.encode_basic_auth(login, password)} instead

Замена:

python
proxy_headers = {"Proxy-Authorization": aiohttp.encode_basic_auth("user", "pass")}
async with session.get(url, proxy=PROXY, proxy_headers=proxy_headers) as response:
    print(response.status)

Перед выкладкой проверьте четыре вещи:

  • Запросы к обычным http:// теряют учётные данные. В документации proxy_headers показан с URL на http://. В лаборатории такой запрос дошёл до прокси без Proxy-Authorization и без дополнительного заголовка, который мы добавили в proxy_headers, и вернулся с кодом 407. В issue #4422 о том же сообщали для aiohttp 3.6.2. Если вы запрашиваете URL с http://, укажите учётные данные в URL прокси: proxy="http://user:pass@host:port" вернул 200 для обеих схем без предупреждений.
  • У ClientSession нет аргумента proxy_headers. В документации показан вызов ClientSession(proxy=..., proxy_headers=...). В 3.14.4 он поднял TypeError: ClientSession.__init__() got an unexpected keyword argument 'proxy_headers'. Задайте proxy= у сессии, а proxy_headers= передавайте с каждым запросом, как в первом примере.
  • Не переносите заголовок в headers=. Для URL с https:// заголовки запроса идут внутри туннеля. Когда Proxy-Authorization был в headers=, целевой сервер лаборатории получил учётные данные прокси.
  • Кодировка по умолчанию изменилась. encode_basic_auth() использует UTF-8, а BasicAuth использовал latin1. Для пароля päss значения заголовка получились разными, поэтому передайте encoding="latin1", если ваш прокси ждёт прежние байты.

encode_basic_auth() появилась в 3.14 (справочник). На aiohttp 3.13.5 тот же код поднял AttributeError: module aiohttp has no attribute encode_basic_auth.

Переменным окружения нужен trust_env=True

ClientSession() по умолчанию игнорирует переменные прокси. При заданной HTTP_PROXY запрос ушёл прямо к цели, а прокси ничего не записал в журнал. Эта сессия пошла через прокси:

python
async with aiohttp.ClientSession(trust_env=True) as session:
    async with session.get("http://example.com/") as response:
        print(response.status)

В документации сказано, что aiohttp читает переменные через urllib.request.getproxies(), применяет их к схемам HTTP, HTTPS, WS и WSS, пропускает мимо прокси хосты из no_proxy и может брать учётные данные прокси из ~/.netrc. В лаборатории с trust_env=True:

  • HTTP_PROXY и http_proxy в нижнем регистре применялись к URL с http://, а HTTPS_PROXY — к URL с https://. Когда была задана только HTTP_PROXY, запрос к https:// шёл напрямую.
  • ALL_PROXY игнорировалась.
  • Учётные данные из URL в переменной авторизовали обе схемы, и предупреждений не было.
  • NO_PROXY=127.0.0.1 отправила запрос напрямую. На явный аргумент proxy= она не повлияла, и он же имел приоритет над переменной.
  • Переменная с URL прокси на https:// была пропущена. aiohttp записал HTTPS proxies https://127.0.0.1:8888 are not supported, ignoring и подключился напрямую.

Переменные окружения прокси сравнивает эти правила в других клиентах.

ClientHttpProxyError: 407 для URL с https://

Когда прокси отклоняет запрос CONNECT, aiohttp поднимает исключение раньше, чем что-либо дойдёт до целевого сервера. С неверным паролем в URL прокси:

code
aiohttp.client_exceptions.ClientHttpProxyError: 407, message='Proxy Authentication Required', url='http://user:wrong@127.0.0.1:8888'

Пароль входит в сообщение, поэтому попадает туда же, куда и исключение: в журналы, трассировки, системы сбора ошибок. Когда учётные данные были в proxy_headers или брались из HTTPS_PROXY, тот же сбой печатал url='http://127.0.0.1:8888'.

Для трафика на https:// держите учётные данные вне URL прокси. Там, где они нужны в URL, записывайте в журнал exc.status и exc.message (в лаборатории это 407 и Proxy Authentication Required), а не текст исключения. Как исправить ошибку прокси 407 даёт порядок проверок на случай, когда учётные данные выглядят верными.

Статус 407 без исключения для URL с http://

Для URL с http:// ответ 407 от прокси — обычный ответ. Исключения нет, response.status равен 407, а заголовок Proxy-Authenticate содержит запрос авторизации. Код, который ловит только исключения, считает это успехом.

Проверяйте response.status или создавайте сессию с raise_for_status=True. Это подняло:

code
aiohttp.client_exceptions.ClientResponseError: 407, message='Proxy Authentication Required', url='http://127.0.0.1:8080/'

raise_for_status=True у запроса и response.raise_for_status() подняли ту же ошибку. Класс здесь ClientResponseError, а URL — целевой, не прокси. ClientHttpProxyError — его подкласс, поэтому except aiohttp.ClientResponseError с проверкой exc.status == 407 покрывает обе схемы.

Bad status line: b'\x05\x00' при URL прокси с socks5://

aiohttp не отклоняет proxy="socks5://...". Он отправляет на порт SOCKS HTTP-запрос и падает на ответе:

code
aiohttp.client_exceptions.ClientResponseError: 400, message="Bad status line:\n  Expected HTTP/, RTSP/ or ICE/:\n\n  b'\\x05\\x00'\n    ^", url='http://127.0.0.1:8080/'

\x05\x00 — начало ответа SOCKS5-сервера, прочитанное как строка статуса HTTP. С socks5h:// сбой был таким же. Решение — SOCKS-коннектор, он показан ниже.

ClientConnectorSSLError при URL прокси с https://

URL прокси с https:// велит aiohttp открыть TLS до самого прокси. С лабораторным прокси, который говорит по обычному HTTP, запрос упал во время рукопожатия:

code
aiohttp.client_exceptions.ClientConnectorSSLError: Cannot connect to host 127.0.0.1:8888 ssl:default [[SSL: RECORD_LAYER_FAILURE] record layer failure (_ssl.c:1081)]

Прокси увидел TLS ClientHello и ответил открытым текстом кодом 400. Схема в URL прокси описывает соединение с прокси, а не с целевым сервером: URL прокси с http:// проводит запросы к https:// через CONNECT. Текст ошибки приходит из OpenSSL 3.5.5 и в других версиях может выглядеть иначе.

DeprecationWarning: BasicAuth is deprecated

Достаточно создать aiohttp.BasicAuth(...), чтобы появилось первое предупреждение, ещё до запроса. Параметр auth= для учётных данных сайта тоже устарел. Его предупреждение называет заменой headers={'Authorization': aiohttp.encode_basic_auth(login, password)}.

Вы можете не увидеть ни одного из них. По умолчанию Python показывает DeprecationWarning, только когда вызвавший его код находится в __main__. В лаборатории устаревший вызов печатал предупреждения, когда был в запускаемом скрипте, и ничего не печатал, когда был в импортированном модуле. python -W always печатал их в обоих местах. python -W error превратил первое в исключение, и так в тестовом прогоне находится каждое место вызова.

SOCKS-прокси: используйте aiohttp-socks

sh
python -m pip install aiohttp-socks
python
import aiohttp
from aiohttp_socks import ProxyConnector

connector = ProxyConnector.from_url("socks5://127.0.0.1:1080")
async with aiohttp.ClientSession(connector=connector) as session:
    async with session.get("https://example.com/") as response:
        print(response.status)

С aiohttp-socks 0.12.0 это вернуло 200 для URL с http:// и https://, а SOCKS-сервер записал каждое соединение. Две детали:

  • socks5:// уже отправляет имя хоста на прокси. Для http://localhost:8080/ сервер записал доменное имя. С rdns=False он записал 127.0.0.1.
  • ProxyConnector.from_url("socks5h://...") поднял ValueError: Invalid scheme component: socks5h. SOCKS5 и SOCKS5h показывает, что каждый клиент делает с этими двумя схемами.

Requests и HTTPX нужны собственные пакеты для SOCKS, о них рассказано в руководстве Missing dependencies for SOCKS support.

Повторные попытки после ошибок прокси

Ошибка 407, ошибка строки статуса от SOCKS и ошибка TLS выше — ошибки конфигурации. Тот же запрос снова упадёт так же, поэтому исправляйте настройку, а не повторяйте запрос. При таймаутах и оборванных соединениях повторяйте только те запросы, которые безопасно повторять. POST мог дойти до целевого сервера до того, как оборвалось соединение с прокси. Повторы через прокси: один потерянный ответ, две созданные задачи показывает этот случай и то, что меняет ключ идемпотентности.

Если для асинхронной работы вы используете HTTPX, смотрите асинхронные прокси в HTTPX. Для помощи с диагностикой внутри агента для написания кода Proxy Toolkit MCP принимает очищенные описания ошибок. Не передавайте в его аргументах учётные данные.

Как это проверялось

На Ubuntu 26.04.1 с Python 3.14.4 и OpenSSL 3.5.5, всё на 127.0.0.1: python -m http.server в роли HTTP- и HTTPS-целей, прокси, который требует Basic-авторизацию и записывает каждый запрос с именами его заголовков, SOCKS5-сервер, который записывает каждое место назначения, и HTTPS-цель, которая сообщает полученные заголовки. Каждый из 67 случаев выполнялся в новом интерпретаторе с чистым окружением. Аккаунт прокси не использовался.

Архив лаборатории содержит скрипт запуска, случаи, прокси, SOCKS5-сервер, README и записанные результаты.

Не проверялось: macOS и Windows, авторизация прокси по Digest и NTLM, SOCKS-прокси с авторизацией, запросы WebSocket, учётные данные из ~/.netrc и прокси, до которых нужно подключаться по TLS.

ipvolt — прокси-сервис для разработчиков, который пока не открыт; запишитесь в список раннего доступа, и вы получите одно письмо, когда он откроется.

Источники и дополнительное чтение

Технические материалы, использованные при подготовке руководства. Сверяйтесь с документацией вашей версии и с поддерживаемой конфигурацией вашего провайдера.