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