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