diff --git a/docs/user/advanced.rst b/docs/user/advanced.rst index f53cbed7..dcd9e228 100644 --- a/docs/user/advanced.rst +++ b/docs/user/advanced.rst @@ -486,8 +486,18 @@ To use HTTP Basic Auth with your proxy, use the `http://user:password@host/` syn "http": "http://user:pass@10.10.1.10:3128/", } -Note that proxy URLs must include the scheme. +To give a proxy for a specific scheme and host, use the +`scheme://hostname` form for the key. This will match for +any request to the given scheme and exact hostname. +:: + + proxies = { + "http://10.20.1.128": "http://10.10.1.10:5323", + } + +Note that proxy URLs must include the scheme. + .. _compliance: Compliance diff --git a/requests/adapters.py b/requests/adapters.py index 60afb583..f911fc57 100644 --- a/requests/adapters.py +++ b/requests/adapters.py @@ -17,7 +17,8 @@ from .packages.urllib3.util import Timeout as TimeoutSauce from .packages.urllib3.util.retry import Retry from .compat import urlparse, basestring from .utils import (DEFAULT_CA_BUNDLE_PATH, get_encoding_from_headers, - prepend_scheme_if_needed, get_auth_from_url, urldefragauth) + prepend_scheme_if_needed, get_auth_from_url, urldefragauth, + select_proxy) from .structures import CaseInsensitiveDict from .packages.urllib3.exceptions import ConnectTimeoutError from .packages.urllib3.exceptions import HTTPError as _HTTPError @@ -238,8 +239,7 @@ class HTTPAdapter(BaseAdapter): :param url: The URL to connect to. :param proxies: (optional) A Requests-style dictionary of proxies used on this request. """ - proxies = proxies or {} - proxy = proxies.get(urlparse(url.lower()).scheme) + proxy = select_proxy(url, proxies) if proxy: proxy = prepend_scheme_if_needed(proxy, 'http') @@ -272,12 +272,10 @@ class HTTPAdapter(BaseAdapter): :class:`HTTPAdapter `. :param request: The :class:`PreparedRequest ` being sent. - :param proxies: A dictionary of schemes to proxy URLs. + :param proxies: A dictionary of schemes or schemes and hosts to proxy URLs. """ - proxies = proxies or {} + proxy = select_proxy(request.url, proxies) scheme = urlparse(request.url).scheme - proxy = proxies.get(scheme) - if proxy and scheme != 'https': url = urldefragauth(request.url) else: diff --git a/requests/sessions.py b/requests/sessions.py index 651fc188..9c0dd73d 100644 --- a/requests/sessions.py +++ b/requests/sessions.py @@ -299,9 +299,9 @@ class Session(SessionRedirectMixin): #: :class:`Request `. self.auth = None - #: Dictionary mapping protocol to the URL of the proxy (e.g. - #: {'http': 'foo.bar:3128'}) to be used on each - #: :class:`Request `. + #: Dictionary mapping protocol or protocol and host to the URL of the proxy + #: (e.g. {'http': 'foo.bar:3128', 'http://host.name': 'foo.bar:4012'}) to + #: be used on each :class:`Request `. self.proxies = {} #: Event-handling hooks. @@ -428,8 +428,8 @@ class Session(SessionRedirectMixin): :type timeout: float or tuple :param allow_redirects: (optional) Set to True by default. :type allow_redirects: bool - :param proxies: (optional) Dictionary mapping protocol to the URL of - the proxy. + :param proxies: (optional) Dictionary mapping protocol or protocol and + hostname to the URL of the proxy. :param stream: (optional) whether to immediately download the response content. Defaults to ``False``. :param verify: (optional) if ``True``, the SSL cert will be verified. diff --git a/requests/utils.py b/requests/utils.py index 3fd0e41f..3d4c7945 100644 --- a/requests/utils.py +++ b/requests/utils.py @@ -537,6 +537,18 @@ def get_environ_proxies(url): else: return getproxies() +def select_proxy(url, proxies): + """Select a proxy for the url, if applicable. + + :param url: The url being for the request + :param proxies: A dictionary of schemes or schemes and hosts to proxy URLs + """ + proxies = proxies or {} + urlparts = urlparse(url) + proxy = proxies.get(urlparts.scheme+'://'+urlparts.hostname) + if proxy is None: + proxy = proxies.get(urlparts.scheme) + return proxy def default_user_agent(name="python-requests"): """Return a string representing the default user agent.""" diff --git a/test_requests.py b/test_requests.py index 33fafdf4..28ea5730 100755 --- a/test_requests.py +++ b/test_requests.py @@ -1327,6 +1327,15 @@ class UtilsTestCase(unittest.TestCase): 'http://localhost.localdomain:5000/v1.0/') == {} assert get_environ_proxies('http://www.requests.com/') != {} + def test_select_proxies(self): + """Make sure we can select per-host proxies correctly.""" + from requests.utils import select_proxy + proxies = {'http': 'http://http.proxy', + 'http://some.host': 'http://some.host.proxy'} + assert select_proxy('hTTp://u:p@Some.Host/path', proxies) == 'http://some.host.proxy' + assert select_proxy('hTTp://u:p@Other.Host/path', proxies) == 'http://http.proxy' + assert select_proxy('hTTps://Other.Host', proxies) is None + def test_guess_filename_when_int(self): from requests.utils import guess_filename assert None is guess_filename(1)