Add exception wrapper for HTTPS proxy connection failure
MODIFY
src/urllib3/connection.py
src/urllib3/connection.py
@@ -23,7 +23,12 @@ except (ImportError, AttributeError): # Platform-specific: No SSL.
from ._version import __version__
-from .exceptions import ConnectTimeoutError, NewConnectionError, SystemTimeWarning
+from .exceptions import (
+ ConnectTimeoutError,
+ HTTPSProxyError,
+ NewConnectionError,
+ SystemTimeWarning,
+)
from .packages.ssl_match_hostname import CertificateError, match_hostname
from .util import SKIP_HEADER, SKIPPABLE_HEADERS, connection
from .util.ssl_ import (
@@ -426,8 +431,11 @@ class HTTPSConnection(HTTPConnection):
"""
Establish a TLS connection to the proxy using the provided SSL context.
"""
+
proxy_config = self.proxy_config
ssl_context = proxy_config.ssl_context
+
+ try:
if ssl_context:
# If the user provided a proxy context, we assume CA and client
# certificates have already been set
@@ -455,6 +463,12 @@ class HTTPSConnection(HTTPConnection):
server_hostname=hostname,
ssl_context=ssl_context,
)
+ except Exception as e:
+ # Wrap into an HTTPSProxyError for easier diagnosis.
+ # Original exception is available on original_error
+ raise HTTPSProxyError(
+ f"Unable to establish a TLS connection to {hostname}", e
+ )
def _match_hostname(cert, asserted_hostname):

MODIFY
src/urllib3/connectionpool.py
src/urllib3/connectionpool.py
@@ -21,6 +21,7 @@ from .exceptions import (
EmptyPoolError,
HeaderParsingError,
HostChangedError,
+ HTTPSProxyError,
InsecureRequestWarning,
LocationValueError,
MaxRetryError,
@@ -695,6 +696,7 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods):
BaseSSLError,
SSLError,
CertificateError,
+ HTTPSProxyError,
) as e:
# Discard the connection for these exceptions. It will be
# replaced during the next _get_conn() call.

MODIFY
src/urllib3/exceptions.py
src/urllib3/exceptions.py
@@ -53,6 +53,13 @@ class ProxyError(HTTPError):
self.original_error = error
+class HTTPSProxyError(ProxyError):
+ """Used only when establishing a TLS connection to a proxy"""
+
+ def __init__(self, message, error, *args):
+ super().__init__(message, error, *args)
+
+
class DecodeError(HTTPError):
"""Raised when automatic decoding based on Content-Type fails."""

MODIFY
test/with_dummyserver/test_proxy_poolmanager.py
test/with_dummyserver/test_proxy_poolmanager.py
@@ -14,6 +14,7 @@ from urllib3._collections import HTTPHeaderDict
from urllib3.connectionpool import VerifiedHTTPSConnection, connection_from_url
from urllib3.exceptions import (
ConnectTimeoutError,
+ HTTPSProxyError,
MaxRetryError,
ProxyError,
ProxySchemeUnknown,
@@ -147,6 +148,19 @@ class TestHTTPProxyManager(HTTPDummyProxyTestCase):
http.request("GET", f"{self.http_url}/")
assert type(e.value.reason) == ProxyError
+ def test_https_conn_failed(self):
+ """
+ Simulates a misconfiguration that is common for users. The test attempts
+ to establish a TLS connection to a non-TLS proxy
+ """
+ bad_proxy_url = f"https://{self.proxy_host}:{int(self.proxy_port)}"
+ with proxy_from_url(
+ bad_proxy_url, ca_certs=DEFAULT_CA, timeout=LONG_TIMEOUT
+ ) as http:
+ with pytest.raises(MaxRetryError) as e:
+ http.request("GET", f"{self.https_url}/")
+ assert type(e.value.reason) == HTTPSProxyError
+
def test_oldapi(self):
with ProxyManager(
connection_from_url(self.proxy_url), ca_certs=DEFAULT_CA

Add exception wrapper for HTTPS proxy connection failure (#2133)