Configurer un proxy HTTPX : authentification, environnement et délais

16 min de lecture

Définissez un proxy HTTPX avec proxy=, passez les identifiants, gardez le HTTPS sur CONNECT, et distinguez les défauts d'environnement des mounts et des délais explicites.

Ce guide s'adresse aux développeurs Python qui ont besoin qu'HTTPX 0.28.1 sorte par un proxy HTTP ou SOCKS5. Les contrôles éditoriaux privés ont utilisé des origines en loopback et un proxy CONNECT factice, pas un compte fournisseur. Pas de clés gratuites, et Top Proxy ne vend pas d'IP. Ces contrôles prouvent uniquement cette route de laboratoire. Ce n'est pas un téléchargement public.

01Router une requête avec proxy=

Le client installé accepte proxy= sur httpx.get et sur httpx.Client. Les anciennes versions d'HTTPX acceptaient aussi un mapping proxies= de motifs d'URL vers des URL de proxy. C'était HTTPX lui-même, pas un dict réservé à Requests. HTTPX 0.28.0 (28 novembre 2024) a retiré l'argument déprécié proxies. Ce tutoriel reste sur proxy= et mounts= de 0.28.1. La forme à un proxy est une chaîne d'URL ou un objet httpx.Proxy.

Les proxies HTTP (consulté le 14 octobre 2026) indiquent que vous passez cette URL à l'init du client ou aux helpers de premier niveau. Un seul proxy="http://127.0.0.1:PORT" envoie HTTP et HTTPS par ce saut. Le HTTPS n'a pas besoin d'une URL de proxy https://. Le schéma de la cible et le schéma du proxy sont des boutons différents.

  1. Épinglez le client que vous allez réellement importer. Ce tutoriel a utilisé HTTPX 0.28.1, httpcore 1.0.9 et Python 3.14.6.
  2. Construisez un Client avec proxy= réglé sur l'URL du proxy HTTP. Laissez trust_env=False le temps de prouver le chemin explicite, pour que des variables de shell restantes ne volent pas la route.
  3. Récupérez une origine HTTP que vous contrôlez. Lisez le journal du proxy, pas seulement le code de statut. Le relais factice du laboratoire a enregistré GET http://httpx-lab.origin:<port>/ avec authorized: true.
  4. Fermez le client avec un gestionnaire de contexte. Cela ferme le pool de transport. Un client oublié est une socket qui fuit, pas un script plus rapide.

Le témoin direct compte. Après avoir retiré HTTP_PROXY, HTTPS_PROXY, ALL_PROXY et NO_PROXY dans un processus enfant, un client sans proxy= a touché l'origine HTTP et le proxy factice est resté inactif. C'est le contrôle sans proxy. Cela ne dit pas ce qu'HTTPX ferait si ces variables étaient encore posées. Cela vient plus loin.

Si vous avez besoin de deux sorties, n'entassez pas deux URL dans proxy=. Utilisez mounts avec HTTPTransport(proxy=...) par motif, et placez verify, trust_env et retries sur chaque transport. L'argument verify= au niveau du client n'a pas voyagé avec dans cette compilation. Un mount qui n'héritait que de Client(verify=ctx) a échoué en TLS avec ConnectError / SSLCertVerificationError : unable to get local issuer certificate. La même requête a réussi quand le transport lui-même a reçu verify=ctx.

02Choisir un proxy HTTP pour une cible HTTPS

On appelle cela un « proxy HTTPS » parce que le site est https://. Le saut devant est encore un proxy HTTP qui parle CONNECT. La documentation le signale comme un piège : la clé https:// d'un dict mounts est le schéma de la cible, et l'URL de proxy devant est en général http://.

Relais et tunnel ne sont pas le même mécanisme. Pour http://, le proxy fait la requête d'origine et renvoie les octets d'origine. Pour https://, le client envoie CONNECT host:443, le proxy ouvre le TCP, et le TLS s'exécute entre client et origine à travers ce tunnel. Un faux 200 de CONNECT sans tunnel d'octets qui marche n'est pas du proxy HTTPS. Le laboratoire a relayé de vrais corps d'origine en HTTP et de vrais octets TLS après CONNECT.

  1. Gardez l'URL de proxy sur http://127.0.0.1:<port> même quand la cible est https://httpx-lab.origin:<port>/.
  2. Faites confiance à l'origine avec ssl.create_default_context(cafile=ca.pem) et passez ce contexte comme verify=. N'éteignez pas la vérification.
  3. Confirmez que le journal du proxy montre CONNECT httpx-lab.origin:<https-port> avant de célébrer un 200.
  4. Répétez une fois avec le magasin d'AC par défaut et sans l'AC du laboratoire. Vous voulez un échec TLS qui a quand même journalisé CONNECT.

Cette dernière étape est celle que les opérateurs sautent. Dans cette exécution, CONNECT a réussi, puis HTTPX a levé ConnectError en enveloppant SSLCertVerificationError : unable to get local issuer certificate. Le tunnel était bon. Le certificat ne l'était pas. Une page navigateur Mon IP aurait montré la sortie du navigateur, pas ce client. Utilisez le vérificateur de proxies pour voir si un hôte répond en HTTP ou en SOCKS5 avant de pointer HTTPX dessus.

SOCKS5 est un troisième chemin. pip install httpx[socks] a tiré socksio 1.0.0. httpx.Proxy en 0.28.1 accepte socks5 et socks5h. Contre un montage SOCKS5 contrôlé, socks5://127.0.0.1:<port> vers http://127.0.0.1:<origin>/ a envoyé ATYP ipv4. Le même client avec socks5h:// et une cible par nom d'hôte a envoyé ATYP domain httpx-lab.origin. Utiliser socks5:// avec ce nom d'hôte a aussi envoyé ATYP domain. Cette compilation n'a pas résolu le nom d'hôte en local pour ensuite envoyer une adresse IPv4, comme le font certains montages curl socks5. Ne copiez pas le folklore socks5h de Requests dans HTTPX. Mesurez la négociation.

03Régler l'authentification du proxy

Les identifiants du proxy ne sont pas auth=. auth= est l'authentification d'origine. Les mélanger, c'est déboguer le mauvais saut pendant une heure.

La page proxies met le userinfo dans l'URL du proxy. L'API documente aussi httpx.Proxy(url, auth=(user, password)). Les deux ont marché au laboratoire. Le mot de passe factice incluait @, :, /, # et %. Ces caractères n'appartiennent au userinfo qu'après un encodage URL composant par composant, ou dans le tuple auth= de Proxy, qui ne passe jamais par l'analyse d'URL. Journaux et figures ont masqué la valeur. N'imprimez pas Proxy-Authorization quand vous déboguez.

Identifiants manquants et identifiants faux ne sont pas un seul comportement.

Sur le relais HTTP, une requête sans utilisateur proxy a renvoyé HTTP 407 depuis le montage. HTTPX l'a fait apparaître comme status_code == 407. Un mauvais mot de passe a fait de même. Le montage a journalisé authReason: missing et authReason: wrong. Pas d'exception. Si votre enveloppe lève plus tard, gardez le 407.

Sur CONNECT HTTPS, les mêmes identifiants factices manquants ou faux ont levé httpx.ProxyError avec le message 407 Proxy Authentication Required. Il n'y avait pas d'objet réponse à lire. CONNECT est une mise en place de tunnel. Un 407 là est un échec de transport, pas une page d'origine.

Les bons identifiants ont renvoyé l'origine 200 sur les deux chemins. L'origine n'a jamais vu Proxy-Authorization. Cet en-tête reste sur le saut proxy. Si vous avez aussi besoin du Basic d'origine, envoyez auth=(origin_user, origin_pass) sur le client. La route /origin-auth du laboratoire a renvoyé 401 sans cela et 200 avec les deux sauts authentifiés.

  1. Placez l'utilisateur et le mot de passe du proxy dans httpx.Proxy(..., auth=(user, password)) quand le mot de passe a des caractères réservés.
  2. Gardez auth= d'origine pour le site, pas pour le proxy.
  3. Traitez HTTP 407 et ProxyError de CONNECT comme des issues différentes. Enregistrez la classe que vous avez réellement attrapée.
  4. Rejetez l'idée que « ça marche dans curl » prouve qu'HTTPX a envoyé le même en-tête. Prouvez-le sur ce client.

Les produits résidentiels documentent la même scission. Oxylabs Residential Proxies (consulté le 14 octobre 2026) liste HTTP, HTTPS, HTTP/3 et SOCKS5, avec identifiant/mot de passe ou liste d'autorisation d'IP, et l'entrée pr.oxylabs.io:7777 utilisant customer-USERNAME:PASSWORD. C'est une forme de point d'accès documentée, pas un test de leur pool. Le démarrage rapide résidentiel Decodo (le même jour) montre gate.decodo.com:7000 et un mapping Python qui envoie http et https par une seule URL de proxy HTTP. Le catalogue anglais liste encore ce produit comme smartproxy-com. Le nom public sur la documentation est Decodo. Le chemin n'a pas changé.

04Comprendre les défauts d'environnement et les mounts

HTTPX lit l'environnement par défaut. Les variables d'environnement (consulté le 14 octobre 2026) listent HTTP_PROXY, HTTPS_PROXY, ALL_PROXY et NO_PROXY, plus SSL_CERT_FILE et SSL_CERT_DIR. trust_env=False coupe tout cet ensemble. Cela ne veut pas dire « sauter les contrôles de certificat ».

Le processus enfant du laboratoire a démarré avec ces variables retirées, plus les restes CGI, REQUESTS_CA_BUNDLE, CURL_CA_BUNDLE et SSLKEYLOGFILE. Il a utilisé un fichier NETRC privé vide. Le HOME du poste a été laissé tel quel. Chaque expérience d'environnement partait de ce témoin et n'injectait que des valeurs factices nommées.

Le contournement loopback vous mentira. Les clients HTTP sautent souvent 127.0.0.1. Les cas de proxy d'environnement ont utilisé le nom d'hôte httpx-lab.origin et NO_PROXY="", avec un patch getaddrinfo borné qui mappe ce nom vers loopback. Ce patch est un comportement de résolveur injecté. Ce n'est pas une mesure du DNS public.

  1. Exportez HTTP_PROXY et HTTPS_PROXY vers l'URL de proxy encodée. Réglez NO_PROXY sur une chaîne vide quand la cible doit utiliser le proxy.
  2. Construisez Client(trust_env=True) sans argument proxy=. La requête du laboratoire a renvoyé 200 et seul le proxy alpha a journalisé le GET. Le proxy bravo est resté inactif.
  3. Répétez avec NO_PROXY=httpx-lab.origin. L'origine a encore renvoyé 200. Alpha n'a pas vu ce GET. C'est le contournement, et c'est un cas dédié, pas un effet de bord que vous supposez.
  4. Répétez avec les mêmes variables d'environnement et trust_env=False. La requête est partie en direct. Alpha n'a rien vu.

Un proxy= explicite a gagné contre un environnement conflictuel. Avec alpha dans HTTP_PROXY et proxy= pointant vers bravo, bravo a reçu le GET et alpha non. Un statut 200 seul ne vous aurait pas dit quel saut a été utilisé. Deux identités instrumentées l'ont fait.

trust_env=False a aussi empêché SSL_CERT_FILE de devenir le magasin d'AC du client. Avec l'AC du laboratoire dans SSL_CERT_FILE et trust_env=True, le HTTPS vers l'origine du laboratoire a renvoyé 200. Avec trust_env=False et verify=True par défaut, la même URL a levé ConnectError / SSLCertVerificationError. Passer verify=ssl.create_default_context(cafile=ca.pem) sur ce client trust_env=False a rétabli 200. La vérification est restée allumée. Seule la lecture de l'environnement a disparu.

transport= et mounts= sont des arguments de constructeur différents. HTTPX 0.28.1 pose allow_env_proxies = trust_env and transport is None. Un objet transport= personnalisé saute les mounts de proxy d'environnement. Un dict mounts= ne le fait pas. Le client construit d'abord les mounts d'environnement, puis met à jour cette carte avec vos mounts. Les motifs d'URL plus spécifiques gagnent. http:// issu de HTTP_PROXY bat all://, donc Client(mounts={"all://": some_transport}, trust_env=True) peut encore envoyer les cibles HTTP par le proxy hérité. Réglez trust_env=False quand le routage d'environnement doit être exclu. Placez proxy= et verify= sur chaque HTTPTransport monté. Le verify= au niveau du client ne se copie toujours pas sur ces transports. C'est ce dernier oubli qui laisse l'échec d'AC non fiable revenir.

05Tester le cycle de vie sync et async

Sync et async sont deux clients. Ils partagent la forme proxy=. Ils ne partagent pas un pool de connexions.

with httpx.Client(proxy=..., verify=ctx) as client: est la forme sync. La fermeture se produit à la sortie du contexte. async with httpx.AsyncClient(proxy=..., verify=ctx) as client: est la forme async. Vous await client.get(...). Mélanger get sans await dans le client async est un bug, pas un délai.

Le cas async de ce laboratoire a utilisé AsyncClient avec la même URL de proxy HTTP et le contexte SSL du laboratoire. await client.get(https_url) a renvoyé 200 avec la marque d'origine HTTPS. Le proxy a journalisé CONNECT. C'est un tunnel async réussi. Ce n'est pas un banc de concurrence, et cela ne dit rien de la réutilisation du pool sous charge.

Les transports montés ont besoin du même chemin de fermeture. Un HTTPTransport créé à la main est fermé par le client si le client le possède via mounts. Si vous gardez une variable de transport vivante d'un client à l'autre, appelez transport.close() dans finally. Le laboratoire l'a fait sur les cas de retry et de mount.

Les nouvelles tentatives appartiennent au transport. Transports (consulté le 14 octobre 2026) dit que HTTPTransport(retries=...) réessaie ConnectError et ConnectTimeout. Il ne réessaie pas 429 ou 503. Le laboratoire a posé retries=2 et a récupéré /status/503. L'origine a compté un hit. HTTPX a renvoyé 503. Ce n'est pas Retry d'urllib3. Ne collez pas le folklore allowed_methods dans HTTPX.

06Diagnostiquer les échecs de connexion, de lecture de corps et de TLS

Nommez l'exception que vous avez attrapée. HTTPX a une vraie hiérarchie. Tout ramener à « erreur de proxy » cache le saut qui a échoué.

Un écouteur loopback fermé a produit ConnectError en enveloppant ConnectionRefusedError : [Errno 61] Connection refused. C'est un refus. Ce n'est pas un délai de connexion. Sur ce noyau Darwin, un socket lié et retenu mais jamais mis en listen n'a pas refusé. HTTPX a levé ConnectTimeout en enveloppant TimeoutError après le délai de connexion configuré. Cette attente est étiquetée comportement de montage injecté pour bind-without-listen. Ce n'est pas une mesure de l'internet public.

Les délais de lecture concernent les chunks après que la réponse a commencé. L'origine /slow-body a envoyé les en-têtes complets immédiatement, a écrit et vidé un octet de corps, puis a attendu plus longtemps que Timeout(read=0.45). En streamant le corps, HTTPX a renvoyé le statut 200, l'événement first-byte s'est déclenché, un octet est arrivé, puis ReadTimeout en enveloppant TimeoutError. Les en-têtes étaient déjà là. ConnectError aurait été la mauvaise classe. ConnectTimeout aurait été la mauvaise classe. Si vous n'appelez que response.text sans streamer, vous timeouttez encore, mais vous perdez la preuve que les en-têtes sont arrivés.

Les échecs TLS après CONNECT sont restés ConnectError avec SSLCertVerificationError. Le proxy avait déjà accepté le tunnel. Mettre verify=False le cacherait. Ne le faites pas.

Utilisez des proxies mobiles ou des proxies résidentiels quand la tâche est une sortie attribuée par un opérateur. Le support de protocole et l'origine de l'IP sont des dimensions différentes. Ce laboratoire n'a jamais revendiqué un pays, un taux de succès ou un compte.

ÉchecCe que le laboratoire 0.28.1 a levéCe que ce n'est pas
Proxy HTTP, Basic manquant ou fauxStatut de réponse 407Une exception CONNECT
CONNECT HTTPS, Basic manquant ou fauxProxyError ("407 Proxy Authentication Required")Une page 407 d'origine
HTTPS du laboratoire sans l'AC du laboratoireConnectError / SSLCertVerificationError après CONNECTLa preuve que CONNECT n'a jamais eu lieu
Port loopback ferméConnectError / ConnectionRefusedErrorConnectTimeout
Bind without listen sur DarwinConnectTimeout (attente propre au montage)Un refus, ou un RTT WAN
Corps retardé après le premier octetReadTimeout en itérant iter_bytes()Un échec de connexion
Origine /status/503 avec retries=2Statut 503, un hit d'origineUne nouvelle tentative applicative
Tableau d'assertions du laboratoire loopback HTTPX 0.28.1 du 14 octobre 2026

La figure est le tableau d'assertions de cette exécution. Ce n'est pas une image de couverture et ce n'est pas une capture d'une UI de vérificateur.

Le bloc suivant est un croquis d'API schématique. Les ports, le chemin d'AC et le mot de passe sont des placeholders que vous remplacez par des valeurs que vous contrôlez. Ce n'est pas un bundle de montage public et cela ne s'exécutera pas depuis un répertoire vide.

import httpx
import ssl

# PLACEHOLDERS. Do not treat these literals as lab values.
ctx = ssl.create_default_context(cafile="PATH/TO/LAB_CA.pem")
proxy = httpx.Proxy(
    "http://127.0.0.1:PROXY_PORT",
    auth=("PROXY_USER", "PROXY_PASSWORD"),
)
timeout = httpx.Timeout(5.0, connect=2.0, read=0.45)

with httpx.Client(proxy=proxy, verify=ctx, trust_env=False, timeout=timeout) as client:
    http_response = client.get("http://TARGET_HOST:HTTP_PORT/")
    https_response = client.get("https://TARGET_HOST:HTTPS_PORT/")

Les classes d'exception 0.28.1 et les issues CONNECT contre 407 de cet article viennent d'un montage éditorial privé. Ce montage n'est pas publié ici. Installez un client que vous pouvez importer avec python -m pip install "httpx[socks]==0.28.1", qui dépend de httpcore 1.* et, pour SOCKS, de socksio. Mettez l'hôte, le port et les identifiants de votre proxy HTTP dans les placeholders, en utilisant httpx.Proxy(..., auth=(user, password)) quand le mot de passe a des caractères réservés. Pointez l'URL HTTP vers une origine que vous contrôlez pour le relais, et l'URL HTTPS vers une origine que vous contrôlez pour CONNECT puis TLS. Pour le HTTPS, passez verify= comme ssl.create_default_context(cafile=...) avec un fichier d'AC auquel vous faites déjà confiance pour cette origine. Gardez trust_env=False le temps de prouver le chemin proxy= explicite. Ne réglez pas verify=False.

07Choisir un point d'accès documenté

Une fois le chemin client honnête, choisissez un hôte d'entrée qui documente le relais HTTP et CONNECT. Les pages du catalogue anglais pour Oxylabs et Decodo (encore listé comme smartproxy-com) ont renvoyé des URL self-canonical en direct le 14 octobre 2026. Leur documentation officielle décrit des passerelles HTTP avec identifiant/mot de passe ou IP en liste d'autorisation. C'est l'adéquation du point d'accès sur le papier. Cet article ne s'est connecté à aucun des deux produits et ne les classe pas.

Oxylabs — proxies résidentiels et datacenter
Oxylabs — proxies résidentiels et datacenter4.0771

Oxylabs vend des proxies résidentiels, mobiles, ISP et datacenter sous un seul abonnement mensuel dès $30. Paiements acheteur : cartes, virement, AliPay, PayPal.

10 pays
0,45 $US
par 1 GB
Decodo (Smartproxy) — proxies résidentiels et datacenter
Decodo (Smartproxy) — proxies résidentiels et datacenter4.22118

Decodo (anciennement Smartproxy) vend des proxies résidentiels et datacenter. Résidentiel : $4/GB sans forfait mensuel, ou packs mensuels dès $3.75/GB pour 3 GB. Paiements : cartes, PayPal, Alipay, Google/Apple Pay, crypto (avec limites).

83 pays
0,38 $US
par 1 GB

Si vous n'aviez besoin que d'une confirmation navigateur, restez sur Mon IP. Si vous aviez besoin d'HTTPX, croyez le journal du proxy et la classe d'exception de cette bibliothèque. Les deux sont en désaccord plus souvent que les billets de blog ne l'admettent.

Comment définir un proxy HTTPX ?

Passez proxy= à httpx.Client ou httpx.get. Utilisez une chaîne d'URL ou httpx.Proxy. HTTPX 0.28.1 n'a pas d'argument proxies=. HTTPX a retiré ce mapping déprécié dans 0.28.0. Pour un routage par schéma, montez des objets HTTPTransport qui reçoivent chacun leurs propres arguments proxy et verify. mounts= fusionne encore les motifs d'environnement quand trust_env=True. Utilisez transport= ou trust_env=False quand le HTTP_PROXY hérité ne doit pas s'appliquer.

Pourquoi utiliser http:// pour un proxy qui gère le HTTPS ?

Le saut proxy est en général du HTTP en clair. Les cibles HTTPS utilisent CONNECT à travers ce saut, puis le TLS vers l'origine. La clé https:// dans mounts est le schéma de la cible. L'URL de proxy devant reste http://, sauf si le produit parle réellement TLS vers le proxy lui-même.

En quoi les identifiants du proxy diffèrent-ils de auth= ?

Le userinfo du proxy ou httpx.Proxy(auth=(user, password)) envoient Proxy-Authorization au proxy. auth= est l'authentification d'origine. Le laboratoire a renvoyé un 407 proxy ou ProxyError quand les identifiants du proxy étaient faux, et un 401 d'origine quand seuls les identifiants d'origine manquaient.

trust_env=False désactive-t-il la vérification des certificats ?

Non. trust_env=False empêche HTTPX de lire HTTP_PROXY, NO_PROXY, SSL_CERT_FILE et SSL_CERT_DIR. La vérification TLS reste allumée. HTTPX ne charge pas les identifiants d'origine depuis netrc du seul fait que trust_env soit vrai. Si vous passez httpx.NetRCAuth vous-même, cette authentification s'applique encore avec trust_env=False. Passez ssl.create_default_context(cafile=...) comme verify= quand vous avez besoin d'une AC privée. verify=False n'est pas l'interrupteur de la gestion d'environnement.

Pourquoi le client lève-t-il ReadTimeout après l'arrivée des en-têtes ?

Le délai de lecture est une échéance de corps par chunk. L'origine du laboratoire a envoyé les en-têtes et un octet de corps, puis s'est arrêtée. Le streaming a renvoyé le statut 200, livré cet octet, puis levé ReadTimeout. Ce n'est pas ConnectTimeout et ce n'est pas un échec à parler au proxy.

HTTPTransport réessaie-t-il 429 ou 503 ?

Non. retries sur HTTPTransport ne réessaie que ConnectError et ConnectTimeout. Avec retries=2, l'origine du laboratoire /status/503 a été touchée une fois et HTTPX a renvoyé 503. Utilisez une autre bibliothèque si vous avez besoin de nouvelles tentatives sur le statut.