##// END OF EJS Templates
httppeer: extract common response handling into own function...
Gregory Szorc -
r37569:946eb204 default
parent child Browse files
Show More
@@ -1,635 +1,643
1 1 # httppeer.py - HTTP repository proxy classes for mercurial
2 2 #
3 3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
4 4 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
5 5 #
6 6 # This software may be used and distributed according to the terms of the
7 7 # GNU General Public License version 2 or any later version.
8 8
9 9 from __future__ import absolute_import
10 10
11 11 import errno
12 12 import io
13 13 import os
14 14 import socket
15 15 import struct
16 16 import tempfile
17 17
18 18 from .i18n import _
19 19 from .thirdparty import (
20 20 cbor,
21 21 )
22 22 from . import (
23 23 bundle2,
24 24 error,
25 25 httpconnection,
26 26 pycompat,
27 27 statichttprepo,
28 28 url as urlmod,
29 29 util,
30 30 wireproto,
31 31 wireprotoframing,
32 32 wireprotov2server,
33 33 )
34 34
35 35 httplib = util.httplib
36 36 urlerr = util.urlerr
37 37 urlreq = util.urlreq
38 38
39 39 def encodevalueinheaders(value, header, limit):
40 40 """Encode a string value into multiple HTTP headers.
41 41
42 42 ``value`` will be encoded into 1 or more HTTP headers with the names
43 43 ``header-<N>`` where ``<N>`` is an integer starting at 1. Each header
44 44 name + value will be at most ``limit`` bytes long.
45 45
46 46 Returns an iterable of 2-tuples consisting of header names and
47 47 values as native strings.
48 48 """
49 49 # HTTP Headers are ASCII. Python 3 requires them to be unicodes,
50 50 # not bytes. This function always takes bytes in as arguments.
51 51 fmt = pycompat.strurl(header) + r'-%s'
52 52 # Note: it is *NOT* a bug that the last bit here is a bytestring
53 53 # and not a unicode: we're just getting the encoded length anyway,
54 54 # and using an r-string to make it portable between Python 2 and 3
55 55 # doesn't work because then the \r is a literal backslash-r
56 56 # instead of a carriage return.
57 57 valuelen = limit - len(fmt % r'000') - len(': \r\n')
58 58 result = []
59 59
60 60 n = 0
61 61 for i in xrange(0, len(value), valuelen):
62 62 n += 1
63 63 result.append((fmt % str(n), pycompat.strurl(value[i:i + valuelen])))
64 64
65 65 return result
66 66
67 67 def _wraphttpresponse(resp):
68 68 """Wrap an HTTPResponse with common error handlers.
69 69
70 70 This ensures that any I/O from any consumer raises the appropriate
71 71 error and messaging.
72 72 """
73 73 origread = resp.read
74 74
75 75 class readerproxy(resp.__class__):
76 76 def read(self, size=None):
77 77 try:
78 78 return origread(size)
79 79 except httplib.IncompleteRead as e:
80 80 # e.expected is an integer if length known or None otherwise.
81 81 if e.expected:
82 82 msg = _('HTTP request error (incomplete response; '
83 83 'expected %d bytes got %d)') % (e.expected,
84 84 len(e.partial))
85 85 else:
86 86 msg = _('HTTP request error (incomplete response)')
87 87
88 88 raise error.PeerTransportError(
89 89 msg,
90 90 hint=_('this may be an intermittent network failure; '
91 91 'if the error persists, consider contacting the '
92 92 'network or server operator'))
93 93 except httplib.HTTPException as e:
94 94 raise error.PeerTransportError(
95 95 _('HTTP request error (%s)') % e,
96 96 hint=_('this may be an intermittent network failure; '
97 97 'if the error persists, consider contacting the '
98 98 'network or server operator'))
99 99
100 100 resp.__class__ = readerproxy
101 101
102 102 class _multifile(object):
103 103 def __init__(self, *fileobjs):
104 104 for f in fileobjs:
105 105 if not util.safehasattr(f, 'length'):
106 106 raise ValueError(
107 107 '_multifile only supports file objects that '
108 108 'have a length but this one does not:', type(f), f)
109 109 self._fileobjs = fileobjs
110 110 self._index = 0
111 111
112 112 @property
113 113 def length(self):
114 114 return sum(f.length for f in self._fileobjs)
115 115
116 116 def read(self, amt=None):
117 117 if amt <= 0:
118 118 return ''.join(f.read() for f in self._fileobjs)
119 119 parts = []
120 120 while amt and self._index < len(self._fileobjs):
121 121 parts.append(self._fileobjs[self._index].read(amt))
122 122 got = len(parts[-1])
123 123 if got < amt:
124 124 self._index += 1
125 125 amt -= got
126 126 return ''.join(parts)
127 127
128 128 def seek(self, offset, whence=os.SEEK_SET):
129 129 if whence != os.SEEK_SET:
130 130 raise NotImplementedError(
131 131 '_multifile does not support anything other'
132 132 ' than os.SEEK_SET for whence on seek()')
133 133 if offset != 0:
134 134 raise NotImplementedError(
135 135 '_multifile only supports seeking to start, but that '
136 136 'could be fixed if you need it')
137 137 for f in self._fileobjs:
138 138 f.seek(0)
139 139 self._index = 0
140 140
141 141 def makev1commandrequest(ui, requestbuilder, caps, capablefn,
142 142 repobaseurl, cmd, args):
143 143 """Make an HTTP request to run a command for a version 1 client.
144 144
145 145 ``caps`` is a set of known server capabilities. The value may be
146 146 None if capabilities are not yet known.
147 147
148 148 ``capablefn`` is a function to evaluate a capability.
149 149
150 150 ``cmd``, ``args``, and ``data`` define the command, its arguments, and
151 151 raw data to pass to it.
152 152 """
153 153 if cmd == 'pushkey':
154 154 args['data'] = ''
155 155 data = args.pop('data', None)
156 156 headers = args.pop('headers', {})
157 157
158 158 ui.debug("sending %s command\n" % cmd)
159 159 q = [('cmd', cmd)]
160 160 headersize = 0
161 161 varyheaders = []
162 162 # Important: don't use self.capable() here or else you end up
163 163 # with infinite recursion when trying to look up capabilities
164 164 # for the first time.
165 165 postargsok = caps is not None and 'httppostargs' in caps
166 166
167 167 # Send arguments via POST.
168 168 if postargsok and args:
169 169 strargs = urlreq.urlencode(sorted(args.items()))
170 170 if not data:
171 171 data = strargs
172 172 else:
173 173 if isinstance(data, bytes):
174 174 i = io.BytesIO(data)
175 175 i.length = len(data)
176 176 data = i
177 177 argsio = io.BytesIO(strargs)
178 178 argsio.length = len(strargs)
179 179 data = _multifile(argsio, data)
180 180 headers[r'X-HgArgs-Post'] = len(strargs)
181 181 elif args:
182 182 # Calling self.capable() can infinite loop if we are calling
183 183 # "capabilities". But that command should never accept wire
184 184 # protocol arguments. So this should never happen.
185 185 assert cmd != 'capabilities'
186 186 httpheader = capablefn('httpheader')
187 187 if httpheader:
188 188 headersize = int(httpheader.split(',', 1)[0])
189 189
190 190 # Send arguments via HTTP headers.
191 191 if headersize > 0:
192 192 # The headers can typically carry more data than the URL.
193 193 encargs = urlreq.urlencode(sorted(args.items()))
194 194 for header, value in encodevalueinheaders(encargs, 'X-HgArg',
195 195 headersize):
196 196 headers[header] = value
197 197 varyheaders.append(header)
198 198 # Send arguments via query string (Mercurial <1.9).
199 199 else:
200 200 q += sorted(args.items())
201 201
202 202 qs = '?%s' % urlreq.urlencode(q)
203 203 cu = "%s%s" % (repobaseurl, qs)
204 204 size = 0
205 205 if util.safehasattr(data, 'length'):
206 206 size = data.length
207 207 elif data is not None:
208 208 size = len(data)
209 209 if data is not None and r'Content-Type' not in headers:
210 210 headers[r'Content-Type'] = r'application/mercurial-0.1'
211 211
212 212 # Tell the server we accept application/mercurial-0.2 and multiple
213 213 # compression formats if the server is capable of emitting those
214 214 # payloads.
215 215 protoparams = {'partial-pull'}
216 216
217 217 mediatypes = set()
218 218 if caps is not None:
219 219 mt = capablefn('httpmediatype')
220 220 if mt:
221 221 protoparams.add('0.1')
222 222 mediatypes = set(mt.split(','))
223 223
224 224 if '0.2tx' in mediatypes:
225 225 protoparams.add('0.2')
226 226
227 227 if '0.2tx' in mediatypes and capablefn('compression'):
228 228 # We /could/ compare supported compression formats and prune
229 229 # non-mutually supported or error if nothing is mutually supported.
230 230 # For now, send the full list to the server and have it error.
231 231 comps = [e.wireprotosupport().name for e in
232 232 util.compengines.supportedwireengines(util.CLIENTROLE)]
233 233 protoparams.add('comp=%s' % ','.join(comps))
234 234
235 235 if protoparams:
236 236 protoheaders = encodevalueinheaders(' '.join(sorted(protoparams)),
237 237 'X-HgProto',
238 238 headersize or 1024)
239 239 for header, value in protoheaders:
240 240 headers[header] = value
241 241 varyheaders.append(header)
242 242
243 243 if varyheaders:
244 244 headers[r'Vary'] = r','.join(varyheaders)
245 245
246 246 req = requestbuilder(pycompat.strurl(cu), data, headers)
247 247
248 248 if data is not None:
249 249 ui.debug("sending %d bytes\n" % size)
250 250 req.add_unredirected_header(r'Content-Length', r'%d' % size)
251 251
252 252 return req, cu, qs
253 253
254 254 def sendrequest(ui, opener, req):
255 255 """Send a prepared HTTP request.
256 256
257 257 Returns the response object.
258 258 """
259 259 if (ui.debugflag
260 260 and ui.configbool('devel', 'debug.peer-request')):
261 261 dbg = ui.debug
262 262 line = 'devel-peer-request: %s\n'
263 263 dbg(line % '%s %s' % (req.get_method(), req.get_full_url()))
264 264 hgargssize = None
265 265
266 266 for header, value in sorted(req.header_items()):
267 267 if header.startswith('X-hgarg-'):
268 268 if hgargssize is None:
269 269 hgargssize = 0
270 270 hgargssize += len(value)
271 271 else:
272 272 dbg(line % ' %s %s' % (header, value))
273 273
274 274 if hgargssize is not None:
275 275 dbg(line % ' %d bytes of commands arguments in headers'
276 276 % hgargssize)
277 277
278 278 if req.has_data():
279 279 data = req.get_data()
280 280 length = getattr(data, 'length', None)
281 281 if length is None:
282 282 length = len(data)
283 283 dbg(line % ' %d bytes of data' % length)
284 284
285 285 start = util.timer()
286 286
287 287 try:
288 288 res = opener.open(req)
289 289 except urlerr.httperror as inst:
290 290 if inst.code == 401:
291 291 raise error.Abort(_('authorization failed'))
292 292 raise
293 293 except httplib.HTTPException as inst:
294 294 ui.debug('http error requesting %s\n' %
295 295 util.hidepassword(req.get_full_url()))
296 296 ui.traceback()
297 297 raise IOError(None, inst)
298 298 finally:
299 299 if ui.configbool('devel', 'debug.peer-request'):
300 300 dbg(line % ' finished in %.4f seconds (%s)'
301 301 % (util.timer() - start, res.code))
302 302
303 303 # Insert error handlers for common I/O failures.
304 304 _wraphttpresponse(res)
305 305
306 306 return res
307 307
308 def parsev1commandresponse(ui, baseurl, requrl, qs, resp, compressible):
309 # record the url we got redirected to
310 respurl = pycompat.bytesurl(resp.geturl())
311 if respurl.endswith(qs):
312 respurl = respurl[:-len(qs)]
313 if baseurl.rstrip('/') != respurl.rstrip('/'):
314 if not ui.quiet:
315 ui.warn(_('real URL is %s\n') % respurl)
316
317 try:
318 proto = pycompat.bytesurl(resp.getheader(r'content-type', r''))
319 except AttributeError:
320 proto = pycompat.bytesurl(resp.headers.get(r'content-type', r''))
321
322 safeurl = util.hidepassword(baseurl)
323 if proto.startswith('application/hg-error'):
324 raise error.OutOfBandError(resp.read())
325 # accept old "text/plain" and "application/hg-changegroup" for now
326 if not (proto.startswith('application/mercurial-') or
327 (proto.startswith('text/plain')
328 and not resp.headers.get('content-length')) or
329 proto.startswith('application/hg-changegroup')):
330 ui.debug("requested URL: '%s'\n" % util.hidepassword(requrl))
331 raise error.RepoError(
332 _("'%s' does not appear to be an hg repository:\n"
333 "---%%<--- (%s)\n%s\n---%%<---\n")
334 % (safeurl, proto or 'no content-type', resp.read(1024)))
335
336 if proto.startswith('application/mercurial-'):
337 try:
338 version = proto.split('-', 1)[1]
339 version_info = tuple([int(n) for n in version.split('.')])
340 except ValueError:
341 raise error.RepoError(_("'%s' sent a broken Content-Type "
342 "header (%s)") % (safeurl, proto))
343
344 # TODO consider switching to a decompression reader that uses
345 # generators.
346 if version_info == (0, 1):
347 if compressible:
348 resp = util.compengines['zlib'].decompressorreader(resp)
349
350 return respurl, resp
351
352 elif version_info == (0, 2):
353 # application/mercurial-0.2 always identifies the compression
354 # engine in the payload header.
355 elen = struct.unpack('B', resp.read(1))[0]
356 ename = resp.read(elen)
357 engine = util.compengines.forwiretype(ename)
358 return respurl, engine.decompressorreader(resp)
359 else:
360 raise error.RepoError(_("'%s' uses newer protocol %s") %
361 (safeurl, version))
362
363 if compressible:
364 resp = util.compengines['zlib'].decompressorreader(resp)
365
366 return respurl, resp
367
308 368 class httppeer(wireproto.wirepeer):
309 369 def __init__(self, ui, path, url, opener, requestbuilder):
310 370 self.ui = ui
311 371 self._path = path
312 372 self._url = url
313 373 self._caps = None
314 374 self._urlopener = opener
315 375 self._requestbuilder = requestbuilder
316 376
317 377 def __del__(self):
318 378 for h in self._urlopener.handlers:
319 379 h.close()
320 380 getattr(h, "close_all", lambda: None)()
321 381
322 382 # Begin of ipeerconnection interface.
323 383
324 384 def url(self):
325 385 return self._path
326 386
327 387 def local(self):
328 388 return None
329 389
330 390 def peer(self):
331 391 return self
332 392
333 393 def canpush(self):
334 394 return True
335 395
336 396 def close(self):
337 397 pass
338 398
339 399 # End of ipeerconnection interface.
340 400
341 401 # Begin of ipeercommands interface.
342 402
343 403 def capabilities(self):
344 404 # self._fetchcaps() should have been called as part of peer
345 405 # handshake. So self._caps should always be set.
346 406 assert self._caps is not None
347 407 return self._caps
348 408
349 409 # End of ipeercommands interface.
350 410
351 411 # look up capabilities only when needed
352 412
353 413 def _fetchcaps(self):
354 414 self._caps = set(self._call('capabilities').split())
355 415
356 416 def _callstream(self, cmd, _compressible=False, **args):
357 417 args = pycompat.byteskwargs(args)
358 418
359 419 req, cu, qs = makev1commandrequest(self.ui, self._requestbuilder,
360 420 self._caps, self.capable,
361 421 self._url, cmd, args)
362 422
363 423 resp = sendrequest(self.ui, self._urlopener, req)
364 424
365 # record the url we got redirected to
366 resp_url = pycompat.bytesurl(resp.geturl())
367 if resp_url.endswith(qs):
368 resp_url = resp_url[:-len(qs)]
369 if self._url.rstrip('/') != resp_url.rstrip('/'):
370 if not self.ui.quiet:
371 self.ui.warn(_('real URL is %s\n') % resp_url)
372 self._url = resp_url
373 try:
374 proto = pycompat.bytesurl(resp.getheader(r'content-type', r''))
375 except AttributeError:
376 proto = pycompat.bytesurl(resp.headers.get(r'content-type', r''))
377
378 safeurl = util.hidepassword(self._url)
379 if proto.startswith('application/hg-error'):
380 raise error.OutOfBandError(resp.read())
381 # accept old "text/plain" and "application/hg-changegroup" for now
382 if not (proto.startswith('application/mercurial-') or
383 (proto.startswith('text/plain')
384 and not resp.headers.get('content-length')) or
385 proto.startswith('application/hg-changegroup')):
386 self.ui.debug("requested URL: '%s'\n" % util.hidepassword(cu))
387 raise error.RepoError(
388 _("'%s' does not appear to be an hg repository:\n"
389 "---%%<--- (%s)\n%s\n---%%<---\n")
390 % (safeurl, proto or 'no content-type', resp.read(1024)))
391
392 if proto.startswith('application/mercurial-'):
393 try:
394 version = proto.split('-', 1)[1]
395 version_info = tuple([int(n) for n in version.split('.')])
396 except ValueError:
397 raise error.RepoError(_("'%s' sent a broken Content-Type "
398 "header (%s)") % (safeurl, proto))
399
400 # TODO consider switching to a decompression reader that uses
401 # generators.
402 if version_info == (0, 1):
403 if _compressible:
404 return util.compengines['zlib'].decompressorreader(resp)
405 return resp
406 elif version_info == (0, 2):
407 # application/mercurial-0.2 always identifies the compression
408 # engine in the payload header.
409 elen = struct.unpack('B', resp.read(1))[0]
410 ename = resp.read(elen)
411 engine = util.compengines.forwiretype(ename)
412 return engine.decompressorreader(resp)
413 else:
414 raise error.RepoError(_("'%s' uses newer protocol %s") %
415 (safeurl, version))
416
417 if _compressible:
418 return util.compengines['zlib'].decompressorreader(resp)
425 self._url, resp = parsev1commandresponse(self.ui, self._url, cu, qs,
426 resp, _compressible)
419 427
420 428 return resp
421 429
422 430 def _call(self, cmd, **args):
423 431 fp = self._callstream(cmd, **args)
424 432 try:
425 433 return fp.read()
426 434 finally:
427 435 # if using keepalive, allow connection to be reused
428 436 fp.close()
429 437
430 438 def _callpush(self, cmd, cg, **args):
431 439 # have to stream bundle to a temp file because we do not have
432 440 # http 1.1 chunked transfer.
433 441
434 442 types = self.capable('unbundle')
435 443 try:
436 444 types = types.split(',')
437 445 except AttributeError:
438 446 # servers older than d1b16a746db6 will send 'unbundle' as a
439 447 # boolean capability. They only support headerless/uncompressed
440 448 # bundles.
441 449 types = [""]
442 450 for x in types:
443 451 if x in bundle2.bundletypes:
444 452 type = x
445 453 break
446 454
447 455 tempname = bundle2.writebundle(self.ui, cg, None, type)
448 456 fp = httpconnection.httpsendfile(self.ui, tempname, "rb")
449 457 headers = {r'Content-Type': r'application/mercurial-0.1'}
450 458
451 459 try:
452 460 r = self._call(cmd, data=fp, headers=headers, **args)
453 461 vals = r.split('\n', 1)
454 462 if len(vals) < 2:
455 463 raise error.ResponseError(_("unexpected response:"), r)
456 464 return vals
457 465 except urlerr.httperror:
458 466 # Catch and re-raise these so we don't try and treat them
459 467 # like generic socket errors. They lack any values in
460 468 # .args on Python 3 which breaks our socket.error block.
461 469 raise
462 470 except socket.error as err:
463 471 if err.args[0] in (errno.ECONNRESET, errno.EPIPE):
464 472 raise error.Abort(_('push failed: %s') % err.args[1])
465 473 raise error.Abort(err.args[1])
466 474 finally:
467 475 fp.close()
468 476 os.unlink(tempname)
469 477
470 478 def _calltwowaystream(self, cmd, fp, **args):
471 479 fh = None
472 480 fp_ = None
473 481 filename = None
474 482 try:
475 483 # dump bundle to disk
476 484 fd, filename = tempfile.mkstemp(prefix="hg-bundle-", suffix=".hg")
477 485 fh = os.fdopen(fd, r"wb")
478 486 d = fp.read(4096)
479 487 while d:
480 488 fh.write(d)
481 489 d = fp.read(4096)
482 490 fh.close()
483 491 # start http push
484 492 fp_ = httpconnection.httpsendfile(self.ui, filename, "rb")
485 493 headers = {r'Content-Type': r'application/mercurial-0.1'}
486 494 return self._callstream(cmd, data=fp_, headers=headers, **args)
487 495 finally:
488 496 if fp_ is not None:
489 497 fp_.close()
490 498 if fh is not None:
491 499 fh.close()
492 500 os.unlink(filename)
493 501
494 502 def _callcompressable(self, cmd, **args):
495 503 return self._callstream(cmd, _compressible=True, **args)
496 504
497 505 def _abort(self, exception):
498 506 raise exception
499 507
500 508 # TODO implement interface for version 2 peers
501 509 class httpv2peer(object):
502 510 def __init__(self, ui, repourl, opener):
503 511 self.ui = ui
504 512
505 513 if repourl.endswith('/'):
506 514 repourl = repourl[:-1]
507 515
508 516 self.url = repourl
509 517 self._opener = opener
510 518 # This is an its own attribute to facilitate extensions overriding
511 519 # the default type.
512 520 self._requestbuilder = urlreq.request
513 521
514 522 def close(self):
515 523 pass
516 524
517 525 # TODO require to be part of a batched primitive, use futures.
518 526 def _call(self, name, **args):
519 527 """Call a wire protocol command with arguments."""
520 528
521 529 # Having this early has a side-effect of importing wireprotov2server,
522 530 # which has the side-effect of ensuring commands are registered.
523 531
524 532 # TODO modify user-agent to reflect v2.
525 533 headers = {
526 534 r'Accept': wireprotov2server.FRAMINGTYPE,
527 535 r'Content-Type': wireprotov2server.FRAMINGTYPE,
528 536 }
529 537
530 538 # TODO permissions should come from capabilities results.
531 539 permission = wireproto.commandsv2[name].permission
532 540 if permission not in ('push', 'pull'):
533 541 raise error.ProgrammingError('unknown permission type: %s' %
534 542 permission)
535 543
536 544 permission = {
537 545 'push': 'rw',
538 546 'pull': 'ro',
539 547 }[permission]
540 548
541 549 url = '%s/api/%s/%s/%s' % (self.url, wireprotov2server.HTTPV2,
542 550 permission, name)
543 551
544 552 # TODO this should be part of a generic peer for the frame-based
545 553 # protocol.
546 554 reactor = wireprotoframing.clientreactor(hasmultiplesend=False,
547 555 buffersends=True)
548 556
549 557 request, action, meta = reactor.callcommand(name, args)
550 558 assert action == 'noop'
551 559
552 560 action, meta = reactor.flushcommands()
553 561 assert action == 'sendframes'
554 562
555 563 body = b''.join(map(bytes, meta['framegen']))
556 564 req = self._requestbuilder(pycompat.strurl(url), body, headers)
557 565 req.add_unredirected_header(r'Content-Length', r'%d' % len(body))
558 566
559 567 # TODO unify this code with httppeer.
560 568 try:
561 569 res = self._opener.open(req)
562 570 except urlerr.httperror as e:
563 571 if e.code == 401:
564 572 raise error.Abort(_('authorization failed'))
565 573
566 574 raise
567 575 except httplib.HTTPException as e:
568 576 self.ui.traceback()
569 577 raise IOError(None, e)
570 578
571 579 # TODO validate response type, wrap response to handle I/O errors.
572 580 # TODO more robust frame receiver.
573 581 results = []
574 582
575 583 while True:
576 584 frame = wireprotoframing.readframe(res)
577 585 if frame is None:
578 586 break
579 587
580 588 self.ui.note(_('received %r\n') % frame)
581 589
582 590 action, meta = reactor.onframerecv(frame)
583 591
584 592 if action == 'responsedata':
585 593 if meta['cbor']:
586 594 payload = util.bytesio(meta['data'])
587 595
588 596 decoder = cbor.CBORDecoder(payload)
589 597 while payload.tell() + 1 < len(meta['data']):
590 598 results.append(decoder.decode())
591 599 else:
592 600 results.append(meta['data'])
593 601 else:
594 602 error.ProgrammingError('unhandled action: %s' % action)
595 603
596 604 return results
597 605
598 606 def makepeer(ui, path, requestbuilder=urlreq.request):
599 607 """Construct an appropriate HTTP peer instance.
600 608
601 609 ``requestbuilder`` is the type used for constructing HTTP requests.
602 610 It exists as an argument so extensions can override the default.
603 611 """
604 612 u = util.url(path)
605 613 if u.query or u.fragment:
606 614 raise error.Abort(_('unsupported URL component: "%s"') %
607 615 (u.query or u.fragment))
608 616
609 617 # urllib cannot handle URLs with embedded user or passwd.
610 618 url, authinfo = u.authinfo()
611 619 ui.debug('using %s\n' % url)
612 620
613 621 opener = urlmod.opener(ui, authinfo)
614 622
615 623 return httppeer(ui, path, url, opener, requestbuilder)
616 624
617 625 def instance(ui, path, create):
618 626 if create:
619 627 raise error.Abort(_('cannot create new http repository'))
620 628 try:
621 629 if path.startswith('https:') and not urlmod.has_https:
622 630 raise error.Abort(_('Python support for SSL and HTTPS '
623 631 'is not installed'))
624 632
625 633 inst = makepeer(ui, path)
626 634 inst._fetchcaps()
627 635
628 636 return inst
629 637 except error.RepoError as httpexception:
630 638 try:
631 639 r = statichttprepo.instance(ui, "static-" + path, create)
632 640 ui.note(_('(falling back to static-http)\n'))
633 641 return r
634 642 except error.RepoError:
635 643 raise httpexception # use the original http RepoError instead
General Comments 0
You need to be logged in to leave comments. Login now