From 905ef41cb0455a9dc3e20a64ff71c2c289b557bf Mon Sep 17 00:00:00 2001 From: Chris Dent Date: Thu, 21 Jan 2016 19:17:56 +0000 Subject: [PATCH] Guard against http_proxy and https_proxy requests and urllib will get upset of socks proxy settings are present in the environment. In those interceptors, an exception will now be raised if $http_proxy or $https_proxy are set. Tests are added to cover the variable being set for all four of the interceptor types. This is the quick and dirty solution to the problem. The way this has been done shows some clear opportunities for refactoring down the road. Fixes #30 --- test/install.py | 12 +++++++++--- test/test_http_client.py | 12 ++++++++++++ test/test_httplib2.py | 11 +++++++++++ test/test_requests.py | 13 +++++++++++++ test/test_urllib.py | 14 ++++++++++++++ wsgi_intercept/__init__.py | 5 +++++ wsgi_intercept/requests_intercept.py | 4 ++++ wsgi_intercept/urllib_intercept.py | 6 ++++++ 8 files changed, 74 insertions(+), 3 deletions(-) diff --git a/test/install.py b/test/install.py index 225e109..65262cd 100644 --- a/test/install.py +++ b/test/install.py @@ -1,9 +1,10 @@ +import os import wsgi_intercept class BaseInstalledApp(object): def __init__(self, app, host, port=80, script_name='', - install=None, uninstall=None): + install=None, uninstall=None, proxy=None): self.app = app self.host = host self.port = port @@ -12,6 +13,7 @@ class BaseInstalledApp(object): self._uninstall = uninstall or (lambda: None) self._hits = 0 self._internals = {} + self._proxy = proxy def __call__(self, environ, start_response): self._hits += 1 @@ -32,10 +34,14 @@ class BaseInstalledApp(object): wsgi_intercept.remove_wsgi_intercept(self.host, self.port) def install(self): + if self._proxy: + os.environ['http_proxy'] = self._proxy self._install() self.install_wsgi_intercept() def uninstall(self): + if self._proxy: + del os.environ['http_proxy'] self.uninstall_wsgi_intercept() self._uninstall() @@ -56,9 +62,9 @@ def installer_class(module=None, install=None, uninstall=None): uninstall = uninstall or getattr(module, 'uninstall', None) class InstalledApp(BaseInstalledApp): - def __init__(self, app, host, port=80, script_name=''): + def __init__(self, app, host, port=80, script_name='', proxy=None): BaseInstalledApp.__init__( self, app=app, host=host, port=port, script_name=script_name, - install=install, uninstall=uninstall) + install=install, uninstall=uninstall, proxy=proxy) return InstalledApp diff --git a/test/test_http_client.py b/test/test_http_client.py index 3ca09b4..0622f6e 100644 --- a/test/test_http_client.py +++ b/test/test_http_client.py @@ -42,6 +42,18 @@ def test_other(): assert app.success() +def test_proxy_handling(): + """Proxy variable no impact.""" + with InstalledApp(wsgi_app.simple_app, host=HOST, port=80, + proxy='some.host:1234') as app: + http_client = http_lib.HTTPConnection(HOST) + http_client.request('GET', '/') + content = http_client.getresponse().read() + http_client.close() + assert content == b'WSGI intercept successful!\n' + assert app.success() + + def test_app_error(): with InstalledApp(wsgi_app.raises_app, host=HOST, port=80): http_client = http_lib.HTTPConnection(HOST) diff --git a/test/test_httplib2.py b/test/test_httplib2.py index 9fa91d1..9a67e28 100644 --- a/test/test_httplib2.py +++ b/test/test_httplib2.py @@ -47,6 +47,17 @@ def test_bogus_domain(): 'httplib2_intercept.HTTP_WSGIInterceptorWithTimeout("_nonexistant_domain_").connect()') +def test_proxy_handling(): + """Proxy has no impact.""" + with InstalledApp(wsgi_app.simple_app, host=HOST, port=80, + proxy='some_proxy.com:1234') as app: + http = httplib2.Http() + resp, content = http.request( + 'http://some_hopefully_nonexistant_domain:80/') + assert content == b'WSGI intercept successful!\n' + assert app.success() + + def test_https(): with InstalledApp(wsgi_app.simple_app, host=HOST, port=443) as app: http = httplib2.Http() diff --git a/test/test_requests.py b/test/test_requests.py index 304d178..8005f93 100644 --- a/test/test_requests.py +++ b/test/test_requests.py @@ -1,3 +1,4 @@ +import os import py.test from wsgi_intercept import requests_intercept, WSGIAppError from test import wsgi_app @@ -40,6 +41,18 @@ def test_bogus_domain(): 'requests.get("http://_nonexistant_domain_")') +def test_proxy_handling(): + with py.test.raises(RuntimeError) as exc: + with InstalledApp(wsgi_app.simple_app, host=HOST, port=80, + proxy='some_proxy.com:1234'): + requests.get('http://some_hopefully_nonexistant_domain:80/') + assert 'http_proxy or https_proxy set in environment' in str(exc.value) + # We need to do this by hand because the exception was raised + # during the entry of the context manager, so the exit handler + # wasn't reached. + del os.environ['http_proxy'] + + def test_https(): with InstalledApp(wsgi_app.simple_app, host=HOST, port=443) as app: resp = requests.get('https://some_hopefully_nonexistant_domain:443/') diff --git a/test/test_urllib.py b/test/test_urllib.py index 82daff7..83d793f 100644 --- a/test/test_urllib.py +++ b/test/test_urllib.py @@ -1,3 +1,4 @@ +import os import py.test from wsgi_intercept import urllib_intercept, WSGIAppError from test import wsgi_app @@ -32,6 +33,19 @@ def test_http_other_port(): assert environ['wsgi.url_scheme'] == 'http' +def test_proxy_handling(): + """Like requests, urllib gets confused about proxy early on.""" + with py.test.raises(RuntimeError) as exc: + with InstalledApp(wsgi_app.simple_app, host=HOST, port=80, + proxy='some.host:1234'): + url_lib.urlopen('http://some_hopefully_nonexistant_domain:80/') + assert 'http_proxy or https_proxy set in environment' in str(exc.value) + # We need to do this by hand because the exception was raised + # during the entry of the context manager, so the exit handler + # wasn't reached. + del os.environ['http_proxy'] + + def test_https(): with InstalledApp(wsgi_app.simple_app, host=HOST, port=443) as app: url_lib.urlopen('https://some_hopefully_nonexistant_domain:443/') diff --git a/wsgi_intercept/__init__.py b/wsgi_intercept/__init__.py index 487cd76..b659d75 100644 --- a/wsgi_intercept/__init__.py +++ b/wsgi_intercept/__init__.py @@ -50,6 +50,11 @@ Note especially that ``app_create_fn`` is a *function object* returning a WSGI application; ``script_name`` becomes ``SCRIPT_NAME`` in the WSGI app's environment, if set. +Note also that if ``http_proxy`` or ``https_proxy`` is set in the environment +this can cause difficulties with some of the intercepted libraries. If +requests or urllib is being used, these will raise an exception if one of +those variables is set. + Install ======= diff --git a/wsgi_intercept/requests_intercept.py b/wsgi_intercept/requests_intercept.py index cdd304a..586b752 100644 --- a/wsgi_intercept/requests_intercept.py +++ b/wsgi_intercept/requests_intercept.py @@ -1,6 +1,7 @@ """Intercept HTTP connections that use `requests `_. """ +import os import sys from . import WSGI_HTTPConnection, WSGI_HTTPSConnection, wsgi_fake_socket @@ -32,6 +33,9 @@ class HTTPS_WSGIInterceptor(WSGI_HTTPSConnection, HTTPSConnection): def install(): + if 'http_proxy' in os.environ or 'https_proxy' in os.environ: + raise RuntimeError( + 'http_proxy or https_proxy set in environment, please unset') HTTPConnectionPool.ConnectionCls = HTTP_WSGIInterceptor HTTPSConnectionPool.ConnectionCls = HTTPS_WSGIInterceptor diff --git a/wsgi_intercept/urllib_intercept.py b/wsgi_intercept/urllib_intercept.py index 3eca406..31d8f46 100644 --- a/wsgi_intercept/urllib_intercept.py +++ b/wsgi_intercept/urllib_intercept.py @@ -1,5 +1,8 @@ """Intercept HTTP connections that use urllib.request (Py3) aka urllib2 (Python 2). """ + +import os + try: import urllib.request as url_lib except ImportError: @@ -27,6 +30,9 @@ class WSGI_HTTPSHandler(url_lib.HTTPSHandler): def install_opener(): + if 'http_proxy' in os.environ or 'https_proxy' in os.environ: + raise RuntimeError( + 'http_proxy or https_proxy set in environment, please unset') handlers = [WSGI_HTTPHandler()] if WSGI_HTTPSHandler is not None: handlers.append(WSGI_HTTPSHandler())