aiohttp 3.14.0 (1 de junio de 2026) declaró obsoletos BasicAuth y el parámetro proxy_auth (registro de cambios, #12499). El sustituto es una cabecera Proxy-Authorization construida con 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())En nuestro laboratorio esto autenticó todas las peticiones a https:// sin ningún DeprecationWarning. No autenticó las peticiones a http:// sin cifrar: aiohttp 3.14.4 envió proxy_headers solo con la petición CONNECT que abre un túnel HTTPS, así que para una URL http:// el proxy no recibió credenciales y respondió 407.
| Credenciales pasadas como | URL https:// | URL http:// | Avisos de obsolescencia |
|---|---|---|---|
proxy_headers={"Proxy-Authorization": aiohttp.encode_basic_auth(...)} | 200 | 407 | ninguno |
proxy="http://user:pass@host:port" | 200 | 200 | ninguno |
proxy_auth=aiohttp.BasicAuth(...) | 200 | 200 | dos por llamada |
Estos resultados se registraron el 6 de octubre de 2026 con aiohttp 3.14.4 y aiohttp-socks 0.12.0 sobre Python 3.14.4, ejecutando con python -W always contra un proxy local que exige autenticación Basic. aiohttp 3.14.0 y 3.14.3 dieron los mismos resultados.
Migrar desde proxy_auth=aiohttp.BasicAuth
La llamada antigua sigue funcionando en 3.14.4 con los dos esquemas de URL:
async with session.get(url, proxy=PROXY, proxy_auth=aiohttp.BasicAuth("user", "pass")) as response:
print(response.status)Cada llamada imprimió dos avisos:
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)} insteadEl sustituto:
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)Comprueba cuatro cosas antes de desplegarlo:
- Las URL
http://sin cifrar pierden sus credenciales. La documentación muestraproxy_headerscon una URLhttp://. En el laboratorio esa petición llegó al proxy sinProxy-Authorization, y sin una cabecera propia que añadimos aproxy_headers, y volvió como 407. La incidencia #4422 informó de lo mismo con aiohttp 3.6.2. Si pides URLhttp://, pon las credenciales en la URL del proxy:proxy="http://user:pass@host:port"devolvió 200 con los dos esquemas y sin avisos. ClientSessionno tiene argumentoproxy_headers. La documentación muestraClientSession(proxy=..., proxy_headers=...). En 3.14.4 lanzóTypeError: ClientSession.__init__() got an unexpected keyword argument 'proxy_headers'. Defineproxy=en la sesión y pasaproxy_headers=con cada petición, como en el primer ejemplo.- No muevas la cabecera a
headers=. Con una URLhttps://, las cabeceras de la petición viajan dentro del túnel. ConProxy-Authorizationenheaders=, el servidor de destino del laboratorio recibió las credenciales del proxy. - La codificación por defecto cambió.
encode_basic_auth()usa UTF-8 yBasicAuthusaba latin1. Con la contraseñapässlos dos produjeron valores de cabecera distintos, así que pasaencoding="latin1"si tu proxy espera los bytes antiguos.
encode_basic_auth() es nueva en 3.14 (referencia). En aiohttp 3.13.5 el mismo código lanzó AttributeError: module aiohttp has no attribute encode_basic_auth.
Las variables de entorno necesitan trust_env=True
Una ClientSession() por defecto ignora las variables de proxy. Con HTTP_PROXY definida, la petición fue directa al destino y el proxy no registró nada. Esta sesión sí usó el proxy:
async with aiohttp.ClientSession(trust_env=True) as session:
async with session.get("http://example.com/") as response:
print(response.status)La documentación dice que aiohttp lee las variables mediante urllib.request.getproxies(), las aplica a los esquemas HTTP, HTTPS, WS y WSS, deja que los hosts de no_proxy eviten el proxy y puede tomar las credenciales del proxy de ~/.netrc. En el laboratorio, con trust_env=True:
HTTP_PROXYyhttp_proxyen minúsculas se aplicaron a las URLhttp://, yHTTPS_PROXYa las URLhttps://. Con soloHTTP_PROXYdefinida, una petición ahttps://fue directa.ALL_PROXYse ignoró.- Las credenciales en la URL de la variable autenticaron los dos esquemas y no imprimieron ningún aviso.
NO_PROXY=127.0.0.1envió la petición directa. No tuvo efecto sobre un argumentoproxy=explícito, que además tuvo prioridad sobre la variable.- Una variable con una URL de proxy
https://se omitió. aiohttp registróHTTPS proxies https://127.0.0.1:8888 are not supported, ignoringy se conectó directamente.
Variables de entorno de proxy compara estas reglas en otros clientes.
ClientHttpProxyError: 407 con URL https://
Cuando el proxy rechaza la petición CONNECT, aiohttp lanza la excepción antes de que nada llegue al destino. Con una contraseña incorrecta en la URL del proxy:
aiohttp.client_exceptions.ClientHttpProxyError: 407, message='Proxy Authentication Required', url='http://user:wrong@127.0.0.1:8888'La contraseña está en el mensaje, así que va adonde vaya la excepción: logs, trazas, gestores de errores. Con las credenciales en proxy_headers, o tomadas de HTTPS_PROXY, el mismo fallo imprimió url='http://127.0.0.1:8888'.
Mantén las credenciales fuera de la URL del proxy para el tráfico https://. Donde las necesites en la URL, registra exc.status y exc.message (407 y Proxy Authentication Required en el laboratorio) en lugar del texto de la excepción. Corrige el error de proxy 407 da el orden de comprobaciones cuando las credenciales parecen correctas.
Estado 407 sin excepción con URL http://
Con una URL http://, el 407 del proxy es una respuesta normal. No se lanza nada, response.status es 407 y la cabecera Proxy-Authenticate contiene el desafío. El código que solo captura excepciones lo trata como un éxito.
Comprueba response.status, o crea la sesión con raise_for_status=True. Eso lanzó:
aiohttp.client_exceptions.ClientResponseError: 407, message='Proxy Authentication Required', url='http://127.0.0.1:8080/'raise_for_status=True en la petición y response.raise_for_status() lanzaron el mismo error. La clase es ClientResponseError y la URL es la del destino, no la del proxy. ClientHttpProxyError es una subclase suya, así que except aiohttp.ClientResponseError con una comprobación de exc.status == 407 cubre los dos esquemas.
Bad status line: b'\x05\x00' con una URL de proxy socks5://
aiohttp no rechaza proxy="socks5://...". Envía una petición HTTP al puerto SOCKS y falla con la respuesta:
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 es el comienzo de la respuesta del servidor SOCKS5, leído como si fuera una línea de estado HTTP. socks5h:// falló igual. La solución es un conector SOCKS, que se muestra más abajo.
ClientConnectorSSLError con una URL de proxy https://
Una URL de proxy https:// le dice a aiohttp que abra TLS con el propio proxy. Contra el proxy del laboratorio, que habla HTTP sin cifrar, la petición falló durante el handshake:
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)]El proxy vio un ClientHello de TLS y respondió con un 400 en texto plano. El esquema de la URL del proxy describe la conexión con el proxy, no con el destino: una URL de proxy http:// lleva las peticiones a https:// mediante CONNECT. El texto del error viene de OpenSSL 3.5.5 y puede ser distinto en otras versiones.
DeprecationWarning: BasicAuth is deprecated
Basta con construir aiohttp.BasicAuth(...) para provocar el primer aviso, antes de cualquier petición. El parámetro auth= para las credenciales del sitio web también está obsoleto. Su aviso indica headers={'Authorization': aiohttp.encode_basic_auth(login, password)} como sustituto.
Puede que no veas ninguno de los dos. Por defecto, Python muestra un DeprecationWarning solo cuando el código que lo provocó está en __main__. En el laboratorio, la llamada obsoleta imprimió sus avisos cuando estaba en el script que se ejecutaba y no imprimió nada cuando estaba en un módulo importado. python -W always los imprimió en los dos sitios. python -W error convirtió el primero en una excepción, lo que encuentra cada punto de llamada en una ejecución de tests.
Proxies SOCKS: usa 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)Con aiohttp-socks 0.12.0 esto devolvió 200 con URL http:// y https://, y el servidor SOCKS registró cada conexión. Dos detalles:
socks5://ya envía el nombre de host al proxy. Parahttp://localhost:8080/el servidor registró un nombre de dominio. Conrdns=Falseregistró127.0.0.1.ProxyConnector.from_url("socks5h://...")lanzóValueError: Invalid scheme component: socks5h. SOCKS5 vs SOCKS5h muestra qué hace cada cliente con los dos esquemas.
Requests y HTTPX necesitan sus propios paquetes para SOCKS, que se tratan en Missing dependencies for SOCKS support.
Reintentos tras errores de proxy
Un 407, el error de línea de estado de SOCKS y el error de TLS de arriba son errores de configuración. La misma petición vuelve a fallar igual, así que corrige la configuración en lugar de reintentar. Con timeouts y conexiones cortadas, reintenta solo las peticiones que sea seguro repetir. Un POST puede haber llegado al destino antes de que fallara la conexión con el proxy. Reintentos con proxy: una respuesta perdida, dos trabajos creados muestra ese caso y qué cambia una clave de idempotencia.
Si usas HTTPX para el trabajo asíncrono, consulta Proxies async en HTTPX. Si quieres ayuda de diagnóstico dentro de un agente de programación, Proxy Toolkit MCP acepta descripciones de errores saneadas. Mantén las credenciales fuera de sus argumentos.
Cómo se probó
En Ubuntu 26.04.1 con Python 3.14.4 y OpenSSL 3.5.5, todo en 127.0.0.1: python -m http.server como destinos HTTP y HTTPS, un proxy que exige autenticación Basic y registra cada petición con los nombres de sus cabeceras, un servidor SOCKS5 que registra cada destino y un destino HTTPS que informa de las cabeceras que recibió. Cada uno de los 67 casos se ejecutó en un intérprete nuevo con un entorno limpio. No se usó ninguna cuenta de proxy.
El archivo del laboratorio contiene el ejecutor, los casos, el proxy, el servidor SOCKS5, el README y los resultados registrados.
No se probó: macOS y Windows, autenticación de proxy Digest y NTLM, proxies SOCKS con autenticación, peticiones WebSocket, credenciales de ~/.netrc y proxies a los que se llega por TLS.
ipvolt es un servicio de proxies para desarrolladores que todavía no está abierto; únete a la lista de acceso anticipado y recibirás un solo correo cuando abra.
Fuentes y lecturas adicionales
Referencias técnicas usadas para esta guía. Consulta la documentación de tu versión instalada y la configuración compatible de tu proveedor.
- 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