##// END OF EJS Templates
httpconnection: remove a mutable default argument...
Pierre-Yves David -
r26347:e9a35411 default
parent child Browse files
Show More
@@ -1,284 +1,286 b''
1 1 # httpconnection.py - urllib2 handler for new http support
2 2 #
3 3 # Copyright 2005, 2006, 2007, 2008 Matt Mackall <mpm@selenic.com>
4 4 # Copyright 2006, 2007 Alexis S. L. Carvalho <alexis@cecm.usp.br>
5 5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
6 6 # Copyright 2011 Google, Inc.
7 7 #
8 8 # This software may be used and distributed according to the terms of the
9 9 # GNU General Public License version 2 or any later version.
10 10 import logging
11 11 import socket
12 12 import urllib
13 13 import urllib2
14 14 import os
15 15
16 16 from mercurial import httpclient
17 17 from mercurial import sslutil
18 18 from mercurial import util
19 19 from mercurial.i18n import _
20 20
21 21 # moved here from url.py to avoid a cycle
22 22 class httpsendfile(object):
23 23 """This is a wrapper around the objects returned by python's "open".
24 24
25 25 Its purpose is to send file-like objects via HTTP.
26 26 It do however not define a __len__ attribute because the length
27 27 might be more than Py_ssize_t can handle.
28 28 """
29 29
30 30 def __init__(self, ui, *args, **kwargs):
31 31 # We can't just "self._data = open(*args, **kwargs)" here because there
32 32 # is an "open" function defined in this module that shadows the global
33 33 # one
34 34 self.ui = ui
35 35 self._data = open(*args, **kwargs)
36 36 self.seek = self._data.seek
37 37 self.close = self._data.close
38 38 self.write = self._data.write
39 39 self.length = os.fstat(self._data.fileno()).st_size
40 40 self._pos = 0
41 41 self._total = self.length // 1024 * 2
42 42
43 43 def read(self, *args, **kwargs):
44 44 try:
45 45 ret = self._data.read(*args, **kwargs)
46 46 except EOFError:
47 47 self.ui.progress(_('sending'), None)
48 48 self._pos += len(ret)
49 49 # We pass double the max for total because we currently have
50 50 # to send the bundle twice in the case of a server that
51 51 # requires authentication. Since we can't know until we try
52 52 # once whether authentication will be required, just lie to
53 53 # the user and maybe the push succeeds suddenly at 50%.
54 54 self.ui.progress(_('sending'), self._pos // 1024,
55 55 unit=_('kb'), total=self._total)
56 56 return ret
57 57
58 58 # moved here from url.py to avoid a cycle
59 59 def readauthforuri(ui, uri, user):
60 60 # Read configuration
61 61 config = dict()
62 62 for key, val in ui.configitems('auth'):
63 63 if '.' not in key:
64 64 ui.warn(_("ignoring invalid [auth] key '%s'\n") % key)
65 65 continue
66 66 group, setting = key.rsplit('.', 1)
67 67 gdict = config.setdefault(group, dict())
68 68 if setting in ('username', 'cert', 'key'):
69 69 val = util.expandpath(val)
70 70 gdict[setting] = val
71 71
72 72 # Find the best match
73 73 scheme, hostpath = uri.split('://', 1)
74 74 bestuser = None
75 75 bestlen = 0
76 76 bestauth = None
77 77 for group, auth in config.iteritems():
78 78 if user and user != auth.get('username', user):
79 79 # If a username was set in the URI, the entry username
80 80 # must either match it or be unset
81 81 continue
82 82 prefix = auth.get('prefix')
83 83 if not prefix:
84 84 continue
85 85 p = prefix.split('://', 1)
86 86 if len(p) > 1:
87 87 schemes, prefix = [p[0]], p[1]
88 88 else:
89 89 schemes = (auth.get('schemes') or 'https').split()
90 90 if (prefix == '*' or hostpath.startswith(prefix)) and \
91 91 (len(prefix) > bestlen or (len(prefix) == bestlen and \
92 92 not bestuser and 'username' in auth)) \
93 93 and scheme in schemes:
94 94 bestlen = len(prefix)
95 95 bestauth = group, auth
96 96 bestuser = auth.get('username')
97 97 if user and not bestuser:
98 98 auth['username'] = user
99 99 return bestauth
100 100
101 101 # Mercurial (at least until we can remove the old codepath) requires
102 102 # that the http response object be sufficiently file-like, so we
103 103 # provide a close() method here.
104 104 class HTTPResponse(httpclient.HTTPResponse):
105 105 def close(self):
106 106 pass
107 107
108 108 class HTTPConnection(httpclient.HTTPConnection):
109 109 response_class = HTTPResponse
110 def request(self, method, uri, body=None, headers={}):
110 def request(self, method, uri, body=None, headers=None):
111 if headers is None:
112 headers = {}
111 113 if isinstance(body, httpsendfile):
112 114 body.seek(0)
113 115 httpclient.HTTPConnection.request(self, method, uri, body=body,
114 116 headers=headers)
115 117
116 118
117 119 _configuredlogging = False
118 120 LOGFMT = '%(levelname)s:%(name)s:%(lineno)d:%(message)s'
119 121 # Subclass BOTH of these because otherwise urllib2 "helpfully"
120 122 # reinserts them since it notices we don't include any subclasses of
121 123 # them.
122 124 class http2handler(urllib2.HTTPHandler, urllib2.HTTPSHandler):
123 125 def __init__(self, ui, pwmgr):
124 126 global _configuredlogging
125 127 urllib2.AbstractHTTPHandler.__init__(self)
126 128 self.ui = ui
127 129 self.pwmgr = pwmgr
128 130 self._connections = {}
129 131 # developer config: ui.http2debuglevel
130 132 loglevel = ui.config('ui', 'http2debuglevel', default=None)
131 133 if loglevel and not _configuredlogging:
132 134 _configuredlogging = True
133 135 logger = logging.getLogger('mercurial.httpclient')
134 136 logger.setLevel(getattr(logging, loglevel.upper()))
135 137 handler = logging.StreamHandler()
136 138 handler.setFormatter(logging.Formatter(LOGFMT))
137 139 logger.addHandler(handler)
138 140
139 141 def close_all(self):
140 142 """Close and remove all connection objects being kept for reuse."""
141 143 for openconns in self._connections.values():
142 144 for conn in openconns:
143 145 conn.close()
144 146 self._connections = {}
145 147
146 148 # shamelessly borrowed from urllib2.AbstractHTTPHandler
147 149 def do_open(self, http_class, req, use_ssl):
148 150 """Return an addinfourl object for the request, using http_class.
149 151
150 152 http_class must implement the HTTPConnection API from httplib.
151 153 The addinfourl return value is a file-like object. It also
152 154 has methods and attributes including:
153 155 - info(): return a mimetools.Message object for the headers
154 156 - geturl(): return the original request URL
155 157 - code: HTTP status code
156 158 """
157 159 # If using a proxy, the host returned by get_host() is
158 160 # actually the proxy. On Python 2.6.1, the real destination
159 161 # hostname is encoded in the URI in the urllib2 request
160 162 # object. On Python 2.6.5, it's stored in the _tunnel_host
161 163 # attribute which has no accessor.
162 164 tunhost = getattr(req, '_tunnel_host', None)
163 165 host = req.get_host()
164 166 if tunhost:
165 167 proxyhost = host
166 168 host = tunhost
167 169 elif req.has_proxy():
168 170 proxyhost = req.get_host()
169 171 host = req.get_selector().split('://', 1)[1].split('/', 1)[0]
170 172 else:
171 173 proxyhost = None
172 174
173 175 if proxyhost:
174 176 if ':' in proxyhost:
175 177 # Note: this means we'll explode if we try and use an
176 178 # IPv6 http proxy. This isn't a regression, so we
177 179 # won't worry about it for now.
178 180 proxyhost, proxyport = proxyhost.rsplit(':', 1)
179 181 else:
180 182 proxyport = 3128 # squid default
181 183 proxy = (proxyhost, proxyport)
182 184 else:
183 185 proxy = None
184 186
185 187 if not host:
186 188 raise urllib2.URLError('no host given')
187 189
188 190 connkey = use_ssl, host, proxy
189 191 allconns = self._connections.get(connkey, [])
190 192 conns = [c for c in allconns if not c.busy()]
191 193 if conns:
192 194 h = conns[0]
193 195 else:
194 196 if allconns:
195 197 self.ui.debug('all connections for %s busy, making a new '
196 198 'one\n' % host)
197 199 timeout = None
198 200 if req.timeout is not socket._GLOBAL_DEFAULT_TIMEOUT:
199 201 timeout = req.timeout
200 202 h = http_class(host, timeout=timeout, proxy_hostport=proxy)
201 203 self._connections.setdefault(connkey, []).append(h)
202 204
203 205 headers = dict(req.headers)
204 206 headers.update(req.unredirected_hdrs)
205 207 headers = dict(
206 208 (name.title(), val) for name, val in headers.items())
207 209 try:
208 210 path = req.get_selector()
209 211 if '://' in path:
210 212 path = path.split('://', 1)[1].split('/', 1)[1]
211 213 if path[0] != '/':
212 214 path = '/' + path
213 215 h.request(req.get_method(), path, req.data, headers)
214 216 r = h.getresponse()
215 217 except socket.error as err: # XXX what error?
216 218 raise urllib2.URLError(err)
217 219
218 220 # Pick apart the HTTPResponse object to get the addinfourl
219 221 # object initialized properly.
220 222 r.recv = r.read
221 223
222 224 resp = urllib.addinfourl(r, r.headers, req.get_full_url())
223 225 resp.code = r.status
224 226 resp.msg = r.reason
225 227 return resp
226 228
227 229 # httplib always uses the given host/port as the socket connect
228 230 # target, and then allows full URIs in the request path, which it
229 231 # then observes and treats as a signal to do proxying instead.
230 232 def http_open(self, req):
231 233 if req.get_full_url().startswith('https'):
232 234 return self.https_open(req)
233 235 def makehttpcon(*args, **kwargs):
234 236 k2 = dict(kwargs)
235 237 k2['use_ssl'] = False
236 238 return HTTPConnection(*args, **k2)
237 239 return self.do_open(makehttpcon, req, False)
238 240
239 241 def https_open(self, req):
240 242 # req.get_full_url() does not contain credentials and we may
241 243 # need them to match the certificates.
242 244 url = req.get_full_url()
243 245 user, password = self.pwmgr.find_stored_password(url)
244 246 res = readauthforuri(self.ui, url, user)
245 247 if res:
246 248 group, auth = res
247 249 self.auth = auth
248 250 self.ui.debug("using auth.%s.* for authentication\n" % group)
249 251 else:
250 252 self.auth = None
251 253 return self.do_open(self._makesslconnection, req, True)
252 254
253 255 def _makesslconnection(self, host, port=443, *args, **kwargs):
254 256 keyfile = None
255 257 certfile = None
256 258
257 259 if args: # key_file
258 260 keyfile = args.pop(0)
259 261 if args: # cert_file
260 262 certfile = args.pop(0)
261 263
262 264 # if the user has specified different key/cert files in
263 265 # hgrc, we prefer these
264 266 if self.auth and 'key' in self.auth and 'cert' in self.auth:
265 267 keyfile = self.auth['key']
266 268 certfile = self.auth['cert']
267 269
268 270 # let host port take precedence
269 271 if ':' in host and '[' not in host or ']:' in host:
270 272 host, port = host.rsplit(':', 1)
271 273 port = int(port)
272 274 if '[' in host:
273 275 host = host[1:-1]
274 276
275 277 kwargs['keyfile'] = keyfile
276 278 kwargs['certfile'] = certfile
277 279
278 280 kwargs.update(sslutil.sslkwargs(self.ui, host))
279 281
280 282 con = HTTPConnection(host, port, use_ssl=True,
281 283 ssl_wrap_socket=sslutil.wrapsocket,
282 284 ssl_validator=sslutil.validator(self.ui, host),
283 285 **kwargs)
284 286 return con
General Comments 0
You need to be logged in to leave comments. Login now