comparison mercurial/url.py @ 14244:e7525a555a64

url: use new http support if requested by the user The new http library is wired in via an extra module (httpconnection.py), as it requires similar but different plumbing to connect the library to Mercurial's internals and urllib2. Eventualy we should be able to remove all of keepalive.py and its associated tangle in url.py and replace it all with the code in httpconnection.py. To use the new library, set 'ui.usehttp2' to true. The underlying http library uses the logging module liberally, so if things break you can use 'ui.http2debuglevel' to set the log level to INFO or DEBUG to get that logging information (for example, ui.http2debuglevel=info.)
author Augie Fackler <durin42@gmail.com>
date Fri, 06 May 2011 10:22:08 -0500
parents 5fa21960b2f4
children 376c32a5ccdc 4a43e23b8c55
comparison
equal deleted inserted replaced
14243:861f28212398 14244:e7525a555a64
6 # 6 #
7 # This software may be used and distributed according to the terms of the 7 # This software may be used and distributed according to the terms of the
8 # GNU General Public License version 2 or any later version. 8 # GNU General Public License version 2 or any later version.
9 9
10 import urllib, urllib2, httplib, os, socket, cStringIO 10 import urllib, urllib2, httplib, os, socket, cStringIO
11 import __builtin__
12 from i18n import _ 11 from i18n import _
13 import keepalive, util, sslutil 12 import keepalive, util, sslutil
14 13 import httpconnection as httpconnectionmod
15 def readauthforuri(ui, uri):
16 # Read configuration
17 config = dict()
18 for key, val in ui.configitems('auth'):
19 if '.' not in key:
20 ui.warn(_("ignoring invalid [auth] key '%s'\n") % key)
21 continue
22 group, setting = key.rsplit('.', 1)
23 gdict = config.setdefault(group, dict())
24 if setting in ('username', 'cert', 'key'):
25 val = util.expandpath(val)
26 gdict[setting] = val
27
28 # Find the best match
29 scheme, hostpath = uri.split('://', 1)
30 bestlen = 0
31 bestauth = None
32 for group, auth in config.iteritems():
33 prefix = auth.get('prefix')
34 if not prefix:
35 continue
36 p = prefix.split('://', 1)
37 if len(p) > 1:
38 schemes, prefix = [p[0]], p[1]
39 else:
40 schemes = (auth.get('schemes') or 'https').split()
41 if (prefix == '*' or hostpath.startswith(prefix)) and \
42 len(prefix) > bestlen and scheme in schemes:
43 bestlen = len(prefix)
44 bestauth = group, auth
45 return bestauth
46 14
47 class passwordmgr(urllib2.HTTPPasswordMgrWithDefaultRealm): 15 class passwordmgr(urllib2.HTTPPasswordMgrWithDefaultRealm):
48 def __init__(self, ui): 16 def __init__(self, ui):
49 urllib2.HTTPPasswordMgrWithDefaultRealm.__init__(self) 17 urllib2.HTTPPasswordMgrWithDefaultRealm.__init__(self)
50 self.ui = ui 18 self.ui = ui
56 if user and passwd: 24 if user and passwd:
57 self._writedebug(user, passwd) 25 self._writedebug(user, passwd)
58 return (user, passwd) 26 return (user, passwd)
59 27
60 if not user: 28 if not user:
61 res = readauthforuri(self.ui, authuri) 29 res = httpconnectionmod.readauthforuri(self.ui, authuri)
62 if res: 30 if res:
63 group, auth = res 31 group, auth = res
64 user, passwd = auth.get('username'), auth.get('password') 32 user, passwd = auth.get('username'), auth.get('password')
65 self.ui.debug("using auth.%s.* for authentication\n" % group) 33 self.ui.debug("using auth.%s.* for authentication\n" % group)
66 if not user or not passwd: 34 if not user or not passwd:
147 return baseclass.add_header(self, key, val) 115 return baseclass.add_header(self, key, val)
148 req.__class__ = _request 116 req.__class__ = _request
149 117
150 return urllib2.ProxyHandler.proxy_open(self, req, proxy, type_) 118 return urllib2.ProxyHandler.proxy_open(self, req, proxy, type_)
151 119
152 class httpsendfile(object):
153 """This is a wrapper around the objects returned by python's "open".
154
155 Its purpose is to send file-like objects via HTTP and, to do so, it
156 defines a __len__ attribute to feed the Content-Length header.
157 """
158
159 def __init__(self, ui, *args, **kwargs):
160 # We can't just "self._data = open(*args, **kwargs)" here because there
161 # is an "open" function defined in this module that shadows the global
162 # one
163 self.ui = ui
164 self._data = __builtin__.open(*args, **kwargs)
165 self.seek = self._data.seek
166 self.close = self._data.close
167 self.write = self._data.write
168 self._len = os.fstat(self._data.fileno()).st_size
169 self._pos = 0
170 self._total = len(self) / 1024 * 2
171
172 def read(self, *args, **kwargs):
173 try:
174 ret = self._data.read(*args, **kwargs)
175 except EOFError:
176 self.ui.progress(_('sending'), None)
177 self._pos += len(ret)
178 # We pass double the max for total because we currently have
179 # to send the bundle twice in the case of a server that
180 # requires authentication. Since we can't know until we try
181 # once whether authentication will be required, just lie to
182 # the user and maybe the push succeeds suddenly at 50%.
183 self.ui.progress(_('sending'), self._pos / 1024,
184 unit=_('kb'), total=self._total)
185 return ret
186
187 def __len__(self):
188 return self._len
189
190 def _gen_sendfile(orgsend): 120 def _gen_sendfile(orgsend):
191 def _sendfile(self, data): 121 def _sendfile(self, data):
192 # send a file 122 # send a file
193 if isinstance(data, httpsendfile): 123 if isinstance(data, httpconnectionmod.httpsendfile):
194 # if auth required, some data sent twice, so rewind here 124 # if auth required, some data sent twice, so rewind here
195 data.seek(0) 125 data.seek(0)
196 for chunk in util.filechunkiter(data): 126 for chunk in util.filechunkiter(data):
197 orgsend(self, chunk) 127 orgsend(self, chunk)
198 else: 128 else:
410 def _start_transaction(self, h, req): 340 def _start_transaction(self, h, req):
411 _generic_start_transaction(self, h, req) 341 _generic_start_transaction(self, h, req)
412 return keepalive.KeepAliveHandler._start_transaction(self, h, req) 342 return keepalive.KeepAliveHandler._start_transaction(self, h, req)
413 343
414 def https_open(self, req): 344 def https_open(self, req):
415 res = readauthforuri(self.ui, req.get_full_url()) 345 res = httpconnectionmod.readauthforuri(self.ui, req.get_full_url())
416 if res: 346 if res:
417 group, auth = res 347 group, auth = res
418 self.auth = auth 348 self.auth = auth
419 self.ui.debug("using auth.%s.* for authentication\n" % group) 349 self.ui.debug("using auth.%s.* for authentication\n" % group)
420 else: 350 else:
493 def opener(ui, authinfo=None): 423 def opener(ui, authinfo=None):
494 ''' 424 '''
495 construct an opener suitable for urllib2 425 construct an opener suitable for urllib2
496 authinfo will be added to the password manager 426 authinfo will be added to the password manager
497 ''' 427 '''
498 handlers = [httphandler()] 428 if ui.configbool('ui', 'usehttp2', False):
499 if has_https: 429 handlers = [httpconnectionmod.http2handler(ui, passwordmgr(ui))]
500 handlers.append(httpshandler(ui)) 430 else:
431 handlers = [httphandler()]
432 if has_https:
433 handlers.append(httpshandler(ui))
501 434
502 handlers.append(proxyhandler(ui)) 435 handlers.append(proxyhandler(ui))
503 436
504 passmgr = passwordmgr(ui) 437 passmgr = passwordmgr(ui)
505 if authinfo is not None: 438 if authinfo is not None: