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