diff --git a/requests/sessions.py b/requests/sessions.py index fdf7e9fe..bb93e9cf 100644 --- a/requests/sessions.py +++ b/requests/sessions.py @@ -698,6 +698,91 @@ class Session(SessionRedirectMixin): return r + def set_http_proxy(self, + http_scheme='http', # type: str + http_host=None, # type: str + http_port=80, # type: int + http_url=None, # type: str + use_http_proxy_for_https_requests=False, # type: bool + https_scheme='https', # type: str + https_host=None, # type: str + https_port=443, # type: int + https_url=None, # type: str + replace=False # type: bool + ): + """Update or replace self.proxies with the provided proxy information. + + This method updates or replaces (depending on the value of `replace`) the dictionary in `self.proxies` with the + provided information. For each kind of connection (http and https), there are two ways to pass the information: + either as an url string (`http_url`, `https_url`), or split in schema/host/port, with sensible defaults. + In addition if the exact same proxy information is to be used for http and https, you can pass only the http + one and set `use_http_proxy_for_https_requests` to True. + + See :ref:`Proxies documentation <_proxies>` for details. + + :param http_host: (optional) a string indicating the http proxy host, for example '10.10.1.10' or 'acme.com'. + :param http_port: (optional) an int indicating the http proxy port, for example `3128`. + :param http_scheme: (optional) a string indicating the scheme to use for http proxy. By default this is 'http' + but you can consider using 'socks5', 'socks5h'. See documentation for details. + :param http_url: (optional) a string indicating the full http proxy url. For example 'http://10.10.1.10:3128' + or 'http://user:pass@10.10.1.10:3128/' or 'socks5://user:pass@host:port'. + Only one of {http_scheme + http_host + http_port} or {http_url} should be provided. + :param use_http_proxy_for_https_requests: (optional) a boolean indicating whether the information provided for + the http proxy should be copied for the https proxy. Note that the full url will be copied including the + scheme (so by default 'http'). + :param https_host: (optional) a string indicating the https proxy host, for example '10.10.1.10' or 'acme.com'. + :param https_port: (optional) an int indicating the https proxy port, for example `3128`. + :param https_scheme: (optional) a string indicating the scheme to use for https proxy. By default this is + 'https' but you can consider using 'socks5', 'socks5h'. See documentation for details. + :param https_url: (optional) a string indicating the full https proxy url. For example 'https://10.10.1.10:3128' + or 'http://user:pass@10.10.1.10:3128/' or 'socks5://user:pass@host:port'. + Only one of {https_scheme + https_host + https_port} or {https_url} should be provided. + :param replace: (optional) a boolean indicating if the provided information should replace the existing one + (True) or just update it (False, default). + :return: + """ + proxies = dict() + + # HTTPS + if http_host is not None: + # (a) scheme + host + port + if http_url is not None: + raise ValueError("Only one of `http_host` and `http_url` should be provided") + proxies['http'] = "%s://%s:%s" % (http_scheme, http_host, http_port) + elif http_url is not None: + # (b) full url + proxies['http'] = http_url + elif http_port != 80 or http_scheme != 'http': + raise ValueError("An `http_host` should be provided if you wish to change `http_port` or `http_scheme`") + + # HTTPS + if use_http_proxy_for_https_requests: + # (a) copy the information from http + if https_host is not None or https_url is not None or https_port != 443 or https_scheme != "https": + raise ValueError("`use_http_proxy_for_https_requests` was set to `True` but custom information for " + "https was provided.") + try: + proxies['https'] = proxies['http'] + except KeyError: + raise ValueError("`use_http_proxy_for_https_requests` was set to `True` but no information was " + "provided for the http proxy") + elif https_host is not None: + # (b) scheme + host + port + if https_url is not None: + raise ValueError("Only one of `https_host` and `https_url` should be provided") + proxies['https'] = '%s://%s:%s' % (https_scheme, https_host, https_port) + elif https_url is not None: + # (c) full url + proxies['https'] = https_url + elif https_port != 443 or https_scheme != 'https': + raise ValueError("An `https_host` should be provided if you wish to change `https_port` or `https_scheme`") + + # Replace or update (default) the configuration + if replace: + self.proxies = proxies + else: + self.proxies.update(proxies) + def merge_environment_settings(self, url, proxies, stream, verify, cert): """ Check the environment and merge it with some settings. diff --git a/tests/test_requests.py b/tests/test_requests.py index 5b6a7f58..b0abe1be 100644 --- a/tests/test_requests.py +++ b/tests/test_requests.py @@ -151,6 +151,41 @@ class TestRequests: request = requests.Request('GET', ' http://example.com').prepare() assert request.url == 'http://example.com/' + def test_proxies_shortcut(self): + """Test that the set_http_proxy helper function works correctly""" + s = requests.Session() + + # nominal + s.set_http_proxy(http_scheme='socks5', http_host='acme.com', http_port=999) + assert s.proxies == {'http': 'socks5://acme.com:999'} + s.set_http_proxy(https_scheme='socks5h', https_host='acme.org', https_port=80) + assert s.proxies == {'http': 'socks5://acme.com:999', 'https': 'socks5h://acme.org:80'} + + # one can not specify a scheme or a port without passing a host + with pytest.raises(ValueError): + s.set_http_proxy(http_scheme='socks5') + with pytest.raises(ValueError): + s.set_http_proxy(https_scheme='socks5') + with pytest.raises(ValueError): + s.set_http_proxy(http_port=999) + with pytest.raises(ValueError): + s.set_http_proxy(https_port=999) + + # use_http_proxy_for_https_requests requires http related information to be present + with pytest.raises(ValueError): + s.set_http_proxy(use_http_proxy_for_https_requests=True) + + # reuse http info for https + s.set_http_proxy(http_url='http://10.10.10.10:80', use_http_proxy_for_https_requests=True) + assert s.proxies == {'http': 'http://10.10.10.10:80', 'https': 'http://10.10.10.10:80'} + + # replace instead of update + s.set_http_proxy(https_url='http://10.10.10.20:80', replace=True) + assert s.proxies == {'https': 'http://10.10.10.20:80'} + + # https url + s.set_http_proxy(https_url='http://10.10.10.20:80', replace=True) + @pytest.mark.parametrize('scheme', ('http://', 'HTTP://', 'hTTp://', 'HttP://')) def test_mixed_case_scheme_acceptable(self, httpbin, scheme): s = requests.Session()