##// END OF EJS Templates
wireprotov2: client support for following content redirects...
Gregory Szorc -
r40062:7e807b8a default
parent child Browse files
Show More
@@ -1,976 +1,978
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 weakref
17 17
18 18 from .i18n import _
19 19 from . import (
20 20 bundle2,
21 21 error,
22 22 httpconnection,
23 23 pycompat,
24 24 repository,
25 25 statichttprepo,
26 26 url as urlmod,
27 27 util,
28 28 wireprotoframing,
29 29 wireprototypes,
30 30 wireprotov1peer,
31 31 wireprotov2peer,
32 32 wireprotov2server,
33 33 )
34 34 from .utils import (
35 35 cborutil,
36 36 interfaceutil,
37 37 stringutil,
38 38 )
39 39
40 40 httplib = util.httplib
41 41 urlerr = util.urlerr
42 42 urlreq = util.urlreq
43 43
44 44 def encodevalueinheaders(value, header, limit):
45 45 """Encode a string value into multiple HTTP headers.
46 46
47 47 ``value`` will be encoded into 1 or more HTTP headers with the names
48 48 ``header-<N>`` where ``<N>`` is an integer starting at 1. Each header
49 49 name + value will be at most ``limit`` bytes long.
50 50
51 51 Returns an iterable of 2-tuples consisting of header names and
52 52 values as native strings.
53 53 """
54 54 # HTTP Headers are ASCII. Python 3 requires them to be unicodes,
55 55 # not bytes. This function always takes bytes in as arguments.
56 56 fmt = pycompat.strurl(header) + r'-%s'
57 57 # Note: it is *NOT* a bug that the last bit here is a bytestring
58 58 # and not a unicode: we're just getting the encoded length anyway,
59 59 # and using an r-string to make it portable between Python 2 and 3
60 60 # doesn't work because then the \r is a literal backslash-r
61 61 # instead of a carriage return.
62 62 valuelen = limit - len(fmt % r'000') - len(': \r\n')
63 63 result = []
64 64
65 65 n = 0
66 66 for i in pycompat.xrange(0, len(value), valuelen):
67 67 n += 1
68 68 result.append((fmt % str(n), pycompat.strurl(value[i:i + valuelen])))
69 69
70 70 return result
71 71
72 72 class _multifile(object):
73 73 def __init__(self, *fileobjs):
74 74 for f in fileobjs:
75 75 if not util.safehasattr(f, 'length'):
76 76 raise ValueError(
77 77 '_multifile only supports file objects that '
78 78 'have a length but this one does not:', type(f), f)
79 79 self._fileobjs = fileobjs
80 80 self._index = 0
81 81
82 82 @property
83 83 def length(self):
84 84 return sum(f.length for f in self._fileobjs)
85 85
86 86 def read(self, amt=None):
87 87 if amt <= 0:
88 88 return ''.join(f.read() for f in self._fileobjs)
89 89 parts = []
90 90 while amt and self._index < len(self._fileobjs):
91 91 parts.append(self._fileobjs[self._index].read(amt))
92 92 got = len(parts[-1])
93 93 if got < amt:
94 94 self._index += 1
95 95 amt -= got
96 96 return ''.join(parts)
97 97
98 98 def seek(self, offset, whence=os.SEEK_SET):
99 99 if whence != os.SEEK_SET:
100 100 raise NotImplementedError(
101 101 '_multifile does not support anything other'
102 102 ' than os.SEEK_SET for whence on seek()')
103 103 if offset != 0:
104 104 raise NotImplementedError(
105 105 '_multifile only supports seeking to start, but that '
106 106 'could be fixed if you need it')
107 107 for f in self._fileobjs:
108 108 f.seek(0)
109 109 self._index = 0
110 110
111 111 def makev1commandrequest(ui, requestbuilder, caps, capablefn,
112 112 repobaseurl, cmd, args):
113 113 """Make an HTTP request to run a command for a version 1 client.
114 114
115 115 ``caps`` is a set of known server capabilities. The value may be
116 116 None if capabilities are not yet known.
117 117
118 118 ``capablefn`` is a function to evaluate a capability.
119 119
120 120 ``cmd``, ``args``, and ``data`` define the command, its arguments, and
121 121 raw data to pass to it.
122 122 """
123 123 if cmd == 'pushkey':
124 124 args['data'] = ''
125 125 data = args.pop('data', None)
126 126 headers = args.pop('headers', {})
127 127
128 128 ui.debug("sending %s command\n" % cmd)
129 129 q = [('cmd', cmd)]
130 130 headersize = 0
131 131 # Important: don't use self.capable() here or else you end up
132 132 # with infinite recursion when trying to look up capabilities
133 133 # for the first time.
134 134 postargsok = caps is not None and 'httppostargs' in caps
135 135
136 136 # Send arguments via POST.
137 137 if postargsok and args:
138 138 strargs = urlreq.urlencode(sorted(args.items()))
139 139 if not data:
140 140 data = strargs
141 141 else:
142 142 if isinstance(data, bytes):
143 143 i = io.BytesIO(data)
144 144 i.length = len(data)
145 145 data = i
146 146 argsio = io.BytesIO(strargs)
147 147 argsio.length = len(strargs)
148 148 data = _multifile(argsio, data)
149 149 headers[r'X-HgArgs-Post'] = len(strargs)
150 150 elif args:
151 151 # Calling self.capable() can infinite loop if we are calling
152 152 # "capabilities". But that command should never accept wire
153 153 # protocol arguments. So this should never happen.
154 154 assert cmd != 'capabilities'
155 155 httpheader = capablefn('httpheader')
156 156 if httpheader:
157 157 headersize = int(httpheader.split(',', 1)[0])
158 158
159 159 # Send arguments via HTTP headers.
160 160 if headersize > 0:
161 161 # The headers can typically carry more data than the URL.
162 162 encargs = urlreq.urlencode(sorted(args.items()))
163 163 for header, value in encodevalueinheaders(encargs, 'X-HgArg',
164 164 headersize):
165 165 headers[header] = value
166 166 # Send arguments via query string (Mercurial <1.9).
167 167 else:
168 168 q += sorted(args.items())
169 169
170 170 qs = '?%s' % urlreq.urlencode(q)
171 171 cu = "%s%s" % (repobaseurl, qs)
172 172 size = 0
173 173 if util.safehasattr(data, 'length'):
174 174 size = data.length
175 175 elif data is not None:
176 176 size = len(data)
177 177 if data is not None and r'Content-Type' not in headers:
178 178 headers[r'Content-Type'] = r'application/mercurial-0.1'
179 179
180 180 # Tell the server we accept application/mercurial-0.2 and multiple
181 181 # compression formats if the server is capable of emitting those
182 182 # payloads.
183 183 # Note: Keep this set empty by default, as client advertisement of
184 184 # protocol parameters should only occur after the handshake.
185 185 protoparams = set()
186 186
187 187 mediatypes = set()
188 188 if caps is not None:
189 189 mt = capablefn('httpmediatype')
190 190 if mt:
191 191 protoparams.add('0.1')
192 192 mediatypes = set(mt.split(','))
193 193
194 194 protoparams.add('partial-pull')
195 195
196 196 if '0.2tx' in mediatypes:
197 197 protoparams.add('0.2')
198 198
199 199 if '0.2tx' in mediatypes and capablefn('compression'):
200 200 # We /could/ compare supported compression formats and prune
201 201 # non-mutually supported or error if nothing is mutually supported.
202 202 # For now, send the full list to the server and have it error.
203 203 comps = [e.wireprotosupport().name for e in
204 204 util.compengines.supportedwireengines(util.CLIENTROLE)]
205 205 protoparams.add('comp=%s' % ','.join(comps))
206 206
207 207 if protoparams:
208 208 protoheaders = encodevalueinheaders(' '.join(sorted(protoparams)),
209 209 'X-HgProto',
210 210 headersize or 1024)
211 211 for header, value in protoheaders:
212 212 headers[header] = value
213 213
214 214 varyheaders = []
215 215 for header in headers:
216 216 if header.lower().startswith(r'x-hg'):
217 217 varyheaders.append(header)
218 218
219 219 if varyheaders:
220 220 headers[r'Vary'] = r','.join(sorted(varyheaders))
221 221
222 222 req = requestbuilder(pycompat.strurl(cu), data, headers)
223 223
224 224 if data is not None:
225 225 ui.debug("sending %d bytes\n" % size)
226 226 req.add_unredirected_header(r'Content-Length', r'%d' % size)
227 227
228 228 return req, cu, qs
229 229
230 230 def _reqdata(req):
231 231 """Get request data, if any. If no data, returns None."""
232 232 if pycompat.ispy3:
233 233 return req.data
234 234 if not req.has_data():
235 235 return None
236 236 return req.get_data()
237 237
238 238 def sendrequest(ui, opener, req):
239 239 """Send a prepared HTTP request.
240 240
241 241 Returns the response object.
242 242 """
243 243 dbg = ui.debug
244 244 if (ui.debugflag
245 245 and ui.configbool('devel', 'debug.peer-request')):
246 246 line = 'devel-peer-request: %s\n'
247 247 dbg(line % '%s %s' % (pycompat.bytesurl(req.get_method()),
248 248 pycompat.bytesurl(req.get_full_url())))
249 249 hgargssize = None
250 250
251 251 for header, value in sorted(req.header_items()):
252 252 header = pycompat.bytesurl(header)
253 253 value = pycompat.bytesurl(value)
254 254 if header.startswith('X-hgarg-'):
255 255 if hgargssize is None:
256 256 hgargssize = 0
257 257 hgargssize += len(value)
258 258 else:
259 259 dbg(line % ' %s %s' % (header, value))
260 260
261 261 if hgargssize is not None:
262 262 dbg(line % ' %d bytes of commands arguments in headers'
263 263 % hgargssize)
264 264 data = _reqdata(req)
265 265 if data is not None:
266 266 length = getattr(data, 'length', None)
267 267 if length is None:
268 268 length = len(data)
269 269 dbg(line % ' %d bytes of data' % length)
270 270
271 271 start = util.timer()
272 272
273 273 res = None
274 274 try:
275 275 res = opener.open(req)
276 276 except urlerr.httperror as inst:
277 277 if inst.code == 401:
278 278 raise error.Abort(_('authorization failed'))
279 279 raise
280 280 except httplib.HTTPException as inst:
281 281 ui.debug('http error requesting %s\n' %
282 282 util.hidepassword(req.get_full_url()))
283 283 ui.traceback()
284 284 raise IOError(None, inst)
285 285 finally:
286 286 if ui.debugflag and ui.configbool('devel', 'debug.peer-request'):
287 287 code = res.code if res else -1
288 288 dbg(line % ' finished in %.4f seconds (%d)'
289 289 % (util.timer() - start, code))
290 290
291 291 # Insert error handlers for common I/O failures.
292 292 urlmod.wrapresponse(res)
293 293
294 294 return res
295 295
296 296 class RedirectedRepoError(error.RepoError):
297 297 def __init__(self, msg, respurl):
298 298 super(RedirectedRepoError, self).__init__(msg)
299 299 self.respurl = respurl
300 300
301 301 def parsev1commandresponse(ui, baseurl, requrl, qs, resp, compressible,
302 302 allowcbor=False):
303 303 # record the url we got redirected to
304 304 redirected = False
305 305 respurl = pycompat.bytesurl(resp.geturl())
306 306 if respurl.endswith(qs):
307 307 respurl = respurl[:-len(qs)]
308 308 qsdropped = False
309 309 else:
310 310 qsdropped = True
311 311
312 312 if baseurl.rstrip('/') != respurl.rstrip('/'):
313 313 redirected = True
314 314 if not ui.quiet:
315 315 ui.warn(_('real URL is %s\n') % respurl)
316 316
317 317 try:
318 318 proto = pycompat.bytesurl(resp.getheader(r'content-type', r''))
319 319 except AttributeError:
320 320 proto = pycompat.bytesurl(resp.headers.get(r'content-type', r''))
321 321
322 322 safeurl = util.hidepassword(baseurl)
323 323 if proto.startswith('application/hg-error'):
324 324 raise error.OutOfBandError(resp.read())
325 325
326 326 # Pre 1.0 versions of Mercurial used text/plain and
327 327 # application/hg-changegroup. We don't support such old servers.
328 328 if not proto.startswith('application/mercurial-'):
329 329 ui.debug("requested URL: '%s'\n" % util.hidepassword(requrl))
330 330 msg = _("'%s' does not appear to be an hg repository:\n"
331 331 "---%%<--- (%s)\n%s\n---%%<---\n") % (
332 332 safeurl, proto or 'no content-type', resp.read(1024))
333 333
334 334 # Some servers may strip the query string from the redirect. We
335 335 # raise a special error type so callers can react to this specially.
336 336 if redirected and qsdropped:
337 337 raise RedirectedRepoError(msg, respurl)
338 338 else:
339 339 raise error.RepoError(msg)
340 340
341 341 try:
342 342 subtype = proto.split('-', 1)[1]
343 343
344 344 # Unless we end up supporting CBOR in the legacy wire protocol,
345 345 # this should ONLY be encountered for the initial capabilities
346 346 # request during handshake.
347 347 if subtype == 'cbor':
348 348 if allowcbor:
349 349 return respurl, proto, resp
350 350 else:
351 351 raise error.RepoError(_('unexpected CBOR response from '
352 352 'server'))
353 353
354 354 version_info = tuple([int(n) for n in subtype.split('.')])
355 355 except ValueError:
356 356 raise error.RepoError(_("'%s' sent a broken Content-Type "
357 357 "header (%s)") % (safeurl, proto))
358 358
359 359 # TODO consider switching to a decompression reader that uses
360 360 # generators.
361 361 if version_info == (0, 1):
362 362 if compressible:
363 363 resp = util.compengines['zlib'].decompressorreader(resp)
364 364
365 365 elif version_info == (0, 2):
366 366 # application/mercurial-0.2 always identifies the compression
367 367 # engine in the payload header.
368 368 elen = struct.unpack('B', util.readexactly(resp, 1))[0]
369 369 ename = util.readexactly(resp, elen)
370 370 engine = util.compengines.forwiretype(ename)
371 371
372 372 resp = engine.decompressorreader(resp)
373 373 else:
374 374 raise error.RepoError(_("'%s' uses newer protocol %s") %
375 375 (safeurl, subtype))
376 376
377 377 return respurl, proto, resp
378 378
379 379 class httppeer(wireprotov1peer.wirepeer):
380 380 def __init__(self, ui, path, url, opener, requestbuilder, caps):
381 381 self.ui = ui
382 382 self._path = path
383 383 self._url = url
384 384 self._caps = caps
385 385 self._urlopener = opener
386 386 self._requestbuilder = requestbuilder
387 387
388 388 def __del__(self):
389 389 for h in self._urlopener.handlers:
390 390 h.close()
391 391 getattr(h, "close_all", lambda: None)()
392 392
393 393 # Begin of ipeerconnection interface.
394 394
395 395 def url(self):
396 396 return self._path
397 397
398 398 def local(self):
399 399 return None
400 400
401 401 def peer(self):
402 402 return self
403 403
404 404 def canpush(self):
405 405 return True
406 406
407 407 def close(self):
408 408 pass
409 409
410 410 # End of ipeerconnection interface.
411 411
412 412 # Begin of ipeercommands interface.
413 413
414 414 def capabilities(self):
415 415 return self._caps
416 416
417 417 # End of ipeercommands interface.
418 418
419 419 def _callstream(self, cmd, _compressible=False, **args):
420 420 args = pycompat.byteskwargs(args)
421 421
422 422 req, cu, qs = makev1commandrequest(self.ui, self._requestbuilder,
423 423 self._caps, self.capable,
424 424 self._url, cmd, args)
425 425
426 426 resp = sendrequest(self.ui, self._urlopener, req)
427 427
428 428 self._url, ct, resp = parsev1commandresponse(self.ui, self._url, cu, qs,
429 429 resp, _compressible)
430 430
431 431 return resp
432 432
433 433 def _call(self, cmd, **args):
434 434 fp = self._callstream(cmd, **args)
435 435 try:
436 436 return fp.read()
437 437 finally:
438 438 # if using keepalive, allow connection to be reused
439 439 fp.close()
440 440
441 441 def _callpush(self, cmd, cg, **args):
442 442 # have to stream bundle to a temp file because we do not have
443 443 # http 1.1 chunked transfer.
444 444
445 445 types = self.capable('unbundle')
446 446 try:
447 447 types = types.split(',')
448 448 except AttributeError:
449 449 # servers older than d1b16a746db6 will send 'unbundle' as a
450 450 # boolean capability. They only support headerless/uncompressed
451 451 # bundles.
452 452 types = [""]
453 453 for x in types:
454 454 if x in bundle2.bundletypes:
455 455 type = x
456 456 break
457 457
458 458 tempname = bundle2.writebundle(self.ui, cg, None, type)
459 459 fp = httpconnection.httpsendfile(self.ui, tempname, "rb")
460 460 headers = {r'Content-Type': r'application/mercurial-0.1'}
461 461
462 462 try:
463 463 r = self._call(cmd, data=fp, headers=headers, **args)
464 464 vals = r.split('\n', 1)
465 465 if len(vals) < 2:
466 466 raise error.ResponseError(_("unexpected response:"), r)
467 467 return vals
468 468 except urlerr.httperror:
469 469 # Catch and re-raise these so we don't try and treat them
470 470 # like generic socket errors. They lack any values in
471 471 # .args on Python 3 which breaks our socket.error block.
472 472 raise
473 473 except socket.error as err:
474 474 if err.args[0] in (errno.ECONNRESET, errno.EPIPE):
475 475 raise error.Abort(_('push failed: %s') % err.args[1])
476 476 raise error.Abort(err.args[1])
477 477 finally:
478 478 fp.close()
479 479 os.unlink(tempname)
480 480
481 481 def _calltwowaystream(self, cmd, fp, **args):
482 482 fh = None
483 483 fp_ = None
484 484 filename = None
485 485 try:
486 486 # dump bundle to disk
487 487 fd, filename = pycompat.mkstemp(prefix="hg-bundle-", suffix=".hg")
488 488 fh = os.fdopen(fd, r"wb")
489 489 d = fp.read(4096)
490 490 while d:
491 491 fh.write(d)
492 492 d = fp.read(4096)
493 493 fh.close()
494 494 # start http push
495 495 fp_ = httpconnection.httpsendfile(self.ui, filename, "rb")
496 496 headers = {r'Content-Type': r'application/mercurial-0.1'}
497 497 return self._callstream(cmd, data=fp_, headers=headers, **args)
498 498 finally:
499 499 if fp_ is not None:
500 500 fp_.close()
501 501 if fh is not None:
502 502 fh.close()
503 503 os.unlink(filename)
504 504
505 505 def _callcompressable(self, cmd, **args):
506 506 return self._callstream(cmd, _compressible=True, **args)
507 507
508 508 def _abort(self, exception):
509 509 raise exception
510 510
511 511 def sendv2request(ui, opener, requestbuilder, apiurl, permission, requests,
512 512 redirect):
513 513 reactor = wireprotoframing.clientreactor(hasmultiplesend=False,
514 514 buffersends=True)
515 515
516 handler = wireprotov2peer.clienthandler(ui, reactor)
516 handler = wireprotov2peer.clienthandler(ui, reactor,
517 opener=opener,
518 requestbuilder=requestbuilder)
517 519
518 520 url = '%s/%s' % (apiurl, permission)
519 521
520 522 if len(requests) > 1:
521 523 url += '/multirequest'
522 524 else:
523 525 url += '/%s' % requests[0][0]
524 526
525 527 ui.debug('sending %d commands\n' % len(requests))
526 528 for command, args, f in requests:
527 529 ui.debug('sending command %s: %s\n' % (
528 530 command, stringutil.pprint(args, indent=2)))
529 531 assert not list(handler.callcommand(command, args, f,
530 532 redirect=redirect))
531 533
532 534 # TODO stream this.
533 535 body = b''.join(map(bytes, handler.flushcommands()))
534 536
535 537 # TODO modify user-agent to reflect v2
536 538 headers = {
537 539 r'Accept': wireprotov2server.FRAMINGTYPE,
538 540 r'Content-Type': wireprotov2server.FRAMINGTYPE,
539 541 }
540 542
541 543 req = requestbuilder(pycompat.strurl(url), body, headers)
542 544 req.add_unredirected_header(r'Content-Length', r'%d' % len(body))
543 545
544 546 try:
545 547 res = opener.open(req)
546 548 except urlerr.httperror as e:
547 549 if e.code == 401:
548 550 raise error.Abort(_('authorization failed'))
549 551
550 552 raise
551 553 except httplib.HTTPException as e:
552 554 ui.traceback()
553 555 raise IOError(None, e)
554 556
555 557 return handler, res
556 558
557 559 class queuedcommandfuture(pycompat.futures.Future):
558 560 """Wraps result() on command futures to trigger submission on call."""
559 561
560 562 def result(self, timeout=None):
561 563 if self.done():
562 564 return pycompat.futures.Future.result(self, timeout)
563 565
564 566 self._peerexecutor.sendcommands()
565 567
566 568 # sendcommands() will restore the original __class__ and self.result
567 569 # will resolve to Future.result.
568 570 return self.result(timeout)
569 571
570 572 @interfaceutil.implementer(repository.ipeercommandexecutor)
571 573 class httpv2executor(object):
572 574 def __init__(self, ui, opener, requestbuilder, apiurl, descriptor,
573 575 redirect):
574 576 self._ui = ui
575 577 self._opener = opener
576 578 self._requestbuilder = requestbuilder
577 579 self._apiurl = apiurl
578 580 self._descriptor = descriptor
579 581 self._redirect = redirect
580 582 self._sent = False
581 583 self._closed = False
582 584 self._neededpermissions = set()
583 585 self._calls = []
584 586 self._futures = weakref.WeakSet()
585 587 self._responseexecutor = None
586 588 self._responsef = None
587 589
588 590 def __enter__(self):
589 591 return self
590 592
591 593 def __exit__(self, exctype, excvalue, exctb):
592 594 self.close()
593 595
594 596 def callcommand(self, command, args):
595 597 if self._sent:
596 598 raise error.ProgrammingError('callcommand() cannot be used after '
597 599 'commands are sent')
598 600
599 601 if self._closed:
600 602 raise error.ProgrammingError('callcommand() cannot be used after '
601 603 'close()')
602 604
603 605 # The service advertises which commands are available. So if we attempt
604 606 # to call an unknown command or pass an unknown argument, we can screen
605 607 # for this.
606 608 if command not in self._descriptor['commands']:
607 609 raise error.ProgrammingError(
608 610 'wire protocol command %s is not available' % command)
609 611
610 612 cmdinfo = self._descriptor['commands'][command]
611 613 unknownargs = set(args.keys()) - set(cmdinfo.get('args', {}))
612 614
613 615 if unknownargs:
614 616 raise error.ProgrammingError(
615 617 'wire protocol command %s does not accept argument: %s' % (
616 618 command, ', '.join(sorted(unknownargs))))
617 619
618 620 self._neededpermissions |= set(cmdinfo['permissions'])
619 621
620 622 # TODO we /could/ also validate types here, since the API descriptor
621 623 # includes types...
622 624
623 625 f = pycompat.futures.Future()
624 626
625 627 # Monkeypatch it so result() triggers sendcommands(), otherwise result()
626 628 # could deadlock.
627 629 f.__class__ = queuedcommandfuture
628 630 f._peerexecutor = self
629 631
630 632 self._futures.add(f)
631 633 self._calls.append((command, args, f))
632 634
633 635 return f
634 636
635 637 def sendcommands(self):
636 638 if self._sent:
637 639 return
638 640
639 641 if not self._calls:
640 642 return
641 643
642 644 self._sent = True
643 645
644 646 # Unhack any future types so caller sees a clean type and so we
645 647 # break reference cycle.
646 648 for f in self._futures:
647 649 if isinstance(f, queuedcommandfuture):
648 650 f.__class__ = pycompat.futures.Future
649 651 f._peerexecutor = None
650 652
651 653 # Mark the future as running and filter out cancelled futures.
652 654 calls = [(command, args, f)
653 655 for command, args, f in self._calls
654 656 if f.set_running_or_notify_cancel()]
655 657
656 658 # Clear out references, prevent improper object usage.
657 659 self._calls = None
658 660
659 661 if not calls:
660 662 return
661 663
662 664 permissions = set(self._neededpermissions)
663 665
664 666 if 'push' in permissions and 'pull' in permissions:
665 667 permissions.remove('pull')
666 668
667 669 if len(permissions) > 1:
668 670 raise error.RepoError(_('cannot make request requiring multiple '
669 671 'permissions: %s') %
670 672 _(', ').join(sorted(permissions)))
671 673
672 674 permission = {
673 675 'push': 'rw',
674 676 'pull': 'ro',
675 677 }[permissions.pop()]
676 678
677 679 handler, resp = sendv2request(
678 680 self._ui, self._opener, self._requestbuilder, self._apiurl,
679 681 permission, calls, self._redirect)
680 682
681 683 # TODO we probably want to validate the HTTP code, media type, etc.
682 684
683 685 self._responseexecutor = pycompat.futures.ThreadPoolExecutor(1)
684 686 self._responsef = self._responseexecutor.submit(self._handleresponse,
685 687 handler, resp)
686 688
687 689 def close(self):
688 690 if self._closed:
689 691 return
690 692
691 693 self.sendcommands()
692 694
693 695 self._closed = True
694 696
695 697 if not self._responsef:
696 698 return
697 699
698 700 # TODO ^C here may not result in immediate program termination.
699 701
700 702 try:
701 703 self._responsef.result()
702 704 finally:
703 705 self._responseexecutor.shutdown(wait=True)
704 706 self._responsef = None
705 707 self._responseexecutor = None
706 708
707 709 # If any of our futures are still in progress, mark them as
708 710 # errored, otherwise a result() could wait indefinitely.
709 711 for f in self._futures:
710 712 if not f.done():
711 713 f.set_exception(error.ResponseError(
712 714 _('unfulfilled command response')))
713 715
714 716 self._futures = None
715 717
716 718 def _handleresponse(self, handler, resp):
717 719 # Called in a thread to read the response.
718 720
719 721 while handler.readdata(resp):
720 722 pass
721 723
722 724 # TODO implement interface for version 2 peers
723 725 @interfaceutil.implementer(repository.ipeerconnection,
724 726 repository.ipeercapabilities,
725 727 repository.ipeerrequests)
726 728 class httpv2peer(object):
727 729 def __init__(self, ui, repourl, apipath, opener, requestbuilder,
728 730 apidescriptor):
729 731 self.ui = ui
730 732
731 733 if repourl.endswith('/'):
732 734 repourl = repourl[:-1]
733 735
734 736 self._url = repourl
735 737 self._apipath = apipath
736 738 self._apiurl = '%s/%s' % (repourl, apipath)
737 739 self._opener = opener
738 740 self._requestbuilder = requestbuilder
739 741 self._descriptor = apidescriptor
740 742
741 743 self._redirect = wireprotov2peer.supportedredirects(ui, apidescriptor)
742 744
743 745 # Start of ipeerconnection.
744 746
745 747 def url(self):
746 748 return self._url
747 749
748 750 def local(self):
749 751 return None
750 752
751 753 def peer(self):
752 754 return self
753 755
754 756 def canpush(self):
755 757 # TODO change once implemented.
756 758 return False
757 759
758 760 def close(self):
759 761 pass
760 762
761 763 # End of ipeerconnection.
762 764
763 765 # Start of ipeercapabilities.
764 766
765 767 def capable(self, name):
766 768 # The capabilities used internally historically map to capabilities
767 769 # advertised from the "capabilities" wire protocol command. However,
768 770 # version 2 of that command works differently.
769 771
770 772 # Maps to commands that are available.
771 773 if name in ('branchmap', 'getbundle', 'known', 'lookup', 'pushkey'):
772 774 return True
773 775
774 776 # Other concepts.
775 777 if name in ('bundle2'):
776 778 return True
777 779
778 780 # Alias command-* to presence of command of that name.
779 781 if name.startswith('command-'):
780 782 return name[len('command-'):] in self._descriptor['commands']
781 783
782 784 return False
783 785
784 786 def requirecap(self, name, purpose):
785 787 if self.capable(name):
786 788 return
787 789
788 790 raise error.CapabilityError(
789 791 _('cannot %s; client or remote repository does not support the %r '
790 792 'capability') % (purpose, name))
791 793
792 794 # End of ipeercapabilities.
793 795
794 796 def _call(self, name, **args):
795 797 with self.commandexecutor() as e:
796 798 return e.callcommand(name, args).result()
797 799
798 800 def commandexecutor(self):
799 801 return httpv2executor(self.ui, self._opener, self._requestbuilder,
800 802 self._apiurl, self._descriptor, self._redirect)
801 803
802 804 # Registry of API service names to metadata about peers that handle it.
803 805 #
804 806 # The following keys are meaningful:
805 807 #
806 808 # init
807 809 # Callable receiving (ui, repourl, servicepath, opener, requestbuilder,
808 810 # apidescriptor) to create a peer.
809 811 #
810 812 # priority
811 813 # Integer priority for the service. If we could choose from multiple
812 814 # services, we choose the one with the highest priority.
813 815 API_PEERS = {
814 816 wireprototypes.HTTP_WIREPROTO_V2: {
815 817 'init': httpv2peer,
816 818 'priority': 50,
817 819 },
818 820 }
819 821
820 822 def performhandshake(ui, url, opener, requestbuilder):
821 823 # The handshake is a request to the capabilities command.
822 824
823 825 caps = None
824 826 def capable(x):
825 827 raise error.ProgrammingError('should not be called')
826 828
827 829 args = {}
828 830
829 831 # The client advertises support for newer protocols by adding an
830 832 # X-HgUpgrade-* header with a list of supported APIs and an
831 833 # X-HgProto-* header advertising which serializing formats it supports.
832 834 # We only support the HTTP version 2 transport and CBOR responses for
833 835 # now.
834 836 advertisev2 = ui.configbool('experimental', 'httppeer.advertise-v2')
835 837
836 838 if advertisev2:
837 839 args['headers'] = {
838 840 r'X-HgProto-1': r'cbor',
839 841 }
840 842
841 843 args['headers'].update(
842 844 encodevalueinheaders(' '.join(sorted(API_PEERS)),
843 845 'X-HgUpgrade',
844 846 # We don't know the header limit this early.
845 847 # So make it small.
846 848 1024))
847 849
848 850 req, requrl, qs = makev1commandrequest(ui, requestbuilder, caps,
849 851 capable, url, 'capabilities',
850 852 args)
851 853 resp = sendrequest(ui, opener, req)
852 854
853 855 # The server may redirect us to the repo root, stripping the
854 856 # ?cmd=capabilities query string from the URL. The server would likely
855 857 # return HTML in this case and ``parsev1commandresponse()`` would raise.
856 858 # We catch this special case and re-issue the capabilities request against
857 859 # the new URL.
858 860 #
859 861 # We should ideally not do this, as a redirect that drops the query
860 862 # string from the URL is arguably a server bug. (Garbage in, garbage out).
861 863 # However, Mercurial clients for several years appeared to handle this
862 864 # issue without behavior degradation. And according to issue 5860, it may
863 865 # be a longstanding bug in some server implementations. So we allow a
864 866 # redirect that drops the query string to "just work."
865 867 try:
866 868 respurl, ct, resp = parsev1commandresponse(ui, url, requrl, qs, resp,
867 869 compressible=False,
868 870 allowcbor=advertisev2)
869 871 except RedirectedRepoError as e:
870 872 req, requrl, qs = makev1commandrequest(ui, requestbuilder, caps,
871 873 capable, e.respurl,
872 874 'capabilities', args)
873 875 resp = sendrequest(ui, opener, req)
874 876 respurl, ct, resp = parsev1commandresponse(ui, url, requrl, qs, resp,
875 877 compressible=False,
876 878 allowcbor=advertisev2)
877 879
878 880 try:
879 881 rawdata = resp.read()
880 882 finally:
881 883 resp.close()
882 884
883 885 if not ct.startswith('application/mercurial-'):
884 886 raise error.ProgrammingError('unexpected content-type: %s' % ct)
885 887
886 888 if advertisev2:
887 889 if ct == 'application/mercurial-cbor':
888 890 try:
889 891 info = cborutil.decodeall(rawdata)[0]
890 892 except cborutil.CBORDecodeError:
891 893 raise error.Abort(_('error decoding CBOR from remote server'),
892 894 hint=_('try again and consider contacting '
893 895 'the server operator'))
894 896
895 897 # We got a legacy response. That's fine.
896 898 elif ct in ('application/mercurial-0.1', 'application/mercurial-0.2'):
897 899 info = {
898 900 'v1capabilities': set(rawdata.split())
899 901 }
900 902
901 903 else:
902 904 raise error.RepoError(
903 905 _('unexpected response type from server: %s') % ct)
904 906 else:
905 907 info = {
906 908 'v1capabilities': set(rawdata.split())
907 909 }
908 910
909 911 return respurl, info
910 912
911 913 def makepeer(ui, path, opener=None, requestbuilder=urlreq.request):
912 914 """Construct an appropriate HTTP peer instance.
913 915
914 916 ``opener`` is an ``url.opener`` that should be used to establish
915 917 connections, perform HTTP requests.
916 918
917 919 ``requestbuilder`` is the type used for constructing HTTP requests.
918 920 It exists as an argument so extensions can override the default.
919 921 """
920 922 u = util.url(path)
921 923 if u.query or u.fragment:
922 924 raise error.Abort(_('unsupported URL component: "%s"') %
923 925 (u.query or u.fragment))
924 926
925 927 # urllib cannot handle URLs with embedded user or passwd.
926 928 url, authinfo = u.authinfo()
927 929 ui.debug('using %s\n' % url)
928 930
929 931 opener = opener or urlmod.opener(ui, authinfo)
930 932
931 933 respurl, info = performhandshake(ui, url, opener, requestbuilder)
932 934
933 935 # Given the intersection of APIs that both we and the server support,
934 936 # sort by their advertised priority and pick the first one.
935 937 #
936 938 # TODO consider making this request-based and interface driven. For
937 939 # example, the caller could say "I want a peer that does X." It's quite
938 940 # possible that not all peers would do that. Since we know the service
939 941 # capabilities, we could filter out services not meeting the
940 942 # requirements. Possibly by consulting the interfaces defined by the
941 943 # peer type.
942 944 apipeerchoices = set(info.get('apis', {}).keys()) & set(API_PEERS.keys())
943 945
944 946 preferredchoices = sorted(apipeerchoices,
945 947 key=lambda x: API_PEERS[x]['priority'],
946 948 reverse=True)
947 949
948 950 for service in preferredchoices:
949 951 apipath = '%s/%s' % (info['apibase'].rstrip('/'), service)
950 952
951 953 return API_PEERS[service]['init'](ui, respurl, apipath, opener,
952 954 requestbuilder,
953 955 info['apis'][service])
954 956
955 957 # Failed to construct an API peer. Fall back to legacy.
956 958 return httppeer(ui, path, respurl, opener, requestbuilder,
957 959 info['v1capabilities'])
958 960
959 961 def instance(ui, path, create, intents=None, createopts=None):
960 962 if create:
961 963 raise error.Abort(_('cannot create new http repository'))
962 964 try:
963 965 if path.startswith('https:') and not urlmod.has_https:
964 966 raise error.Abort(_('Python support for SSL and HTTPS '
965 967 'is not installed'))
966 968
967 969 inst = makepeer(ui, path)
968 970
969 971 return inst
970 972 except error.RepoError as httpexception:
971 973 try:
972 974 r = statichttprepo.instance(ui, "static-" + path, create)
973 975 ui.note(_('(falling back to static-http)\n'))
974 976 return r
975 977 except error.RepoError:
976 978 raise httpexception # use the original http RepoError instead
@@ -1,372 +1,498
1 1 # wireprotov2peer.py - client side code for wire protocol version 2
2 2 #
3 3 # Copyright 2018 Gregory Szorc <gregory.szorc@gmail.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 import threading
11 11
12 12 from .i18n import _
13 13 from . import (
14 14 encoding,
15 15 error,
16 pycompat,
16 17 sslutil,
18 url as urlmod,
19 util,
17 20 wireprotoframing,
21 wireprototypes,
18 22 )
19 23 from .utils import (
20 24 cborutil,
21 25 )
22 26
23 27 def formatrichmessage(atoms):
24 28 """Format an encoded message from the framing protocol."""
25 29
26 30 chunks = []
27 31
28 32 for atom in atoms:
29 33 msg = _(atom[b'msg'])
30 34
31 35 if b'args' in atom:
32 36 msg = msg % tuple(atom[b'args'])
33 37
34 38 chunks.append(msg)
35 39
36 40 return b''.join(chunks)
37 41
38 42 SUPPORTED_REDIRECT_PROTOCOLS = {
39 43 b'http',
40 44 b'https',
41 45 }
42 46
43 47 SUPPORTED_CONTENT_HASHES = {
44 48 b'sha1',
45 49 b'sha256',
46 50 }
47 51
48 52 def redirecttargetsupported(ui, target):
49 53 """Determine whether a redirect target entry is supported.
50 54
51 55 ``target`` should come from the capabilities data structure emitted by
52 56 the server.
53 57 """
54 58 if target.get(b'protocol') not in SUPPORTED_REDIRECT_PROTOCOLS:
55 59 ui.note(_('(remote redirect target %s uses unsupported protocol: %s)\n')
56 60 % (target[b'name'], target.get(b'protocol', b'')))
57 61 return False
58 62
59 63 if target.get(b'snirequired') and not sslutil.hassni:
60 64 ui.note(_('(redirect target %s requires SNI, which is unsupported)\n') %
61 65 target[b'name'])
62 66 return False
63 67
64 68 if b'tlsversions' in target:
65 69 tlsversions = set(target[b'tlsversions'])
66 70 supported = set()
67 71
68 72 for v in sslutil.supportedprotocols:
69 73 assert v.startswith(b'tls')
70 74 supported.add(v[3:])
71 75
72 76 if not tlsversions & supported:
73 77 ui.note(_('(remote redirect target %s requires unsupported TLS '
74 78 'versions: %s)\n') % (
75 79 target[b'name'], b', '.join(sorted(tlsversions))))
76 80 return False
77 81
78 82 ui.note(_('(remote redirect target %s is compatible)\n') % target[b'name'])
79 83
80 84 return True
81 85
82 86 def supportedredirects(ui, apidescriptor):
83 87 """Resolve the "redirect" command request key given an API descriptor.
84 88
85 89 Given an API descriptor returned by the server, returns a data structure
86 90 that can be used in hte "redirect" field of command requests to advertise
87 91 support for compatible redirect targets.
88 92
89 93 Returns None if no redirect targets are remotely advertised or if none are
90 94 supported.
91 95 """
92 96 if not apidescriptor or b'redirect' not in apidescriptor:
93 97 return None
94 98
95 99 targets = [t[b'name'] for t in apidescriptor[b'redirect'][b'targets']
96 100 if redirecttargetsupported(ui, t)]
97 101
98 102 hashes = [h for h in apidescriptor[b'redirect'][b'hashes']
99 103 if h in SUPPORTED_CONTENT_HASHES]
100 104
101 105 return {
102 106 b'targets': targets,
103 107 b'hashes': hashes,
104 108 }
105 109
106 110 class commandresponse(object):
107 111 """Represents the response to a command request.
108 112
109 113 Instances track the state of the command and hold its results.
110 114
111 115 An external entity is required to update the state of the object when
112 116 events occur.
113 117 """
114 118
115 def __init__(self, requestid, command):
119 def __init__(self, requestid, command, fromredirect=False):
116 120 self.requestid = requestid
117 121 self.command = command
122 self.fromredirect = fromredirect
118 123
119 124 # Whether all remote input related to this command has been
120 125 # received.
121 126 self._inputcomplete = False
122 127
123 128 # We have a lock that is acquired when important object state is
124 129 # mutated. This is to prevent race conditions between 1 thread
125 130 # sending us new data and another consuming it.
126 131 self._lock = threading.RLock()
127 132
128 133 # An event is set when state of the object changes. This event
129 134 # is waited on by the generator emitting objects.
130 135 self._serviceable = threading.Event()
131 136
132 137 self._pendingevents = []
133 138 self._decoder = cborutil.bufferingdecoder()
134 139 self._seeninitial = False
140 self._redirect = None
135 141
136 142 def _oninputcomplete(self):
137 143 with self._lock:
138 144 self._inputcomplete = True
139 145 self._serviceable.set()
140 146
141 147 def _onresponsedata(self, data):
142 148 available, readcount, wanted = self._decoder.decode(data)
143 149
144 150 if not available:
145 151 return
146 152
147 153 with self._lock:
148 154 for o in self._decoder.getavailable():
149 if not self._seeninitial:
155 if not self._seeninitial and not self.fromredirect:
150 156 self._handleinitial(o)
151 157 continue
152 158
159 # We should never see an object after a content redirect,
160 # as the spec says the main status object containing the
161 # content redirect is the only object in the stream. Fail
162 # if we see a misbehaving server.
163 if self._redirect:
164 raise error.Abort(_('received unexpected response data '
165 'after content redirect; the remote is '
166 'buggy'))
167
153 168 self._pendingevents.append(o)
154 169
155 170 self._serviceable.set()
156 171
157 172 def _handleinitial(self, o):
158 173 self._seeninitial = True
159 174 if o[b'status'] == b'ok':
160 175 return
161 176
162 177 elif o[b'status'] == b'redirect':
163 raise error.Abort(_('redirect responses not yet supported'))
178 l = o[b'location']
179 self._redirect = wireprototypes.alternatelocationresponse(
180 url=l[b'url'],
181 mediatype=l[b'mediatype'],
182 size=l.get(b'size'),
183 fullhashes=l.get(b'fullhashes'),
184 fullhashseed=l.get(b'fullhashseed'),
185 serverdercerts=l.get(b'serverdercerts'),
186 servercadercerts=l.get(b'servercadercerts'))
187 return
164 188
165 189 atoms = [{'msg': o[b'error'][b'message']}]
166 190 if b'args' in o[b'error']:
167 191 atoms[0]['args'] = o[b'error'][b'args']
168 192
169 193 raise error.RepoError(formatrichmessage(atoms))
170 194
171 195 def objects(self):
172 196 """Obtained decoded objects from this response.
173 197
174 198 This is a generator of data structures that were decoded from the
175 199 command response.
176 200
177 201 Obtaining the next member of the generator may block due to waiting
178 202 on external data to become available.
179 203
180 204 If the server encountered an error in the middle of serving the data
181 205 or if another error occurred, an exception may be raised when
182 206 advancing the generator.
183 207 """
184 208 while True:
185 209 # TODO this can infinite loop if self._inputcomplete is never
186 210 # set. We likely want to tie the lifetime of this object/state
187 211 # to that of the background thread receiving frames and updating
188 212 # our state.
189 213 self._serviceable.wait(1.0)
190 214
191 215 with self._lock:
192 216 self._serviceable.clear()
193 217
194 218 # Make copies because objects could be mutated during
195 219 # iteration.
196 220 stop = self._inputcomplete
197 221 pending = list(self._pendingevents)
198 222 self._pendingevents[:] = []
199 223
200 224 for o in pending:
201 225 yield o
202 226
203 227 if stop:
204 228 break
205 229
206 230 class clienthandler(object):
207 231 """Object to handle higher-level client activities.
208 232
209 233 The ``clientreactor`` is used to hold low-level state about the frame-based
210 234 protocol, such as which requests and streams are active. This type is used
211 235 for higher-level operations, such as reading frames from a socket, exposing
212 236 and managing a higher-level primitive for representing command responses,
213 237 etc. This class is what peers should probably use to bridge wire activity
214 238 with the higher-level peer API.
215 239 """
216 240
217 def __init__(self, ui, clientreactor):
241 def __init__(self, ui, clientreactor, opener=None,
242 requestbuilder=util.urlreq.request):
218 243 self._ui = ui
219 244 self._reactor = clientreactor
220 245 self._requests = {}
221 246 self._futures = {}
222 247 self._responses = {}
248 self._redirects = []
223 249 self._frameseof = False
250 self._opener = opener or urlmod.opener(ui)
251 self._requestbuilder = requestbuilder
224 252
225 253 def callcommand(self, command, args, f, redirect=None):
226 254 """Register a request to call a command.
227 255
228 256 Returns an iterable of frames that should be sent over the wire.
229 257 """
230 258 request, action, meta = self._reactor.callcommand(command, args,
231 259 redirect=redirect)
232 260
233 261 if action != 'noop':
234 262 raise error.ProgrammingError('%s not yet supported' % action)
235 263
236 264 rid = request.requestid
237 265 self._requests[rid] = request
238 266 self._futures[rid] = f
239 267 # TODO we need some kind of lifetime on response instances otherwise
240 268 # objects() may deadlock.
241 269 self._responses[rid] = commandresponse(rid, command)
242 270
243 271 return iter(())
244 272
245 273 def flushcommands(self):
246 274 """Flush all queued commands.
247 275
248 276 Returns an iterable of frames that should be sent over the wire.
249 277 """
250 278 action, meta = self._reactor.flushcommands()
251 279
252 280 if action != 'sendframes':
253 281 raise error.ProgrammingError('%s not yet supported' % action)
254 282
255 283 return meta['framegen']
256 284
257 285 def readdata(self, framefh):
258 286 """Attempt to read data and do work.
259 287
260 288 Returns None if no data was read. Presumably this means we're
261 289 done with all read I/O.
262 290 """
263 291 if not self._frameseof:
264 292 frame = wireprotoframing.readframe(framefh)
265 293 if frame is None:
266 294 # TODO tell reactor?
267 295 self._frameseof = True
268 296 else:
269 297 self._ui.note(_('received %r\n') % frame)
270 298 self._processframe(frame)
271 299
272 if self._frameseof:
300 # Also try to read the first redirect.
301 if self._redirects:
302 if not self._processredirect(*self._redirects[0]):
303 self._redirects.pop(0)
304
305 if self._frameseof and not self._redirects:
273 306 return None
274 307
275 308 return True
276 309
277 310 def _processframe(self, frame):
278 311 """Process a single read frame."""
279 312
280 313 action, meta = self._reactor.onframerecv(frame)
281 314
282 315 if action == 'error':
283 316 e = error.RepoError(meta['message'])
284 317
285 318 if frame.requestid in self._responses:
286 319 self._responses[frame.requestid]._oninputcomplete()
287 320
288 321 if frame.requestid in self._futures:
289 322 self._futures[frame.requestid].set_exception(e)
290 323 del self._futures[frame.requestid]
291 324 else:
292 325 raise e
293 326
294 327 return
295 328
296 329 if frame.requestid not in self._requests:
297 330 raise error.ProgrammingError(
298 331 'received frame for unknown request; this is either a bug in '
299 332 'the clientreactor not screening for this or this instance was '
300 333 'never told about this request: %r' % frame)
301 334
302 335 response = self._responses[frame.requestid]
303 336
304 337 if action == 'responsedata':
305 338 # Any failures processing this frame should bubble up to the
306 339 # future tracking the request.
307 340 try:
308 341 self._processresponsedata(frame, meta, response)
309 342 except BaseException as e:
310 343 self._futures[frame.requestid].set_exception(e)
311 344 del self._futures[frame.requestid]
312 345 response._oninputcomplete()
313 346 else:
314 347 raise error.ProgrammingError(
315 348 'unhandled action from clientreactor: %s' % action)
316 349
317 350 def _processresponsedata(self, frame, meta, response):
318 351 # This can raise. The caller can handle it.
319 352 response._onresponsedata(meta['data'])
320 353
354 # If we got a content redirect response, we want to fetch it and
355 # expose the data as if we received it inline. But we also want to
356 # keep our internal request accounting in order. Our strategy is to
357 # basically put meaningful response handling on pause until EOS occurs
358 # and the stream accounting is in a good state. At that point, we follow
359 # the redirect and replace the response object with its data.
360
361 redirect = response._redirect
362 handlefuture = False if redirect else True
363
321 364 if meta['eos']:
322 365 response._oninputcomplete()
323 366 del self._requests[frame.requestid]
324 367
368 if redirect:
369 self._followredirect(frame.requestid, redirect)
370 return
371
372 if not handlefuture:
373 return
374
325 375 # If the command has a decoder, we wait until all input has been
326 376 # received before resolving the future. Otherwise we resolve the
327 377 # future immediately.
328 378 if frame.requestid not in self._futures:
329 379 return
330 380
331 381 if response.command not in COMMAND_DECODERS:
332 382 self._futures[frame.requestid].set_result(response.objects())
333 383 del self._futures[frame.requestid]
334 384 elif response._inputcomplete:
335 385 decoded = COMMAND_DECODERS[response.command](response.objects())
336 386 self._futures[frame.requestid].set_result(decoded)
337 387 del self._futures[frame.requestid]
338 388
389 def _followredirect(self, requestid, redirect):
390 """Called to initiate redirect following for a request."""
391 self._ui.note(_('(following redirect to %s)\n') % redirect.url)
392
393 # TODO handle framed responses.
394 if redirect.mediatype != b'application/mercurial-cbor':
395 raise error.Abort(_('cannot handle redirects for the %s media type')
396 % redirect.mediatype)
397
398 if redirect.fullhashes:
399 self._ui.warn(_('(support for validating hashes on content '
400 'redirects not supported)\n'))
401
402 if redirect.serverdercerts or redirect.servercadercerts:
403 self._ui.warn(_('(support for pinning server certificates on '
404 'content redirects not supported)\n'))
405
406 headers = {
407 r'Accept': redirect.mediatype,
408 }
409
410 req = self._requestbuilder(pycompat.strurl(redirect.url), None, headers)
411
412 try:
413 res = self._opener.open(req)
414 except util.urlerr.httperror as e:
415 if e.code == 401:
416 raise error.Abort(_('authorization failed'))
417 raise
418 except util.httplib.HTTPException as e:
419 self._ui.debug('http error requesting %s\n' % req.get_full_url())
420 self._ui.traceback()
421 raise IOError(None, e)
422
423 urlmod.wrapresponse(res)
424
425 # The existing response object is associated with frame data. Rather
426 # than try to normalize its state, just create a new object.
427 oldresponse = self._responses[requestid]
428 self._responses[requestid] = commandresponse(requestid,
429 oldresponse.command,
430 fromredirect=True)
431
432 self._redirects.append((requestid, res))
433
434 def _processredirect(self, rid, res):
435 """Called to continue processing a response from a redirect."""
436 response = self._responses[rid]
437
438 try:
439 data = res.read(32768)
440 response._onresponsedata(data)
441
442 # We're at end of stream.
443 if not data:
444 response._oninputcomplete()
445
446 if rid not in self._futures:
447 return
448
449 if response.command not in COMMAND_DECODERS:
450 self._futures[rid].set_result(response.objects())
451 del self._futures[rid]
452 elif response._inputcomplete:
453 decoded = COMMAND_DECODERS[response.command](response.objects())
454 self._futures[rid].set_result(decoded)
455 del self._futures[rid]
456
457 return bool(data)
458
459 except BaseException as e:
460 self._futures[rid].set_exception(e)
461 del self._futures[rid]
462 response._oninputcomplete()
463 return False
464
339 465 def decodebranchmap(objs):
340 466 # Response should be a single CBOR map of branch name to array of nodes.
341 467 bm = next(objs)
342 468
343 469 return {encoding.tolocal(k): v for k, v in bm.items()}
344 470
345 471 def decodeheads(objs):
346 472 # Array of node bytestrings.
347 473 return next(objs)
348 474
349 475 def decodeknown(objs):
350 476 # Bytestring where each byte is a 0 or 1.
351 477 raw = next(objs)
352 478
353 479 return [True if c == '1' else False for c in raw]
354 480
355 481 def decodelistkeys(objs):
356 482 # Map with bytestring keys and values.
357 483 return next(objs)
358 484
359 485 def decodelookup(objs):
360 486 return next(objs)
361 487
362 488 def decodepushkey(objs):
363 489 return next(objs)
364 490
365 491 COMMAND_DECODERS = {
366 492 'branchmap': decodebranchmap,
367 493 'heads': decodeheads,
368 494 'known': decodeknown,
369 495 'listkeys': decodelistkeys,
370 496 'lookup': decodelookup,
371 497 'pushkey': decodepushkey,
372 498 }
@@ -1,1369 +1,1394
1 1 $ . $TESTDIR/wireprotohelpers.sh
2 2
3 3 $ cat >> $HGRCPATH << EOF
4 4 > [extensions]
5 5 > blackbox =
6 6 > [blackbox]
7 7 > track = simplecache
8 8 > EOF
9 9
10 10 $ hg init server
11 11 $ enablehttpv2 server
12 12 $ cd server
13 13 $ cat >> .hg/hgrc << EOF
14 14 > [extensions]
15 15 > simplecache = $TESTDIR/wireprotosimplecache.py
16 16 > [simplecache]
17 17 > cacheapi = true
18 18 > EOF
19 19
20 20 $ echo a0 > a
21 21 $ echo b0 > b
22 22 $ hg -q commit -A -m 'commit 0'
23 23 $ echo a1 > a
24 24 $ hg commit -m 'commit 1'
25 25
26 26 $ hg --debug debugindex -m
27 27 rev linkrev nodeid p1 p2
28 28 0 0 992f4779029a3df8d0666d00bb924f69634e2641 0000000000000000000000000000000000000000 0000000000000000000000000000000000000000
29 29 1 1 a988fb43583e871d1ed5750ee074c6d840bbbfc8 992f4779029a3df8d0666d00bb924f69634e2641 0000000000000000000000000000000000000000
30 30
31 31 $ hg --config simplecache.redirectsfile=redirects.py serve -p $HGPORT -d --pid-file hg.pid -E error.log
32 32 $ cat hg.pid > $DAEMON_PIDS
33 33
34 34 $ cat > redirects.py << EOF
35 35 > [
36 36 > {
37 37 > b'name': b'target-a',
38 38 > b'protocol': b'http',
39 39 > b'snirequired': False,
40 40 > b'tlsversions': [b'1.2', b'1.3'],
41 41 > b'uris': [b'http://example.com/'],
42 42 > },
43 43 > ]
44 44 > EOF
45 45
46 46 Redirect targets advertised when configured
47 47
48 48 $ sendhttpv2peerhandshake << EOF
49 49 > command capabilities
50 50 > EOF
51 51 creating http peer for wire protocol version 2
52 52 s> GET /?cmd=capabilities HTTP/1.1\r\n
53 53 s> Accept-Encoding: identity\r\n
54 54 s> vary: X-HgProto-1,X-HgUpgrade-1\r\n
55 55 s> x-hgproto-1: cbor\r\n
56 56 s> x-hgupgrade-1: exp-http-v2-0002\r\n
57 57 s> accept: application/mercurial-0.1\r\n
58 58 s> host: $LOCALIP:$HGPORT\r\n (glob)
59 59 s> user-agent: Mercurial debugwireproto\r\n
60 60 s> \r\n
61 61 s> makefile('rb', None)
62 62 s> HTTP/1.1 200 OK\r\n
63 63 s> Server: testing stub value\r\n
64 64 s> Date: $HTTP_DATE$\r\n
65 65 s> Content-Type: application/mercurial-cbor\r\n
66 66 s> Content-Length: 1970\r\n
67 67 s> \r\n
68 68 s> \xa3GapibaseDapi/Dapis\xa1Pexp-http-v2-0002\xa6Hcommands\xaaIbranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullMchangesetdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x84IbookmarksGparentsEphaseHrevisionInoderange\xa3Gdefault\xf6Hrequired\xf4DtypeDlistEnodes\xa3Gdefault\xf6Hrequired\xf4DtypeDlistJnodesdepth\xa3Gdefault\xf6Hrequired\xf4DtypeCintKpermissions\x81DpullHfiledata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDpath\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullEheads\xa2Dargs\xa1Jpubliconly\xa3Gdefault\xf4Hrequired\xf4DtypeDboolKpermissions\x81DpullEknown\xa2Dargs\xa1Enodes\xa3Gdefault\x80Hrequired\xf4DtypeDlistKpermissions\x81DpullHlistkeys\xa2Dargs\xa1Inamespace\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullFlookup\xa2Dargs\xa1Ckey\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullLmanifestdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDtree\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullGpushkey\xa2Dargs\xa4Ckey\xa2Hrequired\xf5DtypeEbytesInamespace\xa2Hrequired\xf5DtypeEbytesCnew\xa2Hrequired\xf5DtypeEbytesCold\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpushKcompression\x82\xa1DnameDzstd\xa1DnameDzlibQframingmediatypes\x81X&application/mercurial-exp-framing-0005Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x82LgeneraldeltaHrevlogv1Hredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x81\xa5DnameHtarget-aHprotocolDhttpKsnirequired\xf4Ktlsversions\x82C1.2C1.3Duris\x81Shttp://example.com/Nv1capabilitiesY\x01\xd8batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
69 69 (remote redirect target target-a is compatible)
70 70 sending capabilities command
71 71 s> POST /api/exp-http-v2-0002/ro/capabilities HTTP/1.1\r\n
72 72 s> Accept-Encoding: identity\r\n
73 73 s> accept: application/mercurial-exp-framing-0005\r\n
74 74 s> content-type: application/mercurial-exp-framing-0005\r\n
75 75 s> content-length: 75\r\n
76 76 s> host: $LOCALIP:$HGPORT\r\n (glob)
77 77 s> user-agent: Mercurial debugwireproto\r\n
78 78 s> \r\n
79 79 s> C\x00\x00\x01\x00\x01\x01\x11\xa2DnameLcapabilitiesHredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x81Htarget-a
80 80 s> makefile('rb', None)
81 81 s> HTTP/1.1 200 OK\r\n
82 82 s> Server: testing stub value\r\n
83 83 s> Date: $HTTP_DATE$\r\n
84 84 s> Content-Type: application/mercurial-exp-framing-0005\r\n
85 85 s> Transfer-Encoding: chunked\r\n
86 86 s> \r\n
87 87 s> 13\r\n
88 88 s> \x0b\x00\x00\x01\x00\x02\x011
89 89 s> \xa1FstatusBok
90 90 s> \r\n
91 91 received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
92 92 s> 5ab\r\n
93 93 s> \xa3\x05\x00\x01\x00\x02\x001
94 94 s> \xa6Hcommands\xaaIbranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullMchangesetdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x84IbookmarksGparentsEphaseHrevisionInoderange\xa3Gdefault\xf6Hrequired\xf4DtypeDlistEnodes\xa3Gdefault\xf6Hrequired\xf4DtypeDlistJnodesdepth\xa3Gdefault\xf6Hrequired\xf4DtypeCintKpermissions\x81DpullHfiledata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDpath\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullEheads\xa2Dargs\xa1Jpubliconly\xa3Gdefault\xf4Hrequired\xf4DtypeDboolKpermissions\x81DpullEknown\xa2Dargs\xa1Enodes\xa3Gdefault\x80Hrequired\xf4DtypeDlistKpermissions\x81DpullHlistkeys\xa2Dargs\xa1Inamespace\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullFlookup\xa2Dargs\xa1Ckey\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullLmanifestdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDtree\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullGpushkey\xa2Dargs\xa4Ckey\xa2Hrequired\xf5DtypeEbytesInamespace\xa2Hrequired\xf5DtypeEbytesCnew\xa2Hrequired\xf5DtypeEbytesCold\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpushKcompression\x82\xa1DnameDzstd\xa1DnameDzlibQframingmediatypes\x81X&application/mercurial-exp-framing-0005Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x82LgeneraldeltaHrevlogv1Hredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x81\xa5DnameHtarget-aHprotocolDhttpKsnirequired\xf4Ktlsversions\x82C1.2C1.3Duris\x81Shttp://example.com/
95 95 s> \r\n
96 96 received frame(size=1443; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
97 97 s> 8\r\n
98 98 s> \x00\x00\x00\x01\x00\x02\x002
99 99 s> \r\n
100 100 s> 0\r\n
101 101 s> \r\n
102 102 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
103 103 response: gen[
104 104 {
105 105 b'commands': {
106 106 b'branchmap': {
107 107 b'args': {},
108 108 b'permissions': [
109 109 b'pull'
110 110 ]
111 111 },
112 112 b'capabilities': {
113 113 b'args': {},
114 114 b'permissions': [
115 115 b'pull'
116 116 ]
117 117 },
118 118 b'changesetdata': {
119 119 b'args': {
120 120 b'fields': {
121 121 b'default': set([]),
122 122 b'required': False,
123 123 b'type': b'set',
124 124 b'validvalues': set([
125 125 b'bookmarks',
126 126 b'parents',
127 127 b'phase',
128 128 b'revision'
129 129 ])
130 130 },
131 131 b'noderange': {
132 132 b'default': None,
133 133 b'required': False,
134 134 b'type': b'list'
135 135 },
136 136 b'nodes': {
137 137 b'default': None,
138 138 b'required': False,
139 139 b'type': b'list'
140 140 },
141 141 b'nodesdepth': {
142 142 b'default': None,
143 143 b'required': False,
144 144 b'type': b'int'
145 145 }
146 146 },
147 147 b'permissions': [
148 148 b'pull'
149 149 ]
150 150 },
151 151 b'filedata': {
152 152 b'args': {
153 153 b'fields': {
154 154 b'default': set([]),
155 155 b'required': False,
156 156 b'type': b'set',
157 157 b'validvalues': set([
158 158 b'parents',
159 159 b'revision'
160 160 ])
161 161 },
162 162 b'haveparents': {
163 163 b'default': False,
164 164 b'required': False,
165 165 b'type': b'bool'
166 166 },
167 167 b'nodes': {
168 168 b'required': True,
169 169 b'type': b'list'
170 170 },
171 171 b'path': {
172 172 b'required': True,
173 173 b'type': b'bytes'
174 174 }
175 175 },
176 176 b'permissions': [
177 177 b'pull'
178 178 ]
179 179 },
180 180 b'heads': {
181 181 b'args': {
182 182 b'publiconly': {
183 183 b'default': False,
184 184 b'required': False,
185 185 b'type': b'bool'
186 186 }
187 187 },
188 188 b'permissions': [
189 189 b'pull'
190 190 ]
191 191 },
192 192 b'known': {
193 193 b'args': {
194 194 b'nodes': {
195 195 b'default': [],
196 196 b'required': False,
197 197 b'type': b'list'
198 198 }
199 199 },
200 200 b'permissions': [
201 201 b'pull'
202 202 ]
203 203 },
204 204 b'listkeys': {
205 205 b'args': {
206 206 b'namespace': {
207 207 b'required': True,
208 208 b'type': b'bytes'
209 209 }
210 210 },
211 211 b'permissions': [
212 212 b'pull'
213 213 ]
214 214 },
215 215 b'lookup': {
216 216 b'args': {
217 217 b'key': {
218 218 b'required': True,
219 219 b'type': b'bytes'
220 220 }
221 221 },
222 222 b'permissions': [
223 223 b'pull'
224 224 ]
225 225 },
226 226 b'manifestdata': {
227 227 b'args': {
228 228 b'fields': {
229 229 b'default': set([]),
230 230 b'required': False,
231 231 b'type': b'set',
232 232 b'validvalues': set([
233 233 b'parents',
234 234 b'revision'
235 235 ])
236 236 },
237 237 b'haveparents': {
238 238 b'default': False,
239 239 b'required': False,
240 240 b'type': b'bool'
241 241 },
242 242 b'nodes': {
243 243 b'required': True,
244 244 b'type': b'list'
245 245 },
246 246 b'tree': {
247 247 b'required': True,
248 248 b'type': b'bytes'
249 249 }
250 250 },
251 251 b'permissions': [
252 252 b'pull'
253 253 ]
254 254 },
255 255 b'pushkey': {
256 256 b'args': {
257 257 b'key': {
258 258 b'required': True,
259 259 b'type': b'bytes'
260 260 },
261 261 b'namespace': {
262 262 b'required': True,
263 263 b'type': b'bytes'
264 264 },
265 265 b'new': {
266 266 b'required': True,
267 267 b'type': b'bytes'
268 268 },
269 269 b'old': {
270 270 b'required': True,
271 271 b'type': b'bytes'
272 272 }
273 273 },
274 274 b'permissions': [
275 275 b'push'
276 276 ]
277 277 }
278 278 },
279 279 b'compression': [
280 280 {
281 281 b'name': b'zstd'
282 282 },
283 283 {
284 284 b'name': b'zlib'
285 285 }
286 286 ],
287 287 b'framingmediatypes': [
288 288 b'application/mercurial-exp-framing-0005'
289 289 ],
290 290 b'pathfilterprefixes': set([
291 291 b'path:',
292 292 b'rootfilesin:'
293 293 ]),
294 294 b'rawrepoformats': [
295 295 b'generaldelta',
296 296 b'revlogv1'
297 297 ],
298 298 b'redirect': {
299 299 b'hashes': [
300 300 b'sha256',
301 301 b'sha1'
302 302 ],
303 303 b'targets': [
304 304 {
305 305 b'name': b'target-a',
306 306 b'protocol': b'http',
307 307 b'snirequired': False,
308 308 b'tlsversions': [
309 309 b'1.2',
310 310 b'1.3'
311 311 ],
312 312 b'uris': [
313 313 b'http://example.com/'
314 314 ]
315 315 }
316 316 ]
317 317 }
318 318 }
319 319 ]
320 320
321 321 Unknown protocol is filtered from compatible targets
322 322
323 323 $ cat > redirects.py << EOF
324 324 > [
325 325 > {
326 326 > b'name': b'target-a',
327 327 > b'protocol': b'http',
328 328 > b'uris': [b'http://example.com/'],
329 329 > },
330 330 > {
331 331 > b'name': b'target-b',
332 332 > b'protocol': b'unknown',
333 333 > b'uris': [b'unknown://example.com/'],
334 334 > },
335 335 > ]
336 336 > EOF
337 337
338 338 $ sendhttpv2peerhandshake << EOF
339 339 > command capabilities
340 340 > EOF
341 341 creating http peer for wire protocol version 2
342 342 s> GET /?cmd=capabilities HTTP/1.1\r\n
343 343 s> Accept-Encoding: identity\r\n
344 344 s> vary: X-HgProto-1,X-HgUpgrade-1\r\n
345 345 s> x-hgproto-1: cbor\r\n
346 346 s> x-hgupgrade-1: exp-http-v2-0002\r\n
347 347 s> accept: application/mercurial-0.1\r\n
348 348 s> host: $LOCALIP:$HGPORT\r\n (glob)
349 349 s> user-agent: Mercurial debugwireproto\r\n
350 350 s> \r\n
351 351 s> makefile('rb', None)
352 352 s> HTTP/1.1 200 OK\r\n
353 353 s> Server: testing stub value\r\n
354 354 s> Date: $HTTP_DATE$\r\n
355 355 s> Content-Type: application/mercurial-cbor\r\n
356 356 s> Content-Length: 1997\r\n
357 357 s> \r\n
358 358 s> \xa3GapibaseDapi/Dapis\xa1Pexp-http-v2-0002\xa6Hcommands\xaaIbranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullMchangesetdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x84IbookmarksGparentsEphaseHrevisionInoderange\xa3Gdefault\xf6Hrequired\xf4DtypeDlistEnodes\xa3Gdefault\xf6Hrequired\xf4DtypeDlistJnodesdepth\xa3Gdefault\xf6Hrequired\xf4DtypeCintKpermissions\x81DpullHfiledata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDpath\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullEheads\xa2Dargs\xa1Jpubliconly\xa3Gdefault\xf4Hrequired\xf4DtypeDboolKpermissions\x81DpullEknown\xa2Dargs\xa1Enodes\xa3Gdefault\x80Hrequired\xf4DtypeDlistKpermissions\x81DpullHlistkeys\xa2Dargs\xa1Inamespace\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullFlookup\xa2Dargs\xa1Ckey\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullLmanifestdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDtree\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullGpushkey\xa2Dargs\xa4Ckey\xa2Hrequired\xf5DtypeEbytesInamespace\xa2Hrequired\xf5DtypeEbytesCnew\xa2Hrequired\xf5DtypeEbytesCold\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpushKcompression\x82\xa1DnameDzstd\xa1DnameDzlibQframingmediatypes\x81X&application/mercurial-exp-framing-0005Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x82LgeneraldeltaHrevlogv1Hredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x82\xa3DnameHtarget-aHprotocolDhttpDuris\x81Shttp://example.com/\xa3DnameHtarget-bHprotocolGunknownDuris\x81Vunknown://example.com/Nv1capabilitiesY\x01\xd8batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
359 359 (remote redirect target target-a is compatible)
360 360 (remote redirect target target-b uses unsupported protocol: unknown)
361 361 sending capabilities command
362 362 s> POST /api/exp-http-v2-0002/ro/capabilities HTTP/1.1\r\n
363 363 s> Accept-Encoding: identity\r\n
364 364 s> accept: application/mercurial-exp-framing-0005\r\n
365 365 s> content-type: application/mercurial-exp-framing-0005\r\n
366 366 s> content-length: 75\r\n
367 367 s> host: $LOCALIP:$HGPORT\r\n (glob)
368 368 s> user-agent: Mercurial debugwireproto\r\n
369 369 s> \r\n
370 370 s> C\x00\x00\x01\x00\x01\x01\x11\xa2DnameLcapabilitiesHredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x81Htarget-a
371 371 s> makefile('rb', None)
372 372 s> HTTP/1.1 200 OK\r\n
373 373 s> Server: testing stub value\r\n
374 374 s> Date: $HTTP_DATE$\r\n
375 375 s> Content-Type: application/mercurial-exp-framing-0005\r\n
376 376 s> Transfer-Encoding: chunked\r\n
377 377 s> \r\n
378 378 s> 13\r\n
379 379 s> \x0b\x00\x00\x01\x00\x02\x011
380 380 s> \xa1FstatusBok
381 381 s> \r\n
382 382 received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
383 383 s> 5c6\r\n
384 384 s> \xbe\x05\x00\x01\x00\x02\x001
385 385 s> \xa6Hcommands\xaaIbranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullMchangesetdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x84IbookmarksGparentsEphaseHrevisionInoderange\xa3Gdefault\xf6Hrequired\xf4DtypeDlistEnodes\xa3Gdefault\xf6Hrequired\xf4DtypeDlistJnodesdepth\xa3Gdefault\xf6Hrequired\xf4DtypeCintKpermissions\x81DpullHfiledata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDpath\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullEheads\xa2Dargs\xa1Jpubliconly\xa3Gdefault\xf4Hrequired\xf4DtypeDboolKpermissions\x81DpullEknown\xa2Dargs\xa1Enodes\xa3Gdefault\x80Hrequired\xf4DtypeDlistKpermissions\x81DpullHlistkeys\xa2Dargs\xa1Inamespace\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullFlookup\xa2Dargs\xa1Ckey\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullLmanifestdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDtree\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullGpushkey\xa2Dargs\xa4Ckey\xa2Hrequired\xf5DtypeEbytesInamespace\xa2Hrequired\xf5DtypeEbytesCnew\xa2Hrequired\xf5DtypeEbytesCold\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpushKcompression\x82\xa1DnameDzstd\xa1DnameDzlibQframingmediatypes\x81X&application/mercurial-exp-framing-0005Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x82LgeneraldeltaHrevlogv1Hredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x82\xa3DnameHtarget-aHprotocolDhttpDuris\x81Shttp://example.com/\xa3DnameHtarget-bHprotocolGunknownDuris\x81Vunknown://example.com/
386 386 s> \r\n
387 387 received frame(size=1470; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
388 388 s> 8\r\n
389 389 s> \x00\x00\x00\x01\x00\x02\x002
390 390 s> \r\n
391 391 s> 0\r\n
392 392 s> \r\n
393 393 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
394 394 response: gen[
395 395 {
396 396 b'commands': {
397 397 b'branchmap': {
398 398 b'args': {},
399 399 b'permissions': [
400 400 b'pull'
401 401 ]
402 402 },
403 403 b'capabilities': {
404 404 b'args': {},
405 405 b'permissions': [
406 406 b'pull'
407 407 ]
408 408 },
409 409 b'changesetdata': {
410 410 b'args': {
411 411 b'fields': {
412 412 b'default': set([]),
413 413 b'required': False,
414 414 b'type': b'set',
415 415 b'validvalues': set([
416 416 b'bookmarks',
417 417 b'parents',
418 418 b'phase',
419 419 b'revision'
420 420 ])
421 421 },
422 422 b'noderange': {
423 423 b'default': None,
424 424 b'required': False,
425 425 b'type': b'list'
426 426 },
427 427 b'nodes': {
428 428 b'default': None,
429 429 b'required': False,
430 430 b'type': b'list'
431 431 },
432 432 b'nodesdepth': {
433 433 b'default': None,
434 434 b'required': False,
435 435 b'type': b'int'
436 436 }
437 437 },
438 438 b'permissions': [
439 439 b'pull'
440 440 ]
441 441 },
442 442 b'filedata': {
443 443 b'args': {
444 444 b'fields': {
445 445 b'default': set([]),
446 446 b'required': False,
447 447 b'type': b'set',
448 448 b'validvalues': set([
449 449 b'parents',
450 450 b'revision'
451 451 ])
452 452 },
453 453 b'haveparents': {
454 454 b'default': False,
455 455 b'required': False,
456 456 b'type': b'bool'
457 457 },
458 458 b'nodes': {
459 459 b'required': True,
460 460 b'type': b'list'
461 461 },
462 462 b'path': {
463 463 b'required': True,
464 464 b'type': b'bytes'
465 465 }
466 466 },
467 467 b'permissions': [
468 468 b'pull'
469 469 ]
470 470 },
471 471 b'heads': {
472 472 b'args': {
473 473 b'publiconly': {
474 474 b'default': False,
475 475 b'required': False,
476 476 b'type': b'bool'
477 477 }
478 478 },
479 479 b'permissions': [
480 480 b'pull'
481 481 ]
482 482 },
483 483 b'known': {
484 484 b'args': {
485 485 b'nodes': {
486 486 b'default': [],
487 487 b'required': False,
488 488 b'type': b'list'
489 489 }
490 490 },
491 491 b'permissions': [
492 492 b'pull'
493 493 ]
494 494 },
495 495 b'listkeys': {
496 496 b'args': {
497 497 b'namespace': {
498 498 b'required': True,
499 499 b'type': b'bytes'
500 500 }
501 501 },
502 502 b'permissions': [
503 503 b'pull'
504 504 ]
505 505 },
506 506 b'lookup': {
507 507 b'args': {
508 508 b'key': {
509 509 b'required': True,
510 510 b'type': b'bytes'
511 511 }
512 512 },
513 513 b'permissions': [
514 514 b'pull'
515 515 ]
516 516 },
517 517 b'manifestdata': {
518 518 b'args': {
519 519 b'fields': {
520 520 b'default': set([]),
521 521 b'required': False,
522 522 b'type': b'set',
523 523 b'validvalues': set([
524 524 b'parents',
525 525 b'revision'
526 526 ])
527 527 },
528 528 b'haveparents': {
529 529 b'default': False,
530 530 b'required': False,
531 531 b'type': b'bool'
532 532 },
533 533 b'nodes': {
534 534 b'required': True,
535 535 b'type': b'list'
536 536 },
537 537 b'tree': {
538 538 b'required': True,
539 539 b'type': b'bytes'
540 540 }
541 541 },
542 542 b'permissions': [
543 543 b'pull'
544 544 ]
545 545 },
546 546 b'pushkey': {
547 547 b'args': {
548 548 b'key': {
549 549 b'required': True,
550 550 b'type': b'bytes'
551 551 },
552 552 b'namespace': {
553 553 b'required': True,
554 554 b'type': b'bytes'
555 555 },
556 556 b'new': {
557 557 b'required': True,
558 558 b'type': b'bytes'
559 559 },
560 560 b'old': {
561 561 b'required': True,
562 562 b'type': b'bytes'
563 563 }
564 564 },
565 565 b'permissions': [
566 566 b'push'
567 567 ]
568 568 }
569 569 },
570 570 b'compression': [
571 571 {
572 572 b'name': b'zstd'
573 573 },
574 574 {
575 575 b'name': b'zlib'
576 576 }
577 577 ],
578 578 b'framingmediatypes': [
579 579 b'application/mercurial-exp-framing-0005'
580 580 ],
581 581 b'pathfilterprefixes': set([
582 582 b'path:',
583 583 b'rootfilesin:'
584 584 ]),
585 585 b'rawrepoformats': [
586 586 b'generaldelta',
587 587 b'revlogv1'
588 588 ],
589 589 b'redirect': {
590 590 b'hashes': [
591 591 b'sha256',
592 592 b'sha1'
593 593 ],
594 594 b'targets': [
595 595 {
596 596 b'name': b'target-a',
597 597 b'protocol': b'http',
598 598 b'uris': [
599 599 b'http://example.com/'
600 600 ]
601 601 },
602 602 {
603 603 b'name': b'target-b',
604 604 b'protocol': b'unknown',
605 605 b'uris': [
606 606 b'unknown://example.com/'
607 607 ]
608 608 }
609 609 ]
610 610 }
611 611 }
612 612 ]
613 613
614 614 Missing SNI support filters targets that require SNI
615 615
616 616 $ cat > nosni.py << EOF
617 617 > from mercurial import sslutil
618 618 > sslutil.hassni = False
619 619 > EOF
620 620 $ cat >> $HGRCPATH << EOF
621 621 > [extensions]
622 622 > nosni=`pwd`/nosni.py
623 623 > EOF
624 624
625 625 $ cat > redirects.py << EOF
626 626 > [
627 627 > {
628 628 > b'name': b'target-bad-tls',
629 629 > b'protocol': b'https',
630 630 > b'uris': [b'https://example.com/'],
631 631 > b'snirequired': True,
632 632 > },
633 633 > ]
634 634 > EOF
635 635
636 636 $ sendhttpv2peerhandshake << EOF
637 637 > command capabilities
638 638 > EOF
639 639 creating http peer for wire protocol version 2
640 640 s> GET /?cmd=capabilities HTTP/1.1\r\n
641 641 s> Accept-Encoding: identity\r\n
642 642 s> vary: X-HgProto-1,X-HgUpgrade-1\r\n
643 643 s> x-hgproto-1: cbor\r\n
644 644 s> x-hgupgrade-1: exp-http-v2-0002\r\n
645 645 s> accept: application/mercurial-0.1\r\n
646 646 s> host: $LOCALIP:$HGPORT\r\n (glob)
647 647 s> user-agent: Mercurial debugwireproto\r\n
648 648 s> \r\n
649 649 s> makefile('rb', None)
650 650 s> HTTP/1.1 200 OK\r\n
651 651 s> Server: testing stub value\r\n
652 652 s> Date: $HTTP_DATE$\r\n
653 653 s> Content-Type: application/mercurial-cbor\r\n
654 654 s> Content-Length: 1957\r\n
655 655 s> \r\n
656 656 s> \xa3GapibaseDapi/Dapis\xa1Pexp-http-v2-0002\xa6Hcommands\xaaIbranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullMchangesetdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x84IbookmarksGparentsEphaseHrevisionInoderange\xa3Gdefault\xf6Hrequired\xf4DtypeDlistEnodes\xa3Gdefault\xf6Hrequired\xf4DtypeDlistJnodesdepth\xa3Gdefault\xf6Hrequired\xf4DtypeCintKpermissions\x81DpullHfiledata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDpath\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullEheads\xa2Dargs\xa1Jpubliconly\xa3Gdefault\xf4Hrequired\xf4DtypeDboolKpermissions\x81DpullEknown\xa2Dargs\xa1Enodes\xa3Gdefault\x80Hrequired\xf4DtypeDlistKpermissions\x81DpullHlistkeys\xa2Dargs\xa1Inamespace\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullFlookup\xa2Dargs\xa1Ckey\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullLmanifestdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDtree\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullGpushkey\xa2Dargs\xa4Ckey\xa2Hrequired\xf5DtypeEbytesInamespace\xa2Hrequired\xf5DtypeEbytesCnew\xa2Hrequired\xf5DtypeEbytesCold\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpushKcompression\x82\xa1DnameDzstd\xa1DnameDzlibQframingmediatypes\x81X&application/mercurial-exp-framing-0005Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x82LgeneraldeltaHrevlogv1Hredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x81\xa4DnameNtarget-bad-tlsHprotocolEhttpsKsnirequired\xf5Duris\x81Thttps://example.com/Nv1capabilitiesY\x01\xd8batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
657 657 (redirect target target-bad-tls requires SNI, which is unsupported)
658 658 sending capabilities command
659 659 s> POST /api/exp-http-v2-0002/ro/capabilities HTTP/1.1\r\n
660 660 s> Accept-Encoding: identity\r\n
661 661 s> accept: application/mercurial-exp-framing-0005\r\n
662 662 s> content-type: application/mercurial-exp-framing-0005\r\n
663 663 s> content-length: 66\r\n
664 664 s> host: $LOCALIP:$HGPORT\r\n (glob)
665 665 s> user-agent: Mercurial debugwireproto\r\n
666 666 s> \r\n
667 667 s> :\x00\x00\x01\x00\x01\x01\x11\xa2DnameLcapabilitiesHredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x80
668 668 s> makefile('rb', None)
669 669 s> HTTP/1.1 200 OK\r\n
670 670 s> Server: testing stub value\r\n
671 671 s> Date: $HTTP_DATE$\r\n
672 672 s> Content-Type: application/mercurial-exp-framing-0005\r\n
673 673 s> Transfer-Encoding: chunked\r\n
674 674 s> \r\n
675 675 s> 13\r\n
676 676 s> \x0b\x00\x00\x01\x00\x02\x011
677 677 s> \xa1FstatusBok
678 678 s> \r\n
679 679 received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
680 680 s> 59e\r\n
681 681 s> \x96\x05\x00\x01\x00\x02\x001
682 682 s> \xa6Hcommands\xaaIbranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullMchangesetdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x84IbookmarksGparentsEphaseHrevisionInoderange\xa3Gdefault\xf6Hrequired\xf4DtypeDlistEnodes\xa3Gdefault\xf6Hrequired\xf4DtypeDlistJnodesdepth\xa3Gdefault\xf6Hrequired\xf4DtypeCintKpermissions\x81DpullHfiledata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDpath\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullEheads\xa2Dargs\xa1Jpubliconly\xa3Gdefault\xf4Hrequired\xf4DtypeDboolKpermissions\x81DpullEknown\xa2Dargs\xa1Enodes\xa3Gdefault\x80Hrequired\xf4DtypeDlistKpermissions\x81DpullHlistkeys\xa2Dargs\xa1Inamespace\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullFlookup\xa2Dargs\xa1Ckey\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullLmanifestdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDtree\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullGpushkey\xa2Dargs\xa4Ckey\xa2Hrequired\xf5DtypeEbytesInamespace\xa2Hrequired\xf5DtypeEbytesCnew\xa2Hrequired\xf5DtypeEbytesCold\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpushKcompression\x82\xa1DnameDzstd\xa1DnameDzlibQframingmediatypes\x81X&application/mercurial-exp-framing-0005Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x82LgeneraldeltaHrevlogv1Hredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x81\xa4DnameNtarget-bad-tlsHprotocolEhttpsKsnirequired\xf5Duris\x81Thttps://example.com/
683 683 s> \r\n
684 684 received frame(size=1430; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
685 685 s> 8\r\n
686 686 s> \x00\x00\x00\x01\x00\x02\x002
687 687 s> \r\n
688 688 s> 0\r\n
689 689 s> \r\n
690 690 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
691 691 response: gen[
692 692 {
693 693 b'commands': {
694 694 b'branchmap': {
695 695 b'args': {},
696 696 b'permissions': [
697 697 b'pull'
698 698 ]
699 699 },
700 700 b'capabilities': {
701 701 b'args': {},
702 702 b'permissions': [
703 703 b'pull'
704 704 ]
705 705 },
706 706 b'changesetdata': {
707 707 b'args': {
708 708 b'fields': {
709 709 b'default': set([]),
710 710 b'required': False,
711 711 b'type': b'set',
712 712 b'validvalues': set([
713 713 b'bookmarks',
714 714 b'parents',
715 715 b'phase',
716 716 b'revision'
717 717 ])
718 718 },
719 719 b'noderange': {
720 720 b'default': None,
721 721 b'required': False,
722 722 b'type': b'list'
723 723 },
724 724 b'nodes': {
725 725 b'default': None,
726 726 b'required': False,
727 727 b'type': b'list'
728 728 },
729 729 b'nodesdepth': {
730 730 b'default': None,
731 731 b'required': False,
732 732 b'type': b'int'
733 733 }
734 734 },
735 735 b'permissions': [
736 736 b'pull'
737 737 ]
738 738 },
739 739 b'filedata': {
740 740 b'args': {
741 741 b'fields': {
742 742 b'default': set([]),
743 743 b'required': False,
744 744 b'type': b'set',
745 745 b'validvalues': set([
746 746 b'parents',
747 747 b'revision'
748 748 ])
749 749 },
750 750 b'haveparents': {
751 751 b'default': False,
752 752 b'required': False,
753 753 b'type': b'bool'
754 754 },
755 755 b'nodes': {
756 756 b'required': True,
757 757 b'type': b'list'
758 758 },
759 759 b'path': {
760 760 b'required': True,
761 761 b'type': b'bytes'
762 762 }
763 763 },
764 764 b'permissions': [
765 765 b'pull'
766 766 ]
767 767 },
768 768 b'heads': {
769 769 b'args': {
770 770 b'publiconly': {
771 771 b'default': False,
772 772 b'required': False,
773 773 b'type': b'bool'
774 774 }
775 775 },
776 776 b'permissions': [
777 777 b'pull'
778 778 ]
779 779 },
780 780 b'known': {
781 781 b'args': {
782 782 b'nodes': {
783 783 b'default': [],
784 784 b'required': False,
785 785 b'type': b'list'
786 786 }
787 787 },
788 788 b'permissions': [
789 789 b'pull'
790 790 ]
791 791 },
792 792 b'listkeys': {
793 793 b'args': {
794 794 b'namespace': {
795 795 b'required': True,
796 796 b'type': b'bytes'
797 797 }
798 798 },
799 799 b'permissions': [
800 800 b'pull'
801 801 ]
802 802 },
803 803 b'lookup': {
804 804 b'args': {
805 805 b'key': {
806 806 b'required': True,
807 807 b'type': b'bytes'
808 808 }
809 809 },
810 810 b'permissions': [
811 811 b'pull'
812 812 ]
813 813 },
814 814 b'manifestdata': {
815 815 b'args': {
816 816 b'fields': {
817 817 b'default': set([]),
818 818 b'required': False,
819 819 b'type': b'set',
820 820 b'validvalues': set([
821 821 b'parents',
822 822 b'revision'
823 823 ])
824 824 },
825 825 b'haveparents': {
826 826 b'default': False,
827 827 b'required': False,
828 828 b'type': b'bool'
829 829 },
830 830 b'nodes': {
831 831 b'required': True,
832 832 b'type': b'list'
833 833 },
834 834 b'tree': {
835 835 b'required': True,
836 836 b'type': b'bytes'
837 837 }
838 838 },
839 839 b'permissions': [
840 840 b'pull'
841 841 ]
842 842 },
843 843 b'pushkey': {
844 844 b'args': {
845 845 b'key': {
846 846 b'required': True,
847 847 b'type': b'bytes'
848 848 },
849 849 b'namespace': {
850 850 b'required': True,
851 851 b'type': b'bytes'
852 852 },
853 853 b'new': {
854 854 b'required': True,
855 855 b'type': b'bytes'
856 856 },
857 857 b'old': {
858 858 b'required': True,
859 859 b'type': b'bytes'
860 860 }
861 861 },
862 862 b'permissions': [
863 863 b'push'
864 864 ]
865 865 }
866 866 },
867 867 b'compression': [
868 868 {
869 869 b'name': b'zstd'
870 870 },
871 871 {
872 872 b'name': b'zlib'
873 873 }
874 874 ],
875 875 b'framingmediatypes': [
876 876 b'application/mercurial-exp-framing-0005'
877 877 ],
878 878 b'pathfilterprefixes': set([
879 879 b'path:',
880 880 b'rootfilesin:'
881 881 ]),
882 882 b'rawrepoformats': [
883 883 b'generaldelta',
884 884 b'revlogv1'
885 885 ],
886 886 b'redirect': {
887 887 b'hashes': [
888 888 b'sha256',
889 889 b'sha1'
890 890 ],
891 891 b'targets': [
892 892 {
893 893 b'name': b'target-bad-tls',
894 894 b'protocol': b'https',
895 895 b'snirequired': True,
896 896 b'uris': [
897 897 b'https://example.com/'
898 898 ]
899 899 }
900 900 ]
901 901 }
902 902 }
903 903 ]
904 904
905 905 $ cat >> $HGRCPATH << EOF
906 906 > [extensions]
907 907 > nosni=!
908 908 > EOF
909 909
910 910 Unknown tls value is filtered from compatible targets
911 911
912 912 $ cat > redirects.py << EOF
913 913 > [
914 914 > {
915 915 > b'name': b'target-bad-tls',
916 916 > b'protocol': b'https',
917 917 > b'uris': [b'https://example.com/'],
918 918 > b'tlsversions': [b'42', b'39'],
919 919 > },
920 920 > ]
921 921 > EOF
922 922
923 923 $ sendhttpv2peerhandshake << EOF
924 924 > command capabilities
925 925 > EOF
926 926 creating http peer for wire protocol version 2
927 927 s> GET /?cmd=capabilities HTTP/1.1\r\n
928 928 s> Accept-Encoding: identity\r\n
929 929 s> vary: X-HgProto-1,X-HgUpgrade-1\r\n
930 930 s> x-hgproto-1: cbor\r\n
931 931 s> x-hgupgrade-1: exp-http-v2-0002\r\n
932 932 s> accept: application/mercurial-0.1\r\n
933 933 s> host: $LOCALIP:$HGPORT\r\n (glob)
934 934 s> user-agent: Mercurial debugwireproto\r\n
935 935 s> \r\n
936 936 s> makefile('rb', None)
937 937 s> HTTP/1.1 200 OK\r\n
938 938 s> Server: testing stub value\r\n
939 939 s> Date: $HTTP_DATE$\r\n
940 940 s> Content-Type: application/mercurial-cbor\r\n
941 941 s> Content-Length: 1963\r\n
942 942 s> \r\n
943 943 s> \xa3GapibaseDapi/Dapis\xa1Pexp-http-v2-0002\xa6Hcommands\xaaIbranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullMchangesetdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x84IbookmarksGparentsEphaseHrevisionInoderange\xa3Gdefault\xf6Hrequired\xf4DtypeDlistEnodes\xa3Gdefault\xf6Hrequired\xf4DtypeDlistJnodesdepth\xa3Gdefault\xf6Hrequired\xf4DtypeCintKpermissions\x81DpullHfiledata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDpath\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullEheads\xa2Dargs\xa1Jpubliconly\xa3Gdefault\xf4Hrequired\xf4DtypeDboolKpermissions\x81DpullEknown\xa2Dargs\xa1Enodes\xa3Gdefault\x80Hrequired\xf4DtypeDlistKpermissions\x81DpullHlistkeys\xa2Dargs\xa1Inamespace\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullFlookup\xa2Dargs\xa1Ckey\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullLmanifestdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDtree\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullGpushkey\xa2Dargs\xa4Ckey\xa2Hrequired\xf5DtypeEbytesInamespace\xa2Hrequired\xf5DtypeEbytesCnew\xa2Hrequired\xf5DtypeEbytesCold\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpushKcompression\x82\xa1DnameDzstd\xa1DnameDzlibQframingmediatypes\x81X&application/mercurial-exp-framing-0005Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x82LgeneraldeltaHrevlogv1Hredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x81\xa4DnameNtarget-bad-tlsHprotocolEhttpsKtlsversions\x82B42B39Duris\x81Thttps://example.com/Nv1capabilitiesY\x01\xd8batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
944 944 (remote redirect target target-bad-tls requires unsupported TLS versions: 39, 42)
945 945 sending capabilities command
946 946 s> POST /api/exp-http-v2-0002/ro/capabilities HTTP/1.1\r\n
947 947 s> Accept-Encoding: identity\r\n
948 948 s> accept: application/mercurial-exp-framing-0005\r\n
949 949 s> content-type: application/mercurial-exp-framing-0005\r\n
950 950 s> content-length: 66\r\n
951 951 s> host: $LOCALIP:$HGPORT\r\n (glob)
952 952 s> user-agent: Mercurial debugwireproto\r\n
953 953 s> \r\n
954 954 s> :\x00\x00\x01\x00\x01\x01\x11\xa2DnameLcapabilitiesHredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x80
955 955 s> makefile('rb', None)
956 956 s> HTTP/1.1 200 OK\r\n
957 957 s> Server: testing stub value\r\n
958 958 s> Date: $HTTP_DATE$\r\n
959 959 s> Content-Type: application/mercurial-exp-framing-0005\r\n
960 960 s> Transfer-Encoding: chunked\r\n
961 961 s> \r\n
962 962 s> 13\r\n
963 963 s> \x0b\x00\x00\x01\x00\x02\x011
964 964 s> \xa1FstatusBok
965 965 s> \r\n
966 966 received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
967 967 s> 5a4\r\n
968 968 s> \x9c\x05\x00\x01\x00\x02\x001
969 969 s> \xa6Hcommands\xaaIbranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullMchangesetdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x84IbookmarksGparentsEphaseHrevisionInoderange\xa3Gdefault\xf6Hrequired\xf4DtypeDlistEnodes\xa3Gdefault\xf6Hrequired\xf4DtypeDlistJnodesdepth\xa3Gdefault\xf6Hrequired\xf4DtypeCintKpermissions\x81DpullHfiledata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDpath\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullEheads\xa2Dargs\xa1Jpubliconly\xa3Gdefault\xf4Hrequired\xf4DtypeDboolKpermissions\x81DpullEknown\xa2Dargs\xa1Enodes\xa3Gdefault\x80Hrequired\xf4DtypeDlistKpermissions\x81DpullHlistkeys\xa2Dargs\xa1Inamespace\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullFlookup\xa2Dargs\xa1Ckey\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullLmanifestdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDtree\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullGpushkey\xa2Dargs\xa4Ckey\xa2Hrequired\xf5DtypeEbytesInamespace\xa2Hrequired\xf5DtypeEbytesCnew\xa2Hrequired\xf5DtypeEbytesCold\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpushKcompression\x82\xa1DnameDzstd\xa1DnameDzlibQframingmediatypes\x81X&application/mercurial-exp-framing-0005Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x82LgeneraldeltaHrevlogv1Hredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x81\xa4DnameNtarget-bad-tlsHprotocolEhttpsKtlsversions\x82B42B39Duris\x81Thttps://example.com/
970 970 s> \r\n
971 971 received frame(size=1436; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
972 972 s> 8\r\n
973 973 s> \x00\x00\x00\x01\x00\x02\x002
974 974 s> \r\n
975 975 s> 0\r\n
976 976 s> \r\n
977 977 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
978 978 response: gen[
979 979 {
980 980 b'commands': {
981 981 b'branchmap': {
982 982 b'args': {},
983 983 b'permissions': [
984 984 b'pull'
985 985 ]
986 986 },
987 987 b'capabilities': {
988 988 b'args': {},
989 989 b'permissions': [
990 990 b'pull'
991 991 ]
992 992 },
993 993 b'changesetdata': {
994 994 b'args': {
995 995 b'fields': {
996 996 b'default': set([]),
997 997 b'required': False,
998 998 b'type': b'set',
999 999 b'validvalues': set([
1000 1000 b'bookmarks',
1001 1001 b'parents',
1002 1002 b'phase',
1003 1003 b'revision'
1004 1004 ])
1005 1005 },
1006 1006 b'noderange': {
1007 1007 b'default': None,
1008 1008 b'required': False,
1009 1009 b'type': b'list'
1010 1010 },
1011 1011 b'nodes': {
1012 1012 b'default': None,
1013 1013 b'required': False,
1014 1014 b'type': b'list'
1015 1015 },
1016 1016 b'nodesdepth': {
1017 1017 b'default': None,
1018 1018 b'required': False,
1019 1019 b'type': b'int'
1020 1020 }
1021 1021 },
1022 1022 b'permissions': [
1023 1023 b'pull'
1024 1024 ]
1025 1025 },
1026 1026 b'filedata': {
1027 1027 b'args': {
1028 1028 b'fields': {
1029 1029 b'default': set([]),
1030 1030 b'required': False,
1031 1031 b'type': b'set',
1032 1032 b'validvalues': set([
1033 1033 b'parents',
1034 1034 b'revision'
1035 1035 ])
1036 1036 },
1037 1037 b'haveparents': {
1038 1038 b'default': False,
1039 1039 b'required': False,
1040 1040 b'type': b'bool'
1041 1041 },
1042 1042 b'nodes': {
1043 1043 b'required': True,
1044 1044 b'type': b'list'
1045 1045 },
1046 1046 b'path': {
1047 1047 b'required': True,
1048 1048 b'type': b'bytes'
1049 1049 }
1050 1050 },
1051 1051 b'permissions': [
1052 1052 b'pull'
1053 1053 ]
1054 1054 },
1055 1055 b'heads': {
1056 1056 b'args': {
1057 1057 b'publiconly': {
1058 1058 b'default': False,
1059 1059 b'required': False,
1060 1060 b'type': b'bool'
1061 1061 }
1062 1062 },
1063 1063 b'permissions': [
1064 1064 b'pull'
1065 1065 ]
1066 1066 },
1067 1067 b'known': {
1068 1068 b'args': {
1069 1069 b'nodes': {
1070 1070 b'default': [],
1071 1071 b'required': False,
1072 1072 b'type': b'list'
1073 1073 }
1074 1074 },
1075 1075 b'permissions': [
1076 1076 b'pull'
1077 1077 ]
1078 1078 },
1079 1079 b'listkeys': {
1080 1080 b'args': {
1081 1081 b'namespace': {
1082 1082 b'required': True,
1083 1083 b'type': b'bytes'
1084 1084 }
1085 1085 },
1086 1086 b'permissions': [
1087 1087 b'pull'
1088 1088 ]
1089 1089 },
1090 1090 b'lookup': {
1091 1091 b'args': {
1092 1092 b'key': {
1093 1093 b'required': True,
1094 1094 b'type': b'bytes'
1095 1095 }
1096 1096 },
1097 1097 b'permissions': [
1098 1098 b'pull'
1099 1099 ]
1100 1100 },
1101 1101 b'manifestdata': {
1102 1102 b'args': {
1103 1103 b'fields': {
1104 1104 b'default': set([]),
1105 1105 b'required': False,
1106 1106 b'type': b'set',
1107 1107 b'validvalues': set([
1108 1108 b'parents',
1109 1109 b'revision'
1110 1110 ])
1111 1111 },
1112 1112 b'haveparents': {
1113 1113 b'default': False,
1114 1114 b'required': False,
1115 1115 b'type': b'bool'
1116 1116 },
1117 1117 b'nodes': {
1118 1118 b'required': True,
1119 1119 b'type': b'list'
1120 1120 },
1121 1121 b'tree': {
1122 1122 b'required': True,
1123 1123 b'type': b'bytes'
1124 1124 }
1125 1125 },
1126 1126 b'permissions': [
1127 1127 b'pull'
1128 1128 ]
1129 1129 },
1130 1130 b'pushkey': {
1131 1131 b'args': {
1132 1132 b'key': {
1133 1133 b'required': True,
1134 1134 b'type': b'bytes'
1135 1135 },
1136 1136 b'namespace': {
1137 1137 b'required': True,
1138 1138 b'type': b'bytes'
1139 1139 },
1140 1140 b'new': {
1141 1141 b'required': True,
1142 1142 b'type': b'bytes'
1143 1143 },
1144 1144 b'old': {
1145 1145 b'required': True,
1146 1146 b'type': b'bytes'
1147 1147 }
1148 1148 },
1149 1149 b'permissions': [
1150 1150 b'push'
1151 1151 ]
1152 1152 }
1153 1153 },
1154 1154 b'compression': [
1155 1155 {
1156 1156 b'name': b'zstd'
1157 1157 },
1158 1158 {
1159 1159 b'name': b'zlib'
1160 1160 }
1161 1161 ],
1162 1162 b'framingmediatypes': [
1163 1163 b'application/mercurial-exp-framing-0005'
1164 1164 ],
1165 1165 b'pathfilterprefixes': set([
1166 1166 b'path:',
1167 1167 b'rootfilesin:'
1168 1168 ]),
1169 1169 b'rawrepoformats': [
1170 1170 b'generaldelta',
1171 1171 b'revlogv1'
1172 1172 ],
1173 1173 b'redirect': {
1174 1174 b'hashes': [
1175 1175 b'sha256',
1176 1176 b'sha1'
1177 1177 ],
1178 1178 b'targets': [
1179 1179 {
1180 1180 b'name': b'target-bad-tls',
1181 1181 b'protocol': b'https',
1182 1182 b'tlsversions': [
1183 1183 b'42',
1184 1184 b'39'
1185 1185 ],
1186 1186 b'uris': [
1187 1187 b'https://example.com/'
1188 1188 ]
1189 1189 }
1190 1190 ]
1191 1191 }
1192 1192 }
1193 1193 ]
1194 1194
1195 1195 Set up the server to issue content redirects to its built-in API server.
1196 1196
1197 1197 $ cat > redirects.py << EOF
1198 1198 > [
1199 1199 > {
1200 1200 > b'name': b'local',
1201 1201 > b'protocol': b'http',
1202 1202 > b'uris': [b'http://example.com/'],
1203 1203 > },
1204 1204 > ]
1205 1205 > EOF
1206 1206
1207 1207 Request to eventual cache URL should return 404 (validating the cache server works)
1208 1208
1209 1209 $ sendhttpraw << EOF
1210 1210 > httprequest GET api/simplecache/missingkey
1211 1211 > user-agent: test
1212 1212 > EOF
1213 1213 using raw connection to peer
1214 1214 s> GET /api/simplecache/missingkey HTTP/1.1\r\n
1215 1215 s> Accept-Encoding: identity\r\n
1216 1216 s> user-agent: test\r\n
1217 1217 s> host: $LOCALIP:$HGPORT\r\n (glob)
1218 1218 s> \r\n
1219 1219 s> makefile('rb', None)
1220 1220 s> HTTP/1.1 404 Not Found\r\n
1221 1221 s> Server: testing stub value\r\n
1222 1222 s> Date: $HTTP_DATE$\r\n
1223 1223 s> Content-Type: text/plain\r\n
1224 1224 s> Content-Length: 22\r\n
1225 1225 s> \r\n
1226 1226 s> key not found in cache
1227 1227
1228 1228 Send a cacheable request
1229 1229
1230 1230 $ sendhttpv2peer << EOF
1231 1231 > command manifestdata
1232 1232 > nodes eval:[b'\x99\x2f\x47\x79\x02\x9a\x3d\xf8\xd0\x66\x6d\x00\xbb\x92\x4f\x69\x63\x4e\x26\x41']
1233 1233 > tree eval:b''
1234 1234 > fields eval:[b'parents']
1235 1235 > EOF
1236 1236 creating http peer for wire protocol version 2
1237 1237 sending manifestdata command
1238 1238 s> POST /api/exp-http-v2-0002/ro/manifestdata HTTP/1.1\r\n
1239 1239 s> Accept-Encoding: identity\r\n
1240 1240 s> accept: application/mercurial-exp-framing-0005\r\n
1241 1241 s> content-type: application/mercurial-exp-framing-0005\r\n
1242 1242 s> content-length: 128\r\n
1243 1243 s> host: $LOCALIP:$HGPORT\r\n (glob)
1244 1244 s> user-agent: Mercurial debugwireproto\r\n
1245 1245 s> \r\n
1246 1246 s> x\x00\x00\x01\x00\x01\x01\x11\xa3Dargs\xa3Ffields\x81GparentsEnodes\x81T\x99/Gy\x02\x9a=\xf8\xd0fm\x00\xbb\x92OicN&ADtree@DnameLmanifestdataHredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x81Elocal
1247 1247 s> makefile('rb', None)
1248 1248 s> HTTP/1.1 200 OK\r\n
1249 1249 s> Server: testing stub value\r\n
1250 1250 s> Date: $HTTP_DATE$\r\n
1251 1251 s> Content-Type: application/mercurial-exp-framing-0005\r\n
1252 1252 s> Transfer-Encoding: chunked\r\n
1253 1253 s> \r\n
1254 1254 s> 13\r\n
1255 1255 s> \x0b\x00\x00\x01\x00\x02\x011
1256 1256 s> \xa1FstatusBok
1257 1257 s> \r\n
1258 1258 received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
1259 1259 s> 63\r\n
1260 1260 s> [\x00\x00\x01\x00\x02\x001
1261 1261 s> \xa1Jtotalitems\x01\xa2DnodeT\x99/Gy\x02\x9a=\xf8\xd0fm\x00\xbb\x92OicN&AGparents\x82T\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00T\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00
1262 1262 s> \r\n
1263 1263 received frame(size=91; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
1264 1264 s> 8\r\n
1265 1265 s> \x00\x00\x00\x01\x00\x02\x002
1266 1266 s> \r\n
1267 1267 s> 0\r\n
1268 1268 s> \r\n
1269 1269 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
1270 1270 response: gen[
1271 1271 {
1272 1272 b'totalitems': 1
1273 1273 },
1274 1274 {
1275 1275 b'node': b'\x99/Gy\x02\x9a=\xf8\xd0fm\x00\xbb\x92OicN&A',
1276 1276 b'parents': [
1277 1277 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
1278 1278 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
1279 1279 ]
1280 1280 }
1281 1281 ]
1282 1282
1283 1283 Cached entry should be available on server
1284 1284
1285 1285 $ sendhttpraw << EOF
1286 1286 > httprequest GET api/simplecache/c045a581599d58608efd3d93d8129841f2af04a0
1287 1287 > user-agent: test
1288 1288 > EOF
1289 1289 using raw connection to peer
1290 1290 s> GET /api/simplecache/c045a581599d58608efd3d93d8129841f2af04a0 HTTP/1.1\r\n
1291 1291 s> Accept-Encoding: identity\r\n
1292 1292 s> user-agent: test\r\n
1293 1293 s> host: $LOCALIP:$HGPORT\r\n (glob)
1294 1294 s> \r\n
1295 1295 s> makefile('rb', None)
1296 1296 s> HTTP/1.1 200 OK\r\n
1297 1297 s> Server: testing stub value\r\n
1298 1298 s> Date: $HTTP_DATE$\r\n
1299 1299 s> Content-Type: application/mercurial-cbor\r\n
1300 1300 s> Content-Length: 91\r\n
1301 1301 s> \r\n
1302 1302 s> \xa1Jtotalitems\x01\xa2DnodeT\x99/Gy\x02\x9a=\xf8\xd0fm\x00\xbb\x92OicN&AGparents\x82T\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00T\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00
1303 1303 cbor> [
1304 1304 {
1305 1305 b'totalitems': 1
1306 1306 },
1307 1307 {
1308 1308 b'node': b'\x99/Gy\x02\x9a=\xf8\xd0fm\x00\xbb\x92OicN&A',
1309 1309 b'parents': [
1310 1310 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
1311 1311 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
1312 1312 ]
1313 1313 }
1314 1314 ]
1315 1315
1316 1316 2nd request should result in content redirect response
1317 1317
1318 1318 $ sendhttpv2peer << EOF
1319 1319 > command manifestdata
1320 1320 > nodes eval:[b'\x99\x2f\x47\x79\x02\x9a\x3d\xf8\xd0\x66\x6d\x00\xbb\x92\x4f\x69\x63\x4e\x26\x41']
1321 1321 > tree eval:b''
1322 1322 > fields eval:[b'parents']
1323 1323 > EOF
1324 1324 creating http peer for wire protocol version 2
1325 1325 sending manifestdata command
1326 1326 s> POST /api/exp-http-v2-0002/ro/manifestdata HTTP/1.1\r\n
1327 1327 s> Accept-Encoding: identity\r\n
1328 1328 s> accept: application/mercurial-exp-framing-0005\r\n
1329 1329 s> content-type: application/mercurial-exp-framing-0005\r\n
1330 1330 s> content-length: 128\r\n
1331 1331 s> host: $LOCALIP:$HGPORT\r\n (glob)
1332 1332 s> user-agent: Mercurial debugwireproto\r\n
1333 1333 s> \r\n
1334 1334 s> x\x00\x00\x01\x00\x01\x01\x11\xa3Dargs\xa3Ffields\x81GparentsEnodes\x81T\x99/Gy\x02\x9a=\xf8\xd0fm\x00\xbb\x92OicN&ADtree@DnameLmanifestdataHredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x81Elocal
1335 1335 s> makefile('rb', None)
1336 1336 s> HTTP/1.1 200 OK\r\n
1337 1337 s> Server: testing stub value\r\n
1338 1338 s> Date: $HTTP_DATE$\r\n
1339 1339 s> Content-Type: application/mercurial-exp-framing-0005\r\n
1340 1340 s> Transfer-Encoding: chunked\r\n
1341 1341 s> \r\n
1342 1342 s> *\r\n (glob)
1343 1343 s> \x*\x00\x00\x01\x00\x02\x011 (glob)
1344 1344 s> \xa2Hlocation\xa2ImediatypeX\x1aapplication/mercurial-cborCurl*http://*:$HGPORT/api/simplecache/c045a581599d58608efd3d93d8129841f2af04a0FstatusHredirect (glob)
1345 1345 s> \r\n
1346 1346 received frame(size=*; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation) (glob)
1347 1347 s> 8\r\n
1348 1348 s> \x00\x00\x00\x01\x00\x02\x001
1349 1349 s> \r\n
1350 1350 s> 8\r\n
1351 1351 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
1352 1352 s> \x00\x00\x00\x01\x00\x02\x002
1353 1353 s> \r\n
1354 1354 s> 0\r\n
1355 1355 s> \r\n
1356 1356 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
1357 abort: redirect responses not yet supported
1358 [255]
1357 (following redirect to http://*:$HGPORT/api/simplecache/c045a581599d58608efd3d93d8129841f2af04a0) (glob)
1358 s> GET /api/simplecache/c045a581599d58608efd3d93d8129841f2af04a0 HTTP/1.1\r\n
1359 s> Accept-Encoding: identity\r\n
1360 s> accept: application/mercurial-cbor\r\n
1361 s> host: *:$HGPORT\r\n (glob)
1362 s> user-agent: Mercurial debugwireproto\r\n
1363 s> \r\n
1364 s> makefile('rb', None)
1365 s> HTTP/1.1 200 OK\r\n
1366 s> Server: testing stub value\r\n
1367 s> Date: $HTTP_DATE$\r\n
1368 s> Content-Type: application/mercurial-cbor\r\n
1369 s> Content-Length: 91\r\n
1370 s> \r\n
1371 s> \xa1Jtotalitems\x01\xa2DnodeT\x99/Gy\x02\x9a=\xf8\xd0fm\x00\xbb\x92OicN&AGparents\x82T\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00T\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00
1372 response: gen[
1373 {
1374 b'totalitems': 1
1375 },
1376 {
1377 b'node': b'\x99/Gy\x02\x9a=\xf8\xd0fm\x00\xbb\x92OicN&A',
1378 b'parents': [
1379 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
1380 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
1381 ]
1382 }
1383 ]
1359 1384
1360 1385 $ cat error.log
1361 1386 $ killdaemons.py
1362 1387
1363 1388 $ cat .hg/blackbox.log
1364 1389 *> cacher constructed for manifestdata (glob)
1365 1390 *> cache miss for c045a581599d58608efd3d93d8129841f2af04a0 (glob)
1366 1391 *> storing cache entry for c045a581599d58608efd3d93d8129841f2af04a0 (glob)
1367 1392 *> cacher constructed for manifestdata (glob)
1368 1393 *> cache hit for c045a581599d58608efd3d93d8129841f2af04a0 (glob)
1369 1394 *> sending content redirect for c045a581599d58608efd3d93d8129841f2af04a0 to http://*:$HGPORT/api/simplecache/c045a581599d58608efd3d93d8129841f2af04a0 (glob)
General Comments 0
You need to be logged in to leave comments. Login now