В aiohttp 3.14.0 (1 июня 2026 года) класс BasicAuth и параметр proxy_auth объявлены устаревшими (список изменений, #12499). Замена — заголовок Proxy-Authorization, собранный функцией aiohttp.encode_basic_auth():
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(...)} | 200 | 407 | нет |
proxy="http://user:pass@host:port" | 200 | 200 | нет |
proxy_auth=aiohttp.BasicAuth(...) | 200 | 200 | два на каждый вызов |
Эти результаты записаны 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:
async with session.get(url, proxy=PROXY, proxy_auth=aiohttp.BasicAuth("user", "pass")) as response:
print(response.status)Каждый вызов печатал два предупреждения:
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Замена:
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 запрос ушёл прямо к цели, а прокси ничего не записал в журнал. Эта сессия пошла через прокси:
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 прокси:
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. Это подняло:
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-запрос и падает на ответе:
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, запрос упал во время рукопожатия:
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
python -m pip install aiohttp-socksimport 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 — прокси-сервис для разработчиков, который пока не открыт; запишитесь в список раннего доступа, и вы получите одно письмо, когда он откроется.
Источники и дополнительное чтение
Технические материалы, использованные при подготовке руководства. Сверяйтесь с документацией вашей версии и с поддерживаемой конфигурацией вашего провайдера.
- aiohttp changelog: 3.14.0
- aiohttp pull request #12499: Deprecate auth. Add encode_basic_auth
- aiohttp Advanced Client Usage: proxy support and trust_env
- aiohttp Client Reference: encode_basic_auth and proxy_headers
- aiohttp issue #4422: proxy_headers not passed to proxy
- aiohttp-socks on PyPI
- Python warnings: the default warning filter