Merge lp:~mvo/piston-mini-client/add-proxy-support into lp:piston-mini-client

Proposed by Michael Vogt
Status: Merged
Approved by: Anthony Lenton
Approved revision: 44
Merged at revision: 42
Proposed branch: lp:~mvo/piston-mini-client/add-proxy-support
Merge into: lp:piston-mini-client
Diff against target: 177 lines (+113/-12)
3 files modified
piston_mini_client/__init__.py (+51/-12)
piston_mini_client/tests/test_proxy.py (+59/-0)
piston_mini_client/tests/test_resource.py (+3/-0)
To merge this branch: bzr merge lp:~mvo/piston-mini-client/add-proxy-support
Reviewer Review Type Date Requested Status
Anthony Lenton Approve
Review via email: mp+77183@code.launchpad.net

Description of the change

This adds support for reading the http/https proxy from the environment. Unfortunately it also needs python-socksipy as a additional dependency. Fortunately its tiny.

To post a comment you must log in.
44. By Michael Vogt

support multi scheme apis

Revision history for this message
Anthony Lenton (elachuni) wrote :

Hi mvo!

Thanks for the patch!
Improvements for a later branch, notes to myself more than anything else:
 - Update the docs to mention these improvements
 - The tests fail if you don't have socksipy available already. SocksiPy isn't installable from PyPI it seems, we'll need to figure out a way to make that work or disable the proxy tests if it isn't available

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'piston_mini_client/__init__.py'
2--- piston_mini_client/__init__.py 2011-08-10 16:56:49 +0000
3+++ piston_mini_client/__init__.py 2011-09-27 16:30:36 +0000
4@@ -210,7 +210,8 @@
5 if not service_root:
6 raise ValueError("No service_root provided, and no default found")
7 parsed_service_root = urlparse(service_root)
8- if parsed_service_root.scheme not in ['http', 'https']:
9+ scheme = parsed_service_root.scheme
10+ if scheme not in ['http', 'https']:
11 raise ValueError("service_root's scheme must be http or https")
12 self._service_root = service_root
13 self._parsed_service_root = list(parsed_service_root)
14@@ -220,19 +221,53 @@
15 self._httplib2_cache = None
16 self._auth = auth
17 self._offline_mode = offline_mode
18- self._http = None
19- if (disable_ssl_validation or
20+ self._disable_ssl_validation = disable_ssl_validation
21+ # create one httplib2.Http object per scheme so that we can
22+ # have per-scheme proxy settings (see also Issue 26
23+ # http://code.google.com/p/httplib2/issues/detail?id=26)
24+ self._http = {}
25+ for scheme in ("http", "https"):
26+ self._http[scheme] = self._get_http_obj_for_scheme(scheme)
27+ if self.serializers is None:
28+ self.serializers = {}
29+
30+ def _get_http_obj_for_scheme(self, scheme):
31+ proxy_info = self._get_proxy_info(scheme)
32+ http = None
33+ if (self._disable_ssl_validation or
34 os.environ.get(DISABLE_SSL_VALIDATION_ENVVAR)):
35 try:
36- self._http = httplib2.Http(cache=self._httplib2_cache,
37- disable_ssl_certificate_validation=True)
38+ http = httplib2.Http(
39+ cache=self._httplib2_cache,
40+ disable_ssl_certificate_validation=True,
41+ proxy_info=proxy_info)
42 except TypeError:
43 # httplib2 < 0.7.0 doesn't support cert validation anyway
44 pass
45- if self._http is None:
46- self._http = httplib2.Http(cache=self._httplib2_cache)
47- if self.serializers is None:
48- self.serializers = {}
49+ if http is None:
50+ http = httplib2.Http(cache=self._httplib2_cache,
51+ proxy_info=proxy_info)
52+ return http
53+
54+ def _get_proxy_info(self, scheme):
55+ envvar = "%s_proxy" % scheme
56+ if envvar in os.environ:
57+ import socks
58+ url = urlparse(os.environ[envvar])
59+ user_pass, sep, host_and_port = url.netloc.rpartition("@")
60+ user, sep, passw = user_pass.partition(":")
61+ host, sep, port = host_and_port.partition(":")
62+ if port:
63+ port = int(port)
64+ proxy_info = httplib2.ProxyInfo(
65+ proxy_type=socks.PROXY_TYPE_HTTP,
66+ proxy_host=host,
67+ proxy_port=port or 8080,
68+ proxy_user=user or None,
69+ proxy_pass=passw or None)
70+ return proxy_info
71+ return None
72+
73
74 def _get(self, path, args=None, scheme=None, extra_headers=None):
75 """Perform an HTTP GET request.
76@@ -404,10 +439,13 @@
77 raise OfflineModeException(err)
78 return self._get_from_cache(url)
79
80+ if scheme is None:
81+ scheme = urlparse(url).scheme
82+
83 if self._auth:
84 self._auth.sign_request(url, method, body, headers)
85 try:
86- response, body = self._http.request(url, method=method, body=body,
87+ response, body = self._http[scheme].request(url, method=method, body=body,
88 headers=headers)
89 except AttributeError, e:
90 # Special case out httplib2's way of telling us unable to connect
91@@ -423,8 +461,9 @@
92 """ get a given url from the cachedir even if its expired
93 or return None if no data is available
94 """
95- if self._http.cache:
96- cached_value = self._http.cache.get(httplib2.urlnorm(url)[-1])
97+ scheme = urlparse(url).scheme
98+ if self._http[scheme].cache:
99+ cached_value = self._http[scheme].cache.get(httplib2.urlnorm(url)[-1])
100 if cached_value:
101 info, content = cached_value.split('\r\n\r\n', 1)
102 return content
103
104=== added file 'piston_mini_client/tests/test_proxy.py'
105--- piston_mini_client/tests/test_proxy.py 1970-01-01 00:00:00 +0000
106+++ piston_mini_client/tests/test_proxy.py 2011-09-27 16:30:36 +0000
107@@ -0,0 +1,59 @@
108+# -*- coding: utf-8 -*-
109+# Copyright 2010-2011 Canonical Ltd. This software is licensed under the
110+# GNU Lesser General Public License version 3 (see the file LICENSE).
111+
112+import os
113+
114+from mock import patch
115+from unittest import TestCase
116+
117+from piston_mini_client import PistonAPI
118+
119+class DentistAPI(PistonAPI):
120+ default_service_root = 'http://localhost:12345'
121+ def appointments(self):
122+ self._get('/appointments')
123+
124+class ProxySupportTestCase(TestCase):
125+
126+ def setUp(self):
127+ for envvar in ("http_proxy", "https_proxy"):
128+ if envvar in os.environ:
129+ del os.environ[envvar]
130+
131+ def test_basic_proxy(self):
132+ os.environ["http_proxy"] = "http://localhost:3128"
133+ api = DentistAPI()
134+ proxy_info = api._http["http"].proxy_info
135+ self.assertNotEqual(proxy_info, None)
136+ self.assertEqual(proxy_info.proxy_host, "localhost")
137+ self.assertEqual(proxy_info.proxy_port, 3128)
138+
139+ def test_basic_https_proxy(self):
140+ # not the https this time
141+ os.environ["https_proxy"] = "https://localhost:3128"
142+ api = DentistAPI(service_root="https://localhost:12345")
143+ proxy_info = api._http["https"].proxy_info
144+ self.assertNotEqual(proxy_info, None)
145+ self.assertEqual(proxy_info.proxy_host, "localhost")
146+ self.assertEqual(proxy_info.proxy_port, 3128)
147+
148+ def test_auth_proxy(self):
149+ os.environ["http_proxy"] = "http://user:pass@localhost:3128"
150+ api = DentistAPI()
151+ proxy_info = api._http["http"].proxy_info
152+ self.assertNotEqual(proxy_info, None)
153+ self.assertEqual(proxy_info.proxy_host, "localhost")
154+ self.assertEqual(proxy_info.proxy_port, 3128)
155+ self.assertEqual(proxy_info.proxy_user, "user")
156+ self.assertEqual(proxy_info.proxy_pass, "pass")
157+
158+ def test_no_proxy(self):
159+ api = DentistAPI()
160+ proxy_info = api._http["http"].proxy_info
161+ self.assertEqual(proxy_info, None)
162+
163+
164+if __name__ == "__main__":
165+ import unittest
166+ unittest.main()
167
168=== modified file 'piston_mini_client/tests/test_resource.py'
169--- piston_mini_client/tests/test_resource.py 2011-08-09 15:13:32 +0000
170+++ piston_mini_client/tests/test_resource.py 2011-09-27 16:30:36 +0000
171@@ -429,3 +429,6 @@
172 obj = self.MySerializable(foo='bar', baz=42)
173 self.assertEqual({'foo': 'bar'}, obj._as_serializable())
174
175+if __name__ == "__main__":
176+ import unittest
177+ unittest.main()

Subscribers

People subscribed via source and target branches