Merge lp:~vila/bzr/186920-lp-proxy into lp:bzr
- 186920-lp-proxy
- Merge into bzr.dev
Status: | Merged |
---|---|
Merged at revision: | not available |
Proposed branch: | lp:~vila/bzr/186920-lp-proxy |
Merge into: | lp:bzr |
Diff against target: |
430 lines 6 files modified
NEWS (+3/-0) bzrlib/plugins/launchpad/__init__.py (+12/-23) bzrlib/plugins/launchpad/lp_registration.py (+25/-4) bzrlib/plugins/launchpad/test_lp_directory.py (+144/-1) bzrlib/tests/test_http.py (+55/-1) bzrlib/transport/http/_urllib2_wrappers.py (+19/-7) |
To merge this branch: | bzr merge lp:~vila/bzr/186920-lp-proxy |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
John A Meinel | Pending | ||
Review via email: mp+14238@code.launchpad.net |
This proposal supersedes a proposal from 2009-10-30.
Commit message
Description of the change
Vincent Ladeuil (vila) wrote : Posted in a previous version of this proposal | # |
John A Meinel (jameinel) wrote : Posted in a previous version of this proposal | # |
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1
Vincent Ladeuil wrote:
> Vincent Ladeuil has proposed merging lp:~vila/bzr/186920-lp-proxy into lp:bzr.
>
> Requested reviews:
> bzr-core (bzr-core)
>
>
> Thanks to Gordon Tyler for starting a proof-of-concept implementation, I've finally
> been able to fix bug #186920 et all (the one about lp URLS that can't be
> resolved behind proxies).
>
> This patch implement an XMLRPC transport class using our _urllib2_wrappers (the fundations
> for the urllib http client), that gives us proxy support for free.
>
> I still haven't implemented a true proxy test server but I've added tests that exercise
> the full urllib http stack.
>
+class Transport(
+
+ def __init__(self, scheme, use_datetime=0):
^- Shouldn't we have our own name here? We *can* re-use the existing
name, but it is potentially confusing.
Especially given that we have a "Transport" hierarchy already (and not
to mention that so does Paramiko), I'd like to at least call it
"XMLRPCTransport".
Also, it seems that Gordon responded that there was a problem with not
passing the original port to the proxy? Did you fix that in this? Or did
his comment come after the merge proposal?
I assume that the http/https trick is this line:
+ self._opener = _urllib2_
(Opener supposedly creates the right instance of HTTP or HTTPS based on
the url/the url of the proxy?) I assume that is why you don't need:
- - if uri_type == 'https':
- - transport = xmlrpclib.
- - else:
- - transport = xmlrpclib.
+ transport = Transport(uri_type)
anymore.
+
+ self.wfile.
+
+class PreCannedServer
^- bit of whitespace here
Otherwise:
review: approve
John
=:->
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.9 (Cygwin)
Comment: Using GnuPG with Mozilla - http://
iEYEARECAAYFAkr
VZ0An3B1OXrswFO
=Vmrj
-----END PGP SIGNATURE-----
Vincent Ladeuil (vila) wrote : Posted in a previous version of this proposal | # |
>>>>> "jam" == John A Meinel <email address hidden> writes:
jam> +class Transport(
jam> +
jam> + def __init__(self, scheme, use_datetime=0):
jam> ^- Shouldn't we have our own name here? We *can* re-use
jam> the existing name, but it is potentially confusing.
jam> Especially given that we have a "Transport" hierarchy
jam> already (and not to mention that so does Paramiko), I'd
jam> like to at least call it "XMLRPCTransport".
Sure, done.
jam> Also, it seems that Gordon responded that there was a
jam> problem with not passing the original port to the proxy?
jam> Did you fix that in this? Or did his comment come after
jam> the merge proposal?
His comment went after the mp, I've fixed it since so I'll
resubmit.
jam> I assume that the http/https trick is this line:
jam> + self._opener = _urllib2_
jam> (Opener supposedly creates the right instance of HTTP or HTTPS based on
jam> the url/the url of the proxy?) I assume that is why you don't need:
jam> - if uri_type == 'https':
jam> - transport = xmlrpclib.
jam> - else:
jam> - transport = xmlrpclib.
jam> + transport = Transport(uri_type)
jam> anymore.
Correct. I could have refactored even more aggressively (to pass
the full service url and set the agent) but I wanted to keep the
patch as minimal as possible.
Vincent
Preview Diff
1 | === modified file 'NEWS' | |||
2 | --- NEWS 2009-10-30 00:00:37 +0000 | |||
3 | +++ NEWS 2009-10-30 21:10:26 +0000 | |||
4 | @@ -36,6 +36,9 @@ | |||
5 | 36 | fails intermittently with "second push failed to complete". | 36 | fails intermittently with "second push failed to complete". |
6 | 37 | (Andrew Bennetts, #437626) | 37 | (Andrew Bennetts, #437626) |
7 | 38 | 38 | ||
8 | 39 | * Launchpad urls can now be resolved from behind proxies. | ||
9 | 40 | (Gordon Tyler, Vincent Ladeuil, #198920) | ||
10 | 41 | |||
11 | 39 | * PreviewTree file names are not limited by the encoding of the temp | 42 | * PreviewTree file names are not limited by the encoding of the temp |
12 | 40 | directory's filesystem. (Aaron Bentley, #436794) | 43 | directory's filesystem. (Aaron Bentley, #436794) |
13 | 41 | 44 | ||
14 | 42 | 45 | ||
15 | === modified file 'bzrlib/plugins/launchpad/__init__.py' | |||
16 | --- bzrlib/plugins/launchpad/__init__.py 2009-07-06 13:00:23 +0000 | |||
17 | +++ bzrlib/plugins/launchpad/__init__.py 2009-10-30 21:10:26 +0000 | |||
18 | @@ -262,30 +262,19 @@ | |||
19 | 262 | _register_directory() | 262 | _register_directory() |
20 | 263 | 263 | ||
21 | 264 | 264 | ||
33 | 265 | def test_suite(): | 265 | def load_tests(basic_tests, module, loader): |
34 | 266 | """Called by bzrlib to fetch tests for this plugin""" | 266 | testmod_names = [ |
35 | 267 | from unittest import TestSuite, TestLoader | 267 | 'test_account', |
36 | 268 | from bzrlib.plugins.launchpad import ( | 268 | 'test_register', |
37 | 269 | test_account, | 269 | 'test_lp_directory', |
38 | 270 | test_lp_directory, | 270 | 'test_lp_login', |
39 | 271 | test_lp_login, | 271 | 'test_lp_open', |
40 | 272 | test_lp_open, | 272 | 'test_lp_service', |
41 | 273 | test_lp_service, | 273 | ] |
42 | 274 | test_register, | 274 | basic_tests.addTest(loader.loadTestsFromModuleNames( |
43 | 275 | ) | 275 | ["%s.%s" % (__name__, tmn) for tmn in testmod_names])) |
44 | 276 | return basic_tests | ||
45 | 276 | 277 | ||
46 | 277 | loader = TestLoader() | ||
47 | 278 | suite = TestSuite() | ||
48 | 279 | for module in [ | ||
49 | 280 | test_account, | ||
50 | 281 | test_register, | ||
51 | 282 | test_lp_directory, | ||
52 | 283 | test_lp_login, | ||
53 | 284 | test_lp_open, | ||
54 | 285 | test_lp_service, | ||
55 | 286 | ]: | ||
56 | 287 | suite.addTests(loader.loadTestsFromModule(module)) | ||
57 | 288 | return suite | ||
58 | 289 | 278 | ||
59 | 290 | _launchpad_help = """Integration with Launchpad.net | 279 | _launchpad_help = """Integration with Launchpad.net |
60 | 291 | 280 | ||
61 | 292 | 281 | ||
62 | === modified file 'bzrlib/plugins/launchpad/lp_registration.py' | |||
63 | --- bzrlib/plugins/launchpad/lp_registration.py 2009-07-04 16:22:16 +0000 | |||
64 | +++ bzrlib/plugins/launchpad/lp_registration.py 2009-10-30 21:10:26 +0000 | |||
65 | @@ -31,6 +31,7 @@ | |||
66 | 31 | errors, | 31 | errors, |
67 | 32 | __version__ as _bzrlib_version, | 32 | __version__ as _bzrlib_version, |
68 | 33 | ) | 33 | ) |
69 | 34 | from bzrlib.transport.http import _urllib2_wrappers | ||
70 | 34 | 35 | ||
71 | 35 | # for testing, do | 36 | # for testing, do |
72 | 36 | ''' | 37 | ''' |
73 | @@ -53,6 +54,29 @@ | |||
74 | 53 | errors.BzrError.__init__(self, url=url) | 54 | errors.BzrError.__init__(self, url=url) |
75 | 54 | 55 | ||
76 | 55 | 56 | ||
77 | 57 | class XMLRPCTransport(xmlrpclib.Transport): | ||
78 | 58 | |||
79 | 59 | def __init__(self, scheme, use_datetime=0): | ||
80 | 60 | xmlrpclib.Transport.__init__(self, use_datetime=use_datetime) | ||
81 | 61 | self._scheme = scheme | ||
82 | 62 | self._opener = _urllib2_wrappers.Opener() | ||
83 | 63 | self.verbose = 0 | ||
84 | 64 | |||
85 | 65 | def request(self, host, handler, request_body, verbose=0): | ||
86 | 66 | self.verbose = verbose | ||
87 | 67 | url = self._scheme + "://" + host + handler | ||
88 | 68 | request = _urllib2_wrappers.Request("POST", url, request_body) | ||
89 | 69 | # FIXME: _urllib2_wrappers will override user-agent with its own | ||
90 | 70 | # request.add_header("User-Agent", self.user_agent) | ||
91 | 71 | request.add_header("Content-Type", "text/xml") | ||
92 | 72 | |||
93 | 73 | response = self._opener.open(request) | ||
94 | 74 | if response.code != 200: | ||
95 | 75 | raise xmlrpclib.ProtocolError(host + handler, response.code, | ||
96 | 76 | response.msg, response.info()) | ||
97 | 77 | return self.parse_response(response) | ||
98 | 78 | |||
99 | 79 | |||
100 | 56 | class LaunchpadService(object): | 80 | class LaunchpadService(object): |
101 | 57 | """A service to talk to Launchpad via XMLRPC. | 81 | """A service to talk to Launchpad via XMLRPC. |
102 | 58 | 82 | ||
103 | @@ -90,10 +114,7 @@ | |||
104 | 90 | self._lp_instance = lp_instance | 114 | self._lp_instance = lp_instance |
105 | 91 | if transport is None: | 115 | if transport is None: |
106 | 92 | uri_type = urllib.splittype(self.service_url)[0] | 116 | uri_type = urllib.splittype(self.service_url)[0] |
111 | 93 | if uri_type == 'https': | 117 | transport = XMLRPCTransport(uri_type) |
108 | 94 | transport = xmlrpclib.SafeTransport() | ||
109 | 95 | else: | ||
110 | 96 | transport = xmlrpclib.Transport() | ||
112 | 97 | transport.user_agent = 'bzr/%s (xmlrpclib/%s)' \ | 118 | transport.user_agent = 'bzr/%s (xmlrpclib/%s)' \ |
113 | 98 | % (_bzrlib_version, xmlrpclib.__version__) | 119 | % (_bzrlib_version, xmlrpclib.__version__) |
114 | 99 | self.transport = transport | 120 | self.transport = transport |
115 | 100 | 121 | ||
116 | === modified file 'bzrlib/plugins/launchpad/test_lp_directory.py' | |||
117 | --- bzrlib/plugins/launchpad/test_lp_directory.py 2009-03-23 14:59:43 +0000 | |||
118 | +++ bzrlib/plugins/launchpad/test_lp_directory.py 2009-10-30 21:10:26 +0000 | |||
119 | @@ -16,10 +16,12 @@ | |||
120 | 16 | 16 | ||
121 | 17 | """Tests for directory lookup through Launchpad.net""" | 17 | """Tests for directory lookup through Launchpad.net""" |
122 | 18 | 18 | ||
123 | 19 | import os | ||
124 | 19 | import xmlrpclib | 20 | import xmlrpclib |
125 | 20 | 21 | ||
126 | 21 | from bzrlib import ( | 22 | from bzrlib import ( |
127 | 22 | errors, | 23 | errors, |
128 | 24 | tests, | ||
129 | 23 | ) | 25 | ) |
130 | 24 | from bzrlib.branch import Branch | 26 | from bzrlib.branch import Branch |
131 | 25 | from bzrlib.directory_service import directories | 27 | from bzrlib.directory_service import directories |
132 | @@ -28,10 +30,38 @@ | |||
133 | 28 | TestCaseWithMemoryTransport | 30 | TestCaseWithMemoryTransport |
134 | 29 | ) | 31 | ) |
135 | 30 | from bzrlib.transport import get_transport | 32 | from bzrlib.transport import get_transport |
137 | 31 | from bzrlib.plugins.launchpad import _register_directory | 33 | from bzrlib.plugins.launchpad import ( |
138 | 34 | _register_directory, | ||
139 | 35 | lp_registration, | ||
140 | 36 | ) | ||
141 | 32 | from bzrlib.plugins.launchpad.lp_directory import ( | 37 | from bzrlib.plugins.launchpad.lp_directory import ( |
142 | 33 | LaunchpadDirectory) | 38 | LaunchpadDirectory) |
143 | 34 | from bzrlib.plugins.launchpad.account import get_lp_login | 39 | from bzrlib.plugins.launchpad.account import get_lp_login |
144 | 40 | from bzrlib.tests import ( | ||
145 | 41 | http_server, | ||
146 | 42 | http_utils, | ||
147 | 43 | ) | ||
148 | 44 | |||
149 | 45 | |||
150 | 46 | def load_tests(standard_tests, module, loader): | ||
151 | 47 | result = loader.suiteClass() | ||
152 | 48 | t_tests, remaining_tests = tests.split_suite_by_condition( | ||
153 | 49 | standard_tests, tests.condition_isinstance(( | ||
154 | 50 | TestXMLRPCTransport, | ||
155 | 51 | ))) | ||
156 | 52 | transport_scenarios = [ | ||
157 | 53 | ('http', dict(server_class=PreCannedHTTPServer,)), | ||
158 | 54 | ] | ||
159 | 55 | if tests.HTTPSServerFeature.available(): | ||
160 | 56 | transport_scenarios.append( | ||
161 | 57 | ('https', dict(server_class=PreCannedHTTPSServer,)), | ||
162 | 58 | ) | ||
163 | 59 | tests.multiply_tests(t_tests, transport_scenarios, result) | ||
164 | 60 | |||
165 | 61 | # No parametrization for the remaining tests | ||
166 | 62 | result.addTests(remaining_tests) | ||
167 | 63 | |||
168 | 64 | return result | ||
169 | 35 | 65 | ||
170 | 36 | 66 | ||
171 | 37 | class FakeResolveFactory(object): | 67 | class FakeResolveFactory(object): |
172 | @@ -190,3 +220,116 @@ | |||
173 | 190 | transport = get_transport('lp:///apt') | 220 | transport = get_transport('lp:///apt') |
174 | 191 | branch = Branch.open_from_transport(transport) | 221 | branch = Branch.open_from_transport(transport) |
175 | 192 | self.assertEqual(target_branch.base, branch.base) | 222 | self.assertEqual(target_branch.base, branch.base) |
176 | 223 | |||
177 | 224 | |||
178 | 225 | class PredefinedRequestHandler(http_server.TestingHTTPRequestHandler): | ||
179 | 226 | """Request handler for a unique and pre-defined request. | ||
180 | 227 | |||
181 | 228 | The only thing we care about here is that we receive a connection. But | ||
182 | 229 | since we want to dialog with a real http client, we have to send it correct | ||
183 | 230 | responses. | ||
184 | 231 | |||
185 | 232 | We expect to receive a *single* request nothing more (and we won't even | ||
186 | 233 | check what request it is), the tests will recognize us from our response. | ||
187 | 234 | """ | ||
188 | 235 | |||
189 | 236 | def handle_one_request(self): | ||
190 | 237 | tcs = self.server.test_case_server | ||
191 | 238 | requestline = self.rfile.readline() | ||
192 | 239 | headers = self.MessageClass(self.rfile, 0) | ||
193 | 240 | if requestline.startswith('POST'): | ||
194 | 241 | # The body should be a single line (or we don't know where it ends | ||
195 | 242 | # and we don't want to issue a blocking read) | ||
196 | 243 | body = self.rfile.readline() | ||
197 | 244 | |||
198 | 245 | self.wfile.write(tcs.canned_response) | ||
199 | 246 | |||
200 | 247 | |||
201 | 248 | class PreCannedServerMixin(object): | ||
202 | 249 | |||
203 | 250 | def __init__(self): | ||
204 | 251 | super(PreCannedServerMixin, self).__init__( | ||
205 | 252 | request_handler=PredefinedRequestHandler) | ||
206 | 253 | # Bytes read and written by the server | ||
207 | 254 | self.bytes_read = 0 | ||
208 | 255 | self.bytes_written = 0 | ||
209 | 256 | self.canned_response = None | ||
210 | 257 | |||
211 | 258 | |||
212 | 259 | class PreCannedHTTPServer(PreCannedServerMixin, http_server.HttpServer): | ||
213 | 260 | pass | ||
214 | 261 | |||
215 | 262 | |||
216 | 263 | if tests.HTTPSServerFeature.available(): | ||
217 | 264 | from bzrlib.tests import https_server | ||
218 | 265 | class PreCannedHTTPSServer(PreCannedServerMixin, https_server.HTTPSServer): | ||
219 | 266 | pass | ||
220 | 267 | |||
221 | 268 | |||
222 | 269 | class TestXMLRPCTransport(tests.TestCase): | ||
223 | 270 | |||
224 | 271 | # set by load_tests | ||
225 | 272 | server_class = None | ||
226 | 273 | |||
227 | 274 | def setUp(self): | ||
228 | 275 | tests.TestCase.setUp(self) | ||
229 | 276 | self.server = self.server_class() | ||
230 | 277 | self.server.setUp() | ||
231 | 278 | # Ensure we don't clobber env | ||
232 | 279 | self._captureVar('BZR_LP_XMLRPC_URL', None) | ||
233 | 280 | |||
234 | 281 | def tearDown(self): | ||
235 | 282 | self.server.tearDown() | ||
236 | 283 | tests.TestCase.tearDown(self) | ||
237 | 284 | |||
238 | 285 | def set_canned_response(self, server, path): | ||
239 | 286 | response_format = '''HTTP/1.1 200 OK\r | ||
240 | 287 | Date: Tue, 11 Jul 2006 04:32:56 GMT\r | ||
241 | 288 | Server: Apache/2.0.54 (Fedora)\r | ||
242 | 289 | Last-Modified: Sun, 23 Apr 2006 19:35:20 GMT\r | ||
243 | 290 | ETag: "56691-23-38e9ae00"\r | ||
244 | 291 | Accept-Ranges: bytes\r | ||
245 | 292 | Content-Length: %(length)d\r | ||
246 | 293 | Connection: close\r | ||
247 | 294 | Content-Type: text/plain; charset=UTF-8\r | ||
248 | 295 | \r | ||
249 | 296 | <?xml version='1.0'?> | ||
250 | 297 | <methodResponse> | ||
251 | 298 | <params> | ||
252 | 299 | <param> | ||
253 | 300 | <value><struct> | ||
254 | 301 | <member> | ||
255 | 302 | <name>urls</name> | ||
256 | 303 | <value><array><data> | ||
257 | 304 | <value><string>bzr+ssh://bazaar.launchpad.net/%(path)s</string></value> | ||
258 | 305 | <value><string>http://bazaar.launchpad.net/%(path)s</string></value> | ||
259 | 306 | </data></array></value> | ||
260 | 307 | </member> | ||
261 | 308 | </struct></value> | ||
262 | 309 | </param> | ||
263 | 310 | </params> | ||
264 | 311 | </methodResponse> | ||
265 | 312 | ''' | ||
266 | 313 | length = 334 + 2 * len(path) | ||
267 | 314 | server.canned_response = response_format % dict(length=length, | ||
268 | 315 | path=path) | ||
269 | 316 | |||
270 | 317 | def do_request(self, server_url): | ||
271 | 318 | os.environ['BZR_LP_XMLRPC_URL'] = self.server.get_url() | ||
272 | 319 | service = lp_registration.LaunchpadService() | ||
273 | 320 | resolve = lp_registration.ResolveLaunchpadPathRequest('bzr') | ||
274 | 321 | result = resolve.submit(service) | ||
275 | 322 | return result | ||
276 | 323 | |||
277 | 324 | def test_direct_request(self): | ||
278 | 325 | self.set_canned_response(self.server, '~bzr-pqm/bzr/bzr.dev') | ||
279 | 326 | result = self.do_request(self.server.get_url()) | ||
280 | 327 | urls = result.get('urls', None) | ||
281 | 328 | self.assertIsNot(None, urls) | ||
282 | 329 | self.assertEquals( | ||
283 | 330 | ['bzr+ssh://bazaar.launchpad.net/~bzr-pqm/bzr/bzr.dev', | ||
284 | 331 | 'http://bazaar.launchpad.net/~bzr-pqm/bzr/bzr.dev'], | ||
285 | 332 | urls) | ||
286 | 333 | # FIXME: we need to test with a real proxy, I can't find a way so simulate | ||
287 | 334 | # CONNECT without leaving one server hanging the test :-/ Since that maybe | ||
288 | 335 | # related to the leaking tests problems, I'll punt for now -- vila 20091030 | ||
289 | 193 | 336 | ||
290 | === modified file 'bzrlib/tests/test_http.py' | |||
291 | --- bzrlib/tests/test_http.py 2009-09-17 11:54:41 +0000 | |||
292 | +++ bzrlib/tests/test_http.py 2009-10-30 21:10:26 +0000 | |||
293 | @@ -1956,7 +1956,7 @@ | |||
294 | 1956 | pass | 1956 | pass |
295 | 1957 | 1957 | ||
296 | 1958 | 1958 | ||
298 | 1959 | class TestActivity(tests.TestCase): | 1959 | class TestActivityMixin(object): |
299 | 1960 | """Test socket activity reporting. | 1960 | """Test socket activity reporting. |
300 | 1961 | 1961 | ||
301 | 1962 | We use a special purpose server to control the bytes sent and received and | 1962 | We use a special purpose server to control the bytes sent and received and |
302 | @@ -2100,3 +2100,57 @@ | |||
303 | 2100 | code, f = t._post('abc def end-of-body\n') | 2100 | code, f = t._post('abc def end-of-body\n') |
304 | 2101 | self.assertEqual('lalala whatever as long as itsssss\n', f.read()) | 2101 | self.assertEqual('lalala whatever as long as itsssss\n', f.read()) |
305 | 2102 | self.assertActivitiesMatch() | 2102 | self.assertActivitiesMatch() |
306 | 2103 | |||
307 | 2104 | |||
308 | 2105 | class TestActivity(tests.TestCase, TestActivityMixin): | ||
309 | 2106 | |||
310 | 2107 | def setUp(self): | ||
311 | 2108 | tests.TestCase.setUp(self) | ||
312 | 2109 | self.server = self._activity_server(self._protocol_version) | ||
313 | 2110 | self.server.setUp() | ||
314 | 2111 | self.activities = {} | ||
315 | 2112 | def report_activity(t, bytes, direction): | ||
316 | 2113 | count = self.activities.get(direction, 0) | ||
317 | 2114 | count += bytes | ||
318 | 2115 | self.activities[direction] = count | ||
319 | 2116 | |||
320 | 2117 | # We override at class level because constructors may propagate the | ||
321 | 2118 | # bound method and render instance overriding ineffective (an | ||
322 | 2119 | # alternative would be to define a specific ui factory instead...) | ||
323 | 2120 | self.orig_report_activity = self._transport._report_activity | ||
324 | 2121 | self._transport._report_activity = report_activity | ||
325 | 2122 | |||
326 | 2123 | def tearDown(self): | ||
327 | 2124 | self._transport._report_activity = self.orig_report_activity | ||
328 | 2125 | self.server.tearDown() | ||
329 | 2126 | tests.TestCase.tearDown(self) | ||
330 | 2127 | |||
331 | 2128 | |||
332 | 2129 | class TestNoReportActivity(tests.TestCase, TestActivityMixin): | ||
333 | 2130 | |||
334 | 2131 | def setUp(self): | ||
335 | 2132 | tests.TestCase.setUp(self) | ||
336 | 2133 | # Unlike TestActivity, we are really testing ReportingFileSocket and | ||
337 | 2134 | # ReportingSocket, so we don't need all the parametrization. Since | ||
338 | 2135 | # ReportingFileSocket and ReportingSocket are wrappers, it's easier to | ||
339 | 2136 | # test them through their use by the transport than directly (that's a | ||
340 | 2137 | # bit less clean but far more simpler and effective). | ||
341 | 2138 | self.server = ActivityHTTPServer('HTTP/1.1') | ||
342 | 2139 | self._transport=_urllib.HttpTransport_urllib | ||
343 | 2140 | |||
344 | 2141 | self.server.setUp() | ||
345 | 2142 | |||
346 | 2143 | # We override at class level because constructors may propagate the | ||
347 | 2144 | # bound method and render instance overriding ineffective (an | ||
348 | 2145 | # alternative would be to define a specific ui factory instead...) | ||
349 | 2146 | self.orig_report_activity = self._transport._report_activity | ||
350 | 2147 | self._transport._report_activity = None | ||
351 | 2148 | |||
352 | 2149 | def tearDown(self): | ||
353 | 2150 | self._transport._report_activity = self.orig_report_activity | ||
354 | 2151 | self.server.tearDown() | ||
355 | 2152 | tests.TestCase.tearDown(self) | ||
356 | 2153 | |||
357 | 2154 | def assertActivitiesMatch(self): | ||
358 | 2155 | # Nothing to check here | ||
359 | 2156 | pass | ||
360 | 2103 | 2157 | ||
361 | === modified file 'bzrlib/transport/http/_urllib2_wrappers.py' | |||
362 | --- bzrlib/transport/http/_urllib2_wrappers.py 2009-08-19 16:33:39 +0000 | |||
363 | +++ bzrlib/transport/http/_urllib2_wrappers.py 2009-10-30 21:10:26 +0000 | |||
364 | @@ -80,10 +80,13 @@ | |||
365 | 80 | self.filesock = filesock | 80 | self.filesock = filesock |
366 | 81 | self._report_activity = report_activity | 81 | self._report_activity = report_activity |
367 | 82 | 82 | ||
368 | 83 | def report_activity(self, size, direction): | ||
369 | 84 | if self._report_activity: | ||
370 | 85 | self._report_activity(size, direction) | ||
371 | 83 | 86 | ||
372 | 84 | def read(self, size=1): | 87 | def read(self, size=1): |
373 | 85 | s = self.filesock.read(size) | 88 | s = self.filesock.read(size) |
375 | 86 | self._report_activity(len(s), 'read') | 89 | self.report_activity(len(s), 'read') |
376 | 87 | return s | 90 | return s |
377 | 88 | 91 | ||
378 | 89 | def readline(self): | 92 | def readline(self): |
379 | @@ -93,7 +96,7 @@ | |||
380 | 93 | # don't *need* the size parameter we'll stay with readline(self) | 96 | # don't *need* the size parameter we'll stay with readline(self) |
381 | 94 | # -- vila 20090209 | 97 | # -- vila 20090209 |
382 | 95 | s = self.filesock.readline() | 98 | s = self.filesock.readline() |
384 | 96 | self._report_activity(len(s), 'read') | 99 | self.report_activity(len(s), 'read') |
385 | 97 | return s | 100 | return s |
386 | 98 | 101 | ||
387 | 99 | def __getattr__(self, name): | 102 | def __getattr__(self, name): |
388 | @@ -106,13 +109,17 @@ | |||
389 | 106 | self.sock = sock | 109 | self.sock = sock |
390 | 107 | self._report_activity = report_activity | 110 | self._report_activity = report_activity |
391 | 108 | 111 | ||
392 | 112 | def report_activity(self, size, direction): | ||
393 | 113 | if self._report_activity: | ||
394 | 114 | self._report_activity(size, direction) | ||
395 | 115 | |||
396 | 109 | def sendall(self, s, *args): | 116 | def sendall(self, s, *args): |
397 | 110 | self.sock.sendall(s, *args) | 117 | self.sock.sendall(s, *args) |
399 | 111 | self._report_activity(len(s), 'write') | 118 | self.report_activity(len(s), 'write') |
400 | 112 | 119 | ||
401 | 113 | def recv(self, *args): | 120 | def recv(self, *args): |
402 | 114 | s = self.sock.recv(*args) | 121 | s = self.sock.recv(*args) |
404 | 115 | self._report_activity(len(s), 'read') | 122 | self.report_activity(len(s), 'read') |
405 | 116 | return s | 123 | return s |
406 | 117 | 124 | ||
407 | 118 | def makefile(self, mode='r', bufsize=-1): | 125 | def makefile(self, mode='r', bufsize=-1): |
408 | @@ -219,8 +226,7 @@ | |||
409 | 219 | # we want to warn. But not below a given thresold. | 226 | # we want to warn. But not below a given thresold. |
410 | 220 | _range_warning_thresold = 1024 * 1024 | 227 | _range_warning_thresold = 1024 * 1024 |
411 | 221 | 228 | ||
414 | 222 | def __init__(self, | 229 | def __init__(self, report_activity=None): |
413 | 223 | report_activity=None): | ||
415 | 224 | self._response = None | 230 | self._response = None |
416 | 225 | self._report_activity = report_activity | 231 | self._report_activity = report_activity |
417 | 226 | self._ranges_received_whole_file = None | 232 | self._ranges_received_whole_file = None |
418 | @@ -360,7 +366,13 @@ | |||
419 | 360 | 366 | ||
420 | 361 | def set_proxy(self, proxy, type): | 367 | def set_proxy(self, proxy, type): |
421 | 362 | """Set the proxy and remember the proxied host.""" | 368 | """Set the proxy and remember the proxied host.""" |
423 | 363 | self.proxied_host = self.get_host() | 369 | # We need to set the default port ourselves way before it gets set |
424 | 370 | # in the HTTP[S]Connection object at build time. | ||
425 | 371 | if self.type == 'https': | ||
426 | 372 | conn_class = HTTPSConnection | ||
427 | 373 | else: | ||
428 | 374 | conn_class = HTTPConnection | ||
429 | 375 | self.proxied_host = '%s:%s' % (self.get_host(), conn_class.default_port) | ||
430 | 364 | urllib2.Request.set_proxy(self, proxy, type) | 376 | urllib2.Request.set_proxy(self, proxy, type) |
431 | 365 | 377 | ||
432 | 366 | 378 |
Thanks to Gordon Tyler for starting a proof-of-concept implementation, I've finally
been able to fix bug #186920 et all (the one about lp URLS that can't be
resolved behind proxies).
This patch implement an XMLRPC transport class using our _urllib2_wrappers (the fundations
for the urllib http client), that gives us proxy support for free.
I still haven't implemented a true proxy test server but I've added tests that exercise
the full urllib http stack.