##// END OF EJS Templates
wireprotov2: send content encoded frames from server...
Gregory Szorc -
r40173:b5bf3dd6 default
parent child Browse files
Show More
@@ -1,1744 +1,1855 b''
1 1 # wireprotoframing.py - unified framing protocol for wire protocol
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 # This file contains functionality to support the unified frame-based wire
9 9 # protocol. For details about the protocol, see
10 10 # `hg help internals.wireprotocol`.
11 11
12 12 from __future__ import absolute_import
13 13
14 14 import collections
15 15 import struct
16 16
17 17 from .i18n import _
18 18 from .thirdparty import (
19 19 attr,
20 20 )
21 21 from . import (
22 22 encoding,
23 23 error,
24 24 pycompat,
25 25 util,
26 26 wireprototypes,
27 27 )
28 28 from .utils import (
29 29 cborutil,
30 30 stringutil,
31 31 )
32 32
33 33 FRAME_HEADER_SIZE = 8
34 34 DEFAULT_MAX_FRAME_SIZE = 32768
35 35
36 36 STREAM_FLAG_BEGIN_STREAM = 0x01
37 37 STREAM_FLAG_END_STREAM = 0x02
38 38 STREAM_FLAG_ENCODING_APPLIED = 0x04
39 39
40 40 STREAM_FLAGS = {
41 41 b'stream-begin': STREAM_FLAG_BEGIN_STREAM,
42 42 b'stream-end': STREAM_FLAG_END_STREAM,
43 43 b'encoded': STREAM_FLAG_ENCODING_APPLIED,
44 44 }
45 45
46 46 FRAME_TYPE_COMMAND_REQUEST = 0x01
47 47 FRAME_TYPE_COMMAND_DATA = 0x02
48 48 FRAME_TYPE_COMMAND_RESPONSE = 0x03
49 49 FRAME_TYPE_ERROR_RESPONSE = 0x05
50 50 FRAME_TYPE_TEXT_OUTPUT = 0x06
51 51 FRAME_TYPE_PROGRESS = 0x07
52 52 FRAME_TYPE_SENDER_PROTOCOL_SETTINGS = 0x08
53 53 FRAME_TYPE_STREAM_SETTINGS = 0x09
54 54
55 55 FRAME_TYPES = {
56 56 b'command-request': FRAME_TYPE_COMMAND_REQUEST,
57 57 b'command-data': FRAME_TYPE_COMMAND_DATA,
58 58 b'command-response': FRAME_TYPE_COMMAND_RESPONSE,
59 59 b'error-response': FRAME_TYPE_ERROR_RESPONSE,
60 60 b'text-output': FRAME_TYPE_TEXT_OUTPUT,
61 61 b'progress': FRAME_TYPE_PROGRESS,
62 62 b'sender-protocol-settings': FRAME_TYPE_SENDER_PROTOCOL_SETTINGS,
63 63 b'stream-settings': FRAME_TYPE_STREAM_SETTINGS,
64 64 }
65 65
66 66 FLAG_COMMAND_REQUEST_NEW = 0x01
67 67 FLAG_COMMAND_REQUEST_CONTINUATION = 0x02
68 68 FLAG_COMMAND_REQUEST_MORE_FRAMES = 0x04
69 69 FLAG_COMMAND_REQUEST_EXPECT_DATA = 0x08
70 70
71 71 FLAGS_COMMAND_REQUEST = {
72 72 b'new': FLAG_COMMAND_REQUEST_NEW,
73 73 b'continuation': FLAG_COMMAND_REQUEST_CONTINUATION,
74 74 b'more': FLAG_COMMAND_REQUEST_MORE_FRAMES,
75 75 b'have-data': FLAG_COMMAND_REQUEST_EXPECT_DATA,
76 76 }
77 77
78 78 FLAG_COMMAND_DATA_CONTINUATION = 0x01
79 79 FLAG_COMMAND_DATA_EOS = 0x02
80 80
81 81 FLAGS_COMMAND_DATA = {
82 82 b'continuation': FLAG_COMMAND_DATA_CONTINUATION,
83 83 b'eos': FLAG_COMMAND_DATA_EOS,
84 84 }
85 85
86 86 FLAG_COMMAND_RESPONSE_CONTINUATION = 0x01
87 87 FLAG_COMMAND_RESPONSE_EOS = 0x02
88 88
89 89 FLAGS_COMMAND_RESPONSE = {
90 90 b'continuation': FLAG_COMMAND_RESPONSE_CONTINUATION,
91 91 b'eos': FLAG_COMMAND_RESPONSE_EOS,
92 92 }
93 93
94 94 FLAG_SENDER_PROTOCOL_SETTINGS_CONTINUATION = 0x01
95 95 FLAG_SENDER_PROTOCOL_SETTINGS_EOS = 0x02
96 96
97 97 FLAGS_SENDER_PROTOCOL_SETTINGS = {
98 98 b'continuation': FLAG_SENDER_PROTOCOL_SETTINGS_CONTINUATION,
99 99 b'eos': FLAG_SENDER_PROTOCOL_SETTINGS_EOS,
100 100 }
101 101
102 102 FLAG_STREAM_ENCODING_SETTINGS_CONTINUATION = 0x01
103 103 FLAG_STREAM_ENCODING_SETTINGS_EOS = 0x02
104 104
105 105 FLAGS_STREAM_ENCODING_SETTINGS = {
106 106 b'continuation': FLAG_STREAM_ENCODING_SETTINGS_CONTINUATION,
107 107 b'eos': FLAG_STREAM_ENCODING_SETTINGS_EOS,
108 108 }
109 109
110 110 # Maps frame types to their available flags.
111 111 FRAME_TYPE_FLAGS = {
112 112 FRAME_TYPE_COMMAND_REQUEST: FLAGS_COMMAND_REQUEST,
113 113 FRAME_TYPE_COMMAND_DATA: FLAGS_COMMAND_DATA,
114 114 FRAME_TYPE_COMMAND_RESPONSE: FLAGS_COMMAND_RESPONSE,
115 115 FRAME_TYPE_ERROR_RESPONSE: {},
116 116 FRAME_TYPE_TEXT_OUTPUT: {},
117 117 FRAME_TYPE_PROGRESS: {},
118 118 FRAME_TYPE_SENDER_PROTOCOL_SETTINGS: FLAGS_SENDER_PROTOCOL_SETTINGS,
119 119 FRAME_TYPE_STREAM_SETTINGS: FLAGS_STREAM_ENCODING_SETTINGS,
120 120 }
121 121
122 122 ARGUMENT_RECORD_HEADER = struct.Struct(r'<HH')
123 123
124 124 def humanflags(mapping, value):
125 125 """Convert a numeric flags value to a human value, using a mapping table."""
126 126 namemap = {v: k for k, v in mapping.iteritems()}
127 127 flags = []
128 128 val = 1
129 129 while value >= val:
130 130 if value & val:
131 131 flags.append(namemap.get(val, '<unknown 0x%02x>' % val))
132 132 val <<= 1
133 133
134 134 return b'|'.join(flags)
135 135
136 136 @attr.s(slots=True)
137 137 class frameheader(object):
138 138 """Represents the data in a frame header."""
139 139
140 140 length = attr.ib()
141 141 requestid = attr.ib()
142 142 streamid = attr.ib()
143 143 streamflags = attr.ib()
144 144 typeid = attr.ib()
145 145 flags = attr.ib()
146 146
147 147 @attr.s(slots=True, repr=False)
148 148 class frame(object):
149 149 """Represents a parsed frame."""
150 150
151 151 requestid = attr.ib()
152 152 streamid = attr.ib()
153 153 streamflags = attr.ib()
154 154 typeid = attr.ib()
155 155 flags = attr.ib()
156 156 payload = attr.ib()
157 157
158 158 @encoding.strmethod
159 159 def __repr__(self):
160 160 typename = '<unknown 0x%02x>' % self.typeid
161 161 for name, value in FRAME_TYPES.iteritems():
162 162 if value == self.typeid:
163 163 typename = name
164 164 break
165 165
166 166 return ('frame(size=%d; request=%d; stream=%d; streamflags=%s; '
167 167 'type=%s; flags=%s)' % (
168 168 len(self.payload), self.requestid, self.streamid,
169 169 humanflags(STREAM_FLAGS, self.streamflags), typename,
170 170 humanflags(FRAME_TYPE_FLAGS.get(self.typeid, {}), self.flags)))
171 171
172 172 def makeframe(requestid, streamid, streamflags, typeid, flags, payload):
173 173 """Assemble a frame into a byte array."""
174 174 # TODO assert size of payload.
175 175 frame = bytearray(FRAME_HEADER_SIZE + len(payload))
176 176
177 177 # 24 bits length
178 178 # 16 bits request id
179 179 # 8 bits stream id
180 180 # 8 bits stream flags
181 181 # 4 bits type
182 182 # 4 bits flags
183 183
184 184 l = struct.pack(r'<I', len(payload))
185 185 frame[0:3] = l[0:3]
186 186 struct.pack_into(r'<HBB', frame, 3, requestid, streamid, streamflags)
187 187 frame[7] = (typeid << 4) | flags
188 188 frame[8:] = payload
189 189
190 190 return frame
191 191
192 192 def makeframefromhumanstring(s):
193 193 """Create a frame from a human readable string
194 194
195 195 Strings have the form:
196 196
197 197 <request-id> <stream-id> <stream-flags> <type> <flags> <payload>
198 198
199 199 This can be used by user-facing applications and tests for creating
200 200 frames easily without having to type out a bunch of constants.
201 201
202 202 Request ID and stream IDs are integers.
203 203
204 204 Stream flags, frame type, and flags can be specified by integer or
205 205 named constant.
206 206
207 207 Flags can be delimited by `|` to bitwise OR them together.
208 208
209 209 If the payload begins with ``cbor:``, the following string will be
210 210 evaluated as Python literal and the resulting object will be fed into
211 211 a CBOR encoder. Otherwise, the payload is interpreted as a Python
212 212 byte string literal.
213 213 """
214 214 fields = s.split(b' ', 5)
215 215 requestid, streamid, streamflags, frametype, frameflags, payload = fields
216 216
217 217 requestid = int(requestid)
218 218 streamid = int(streamid)
219 219
220 220 finalstreamflags = 0
221 221 for flag in streamflags.split(b'|'):
222 222 if flag in STREAM_FLAGS:
223 223 finalstreamflags |= STREAM_FLAGS[flag]
224 224 else:
225 225 finalstreamflags |= int(flag)
226 226
227 227 if frametype in FRAME_TYPES:
228 228 frametype = FRAME_TYPES[frametype]
229 229 else:
230 230 frametype = int(frametype)
231 231
232 232 finalflags = 0
233 233 validflags = FRAME_TYPE_FLAGS[frametype]
234 234 for flag in frameflags.split(b'|'):
235 235 if flag in validflags:
236 236 finalflags |= validflags[flag]
237 237 else:
238 238 finalflags |= int(flag)
239 239
240 240 if payload.startswith(b'cbor:'):
241 241 payload = b''.join(cborutil.streamencode(
242 242 stringutil.evalpythonliteral(payload[5:])))
243 243
244 244 else:
245 245 payload = stringutil.unescapestr(payload)
246 246
247 247 return makeframe(requestid=requestid, streamid=streamid,
248 248 streamflags=finalstreamflags, typeid=frametype,
249 249 flags=finalflags, payload=payload)
250 250
251 251 def parseheader(data):
252 252 """Parse a unified framing protocol frame header from a buffer.
253 253
254 254 The header is expected to be in the buffer at offset 0 and the
255 255 buffer is expected to be large enough to hold a full header.
256 256 """
257 257 # 24 bits payload length (little endian)
258 258 # 16 bits request ID
259 259 # 8 bits stream ID
260 260 # 8 bits stream flags
261 261 # 4 bits frame type
262 262 # 4 bits frame flags
263 263 # ... payload
264 264 framelength = data[0] + 256 * data[1] + 16384 * data[2]
265 265 requestid, streamid, streamflags = struct.unpack_from(r'<HBB', data, 3)
266 266 typeflags = data[7]
267 267
268 268 frametype = (typeflags & 0xf0) >> 4
269 269 frameflags = typeflags & 0x0f
270 270
271 271 return frameheader(framelength, requestid, streamid, streamflags,
272 272 frametype, frameflags)
273 273
274 274 def readframe(fh):
275 275 """Read a unified framing protocol frame from a file object.
276 276
277 277 Returns a 3-tuple of (type, flags, payload) for the decoded frame or
278 278 None if no frame is available. May raise if a malformed frame is
279 279 seen.
280 280 """
281 281 header = bytearray(FRAME_HEADER_SIZE)
282 282
283 283 readcount = fh.readinto(header)
284 284
285 285 if readcount == 0:
286 286 return None
287 287
288 288 if readcount != FRAME_HEADER_SIZE:
289 289 raise error.Abort(_('received incomplete frame: got %d bytes: %s') %
290 290 (readcount, header))
291 291
292 292 h = parseheader(header)
293 293
294 294 payload = fh.read(h.length)
295 295 if len(payload) != h.length:
296 296 raise error.Abort(_('frame length error: expected %d; got %d') %
297 297 (h.length, len(payload)))
298 298
299 299 return frame(h.requestid, h.streamid, h.streamflags, h.typeid, h.flags,
300 300 payload)
301 301
302 302 def createcommandframes(stream, requestid, cmd, args, datafh=None,
303 303 maxframesize=DEFAULT_MAX_FRAME_SIZE,
304 304 redirect=None):
305 305 """Create frames necessary to transmit a request to run a command.
306 306
307 307 This is a generator of bytearrays. Each item represents a frame
308 308 ready to be sent over the wire to a peer.
309 309 """
310 310 data = {b'name': cmd}
311 311 if args:
312 312 data[b'args'] = args
313 313
314 314 if redirect:
315 315 data[b'redirect'] = redirect
316 316
317 317 data = b''.join(cborutil.streamencode(data))
318 318
319 319 offset = 0
320 320
321 321 while True:
322 322 flags = 0
323 323
324 324 # Must set new or continuation flag.
325 325 if not offset:
326 326 flags |= FLAG_COMMAND_REQUEST_NEW
327 327 else:
328 328 flags |= FLAG_COMMAND_REQUEST_CONTINUATION
329 329
330 330 # Data frames is set on all frames.
331 331 if datafh:
332 332 flags |= FLAG_COMMAND_REQUEST_EXPECT_DATA
333 333
334 334 payload = data[offset:offset + maxframesize]
335 335 offset += len(payload)
336 336
337 337 if len(payload) == maxframesize and offset < len(data):
338 338 flags |= FLAG_COMMAND_REQUEST_MORE_FRAMES
339 339
340 340 yield stream.makeframe(requestid=requestid,
341 341 typeid=FRAME_TYPE_COMMAND_REQUEST,
342 342 flags=flags,
343 343 payload=payload)
344 344
345 345 if not (flags & FLAG_COMMAND_REQUEST_MORE_FRAMES):
346 346 break
347 347
348 348 if datafh:
349 349 while True:
350 350 data = datafh.read(DEFAULT_MAX_FRAME_SIZE)
351 351
352 352 done = False
353 353 if len(data) == DEFAULT_MAX_FRAME_SIZE:
354 354 flags = FLAG_COMMAND_DATA_CONTINUATION
355 355 else:
356 356 flags = FLAG_COMMAND_DATA_EOS
357 357 assert datafh.read(1) == b''
358 358 done = True
359 359
360 360 yield stream.makeframe(requestid=requestid,
361 361 typeid=FRAME_TYPE_COMMAND_DATA,
362 362 flags=flags,
363 363 payload=data)
364 364
365 365 if done:
366 366 break
367 367
368 368 def createcommandresponseokframe(stream, requestid):
369 369 overall = b''.join(cborutil.streamencode({b'status': b'ok'}))
370 370
371 if stream.streamsettingssent:
372 overall = stream.encode(overall)
373 encoded = True
374
375 if not overall:
376 return None
377 else:
378 encoded = False
379
371 380 return stream.makeframe(requestid=requestid,
372 381 typeid=FRAME_TYPE_COMMAND_RESPONSE,
373 382 flags=FLAG_COMMAND_RESPONSE_CONTINUATION,
374 payload=overall)
383 payload=overall,
384 encoded=encoded)
375 385
376 def createcommandresponseeosframe(stream, requestid):
386 def createcommandresponseeosframes(stream, requestid,
387 maxframesize=DEFAULT_MAX_FRAME_SIZE):
377 388 """Create an empty payload frame representing command end-of-stream."""
378 return stream.makeframe(requestid=requestid,
389 payload = stream.flush()
390
391 offset = 0
392 while True:
393 chunk = payload[offset:offset + maxframesize]
394 offset += len(chunk)
395
396 done = offset == len(payload)
397
398 if done:
399 flags = FLAG_COMMAND_RESPONSE_EOS
400 else:
401 flags = FLAG_COMMAND_RESPONSE_CONTINUATION
402
403 yield stream.makeframe(requestid=requestid,
379 404 typeid=FRAME_TYPE_COMMAND_RESPONSE,
380 flags=FLAG_COMMAND_RESPONSE_EOS,
381 payload=b'')
405 flags=flags,
406 payload=chunk,
407 encoded=payload != b'')
408
409 if done:
410 break
382 411
383 412 def createalternatelocationresponseframe(stream, requestid, location):
384 413 data = {
385 414 b'status': b'redirect',
386 415 b'location': {
387 416 b'url': location.url,
388 417 b'mediatype': location.mediatype,
389 418 }
390 419 }
391 420
392 421 for a in (r'size', r'fullhashes', r'fullhashseed', r'serverdercerts',
393 422 r'servercadercerts'):
394 423 value = getattr(location, a)
395 424 if value is not None:
396 425 data[b'location'][pycompat.bytestr(a)] = value
397 426
427 payload = b''.join(cborutil.streamencode(data))
428
429 if stream.streamsettingssent:
430 payload = stream.encode(payload)
431 encoded = True
432 else:
433 encoded = False
434
398 435 return stream.makeframe(requestid=requestid,
399 436 typeid=FRAME_TYPE_COMMAND_RESPONSE,
400 437 flags=FLAG_COMMAND_RESPONSE_CONTINUATION,
401 payload=b''.join(cborutil.streamencode(data)))
438 payload=payload,
439 encoded=encoded)
402 440
403 441 def createcommanderrorresponse(stream, requestid, message, args=None):
404 442 # TODO should this be using a list of {'msg': ..., 'args': {}} so atom
405 443 # formatting works consistently?
406 444 m = {
407 445 b'status': b'error',
408 446 b'error': {
409 447 b'message': message,
410 448 }
411 449 }
412 450
413 451 if args:
414 452 m[b'error'][b'args'] = args
415 453
416 454 overall = b''.join(cborutil.streamencode(m))
417 455
418 456 yield stream.makeframe(requestid=requestid,
419 457 typeid=FRAME_TYPE_COMMAND_RESPONSE,
420 458 flags=FLAG_COMMAND_RESPONSE_EOS,
421 459 payload=overall)
422 460
423 461 def createerrorframe(stream, requestid, msg, errtype):
424 462 # TODO properly handle frame size limits.
425 463 assert len(msg) <= DEFAULT_MAX_FRAME_SIZE
426 464
427 465 payload = b''.join(cborutil.streamencode({
428 466 b'type': errtype,
429 467 b'message': [{b'msg': msg}],
430 468 }))
431 469
432 470 yield stream.makeframe(requestid=requestid,
433 471 typeid=FRAME_TYPE_ERROR_RESPONSE,
434 472 flags=0,
435 473 payload=payload)
436 474
437 475 def createtextoutputframe(stream, requestid, atoms,
438 476 maxframesize=DEFAULT_MAX_FRAME_SIZE):
439 477 """Create a text output frame to render text to people.
440 478
441 479 ``atoms`` is a 3-tuple of (formatting string, args, labels).
442 480
443 481 The formatting string contains ``%s`` tokens to be replaced by the
444 482 corresponding indexed entry in ``args``. ``labels`` is an iterable of
445 483 formatters to be applied at rendering time. In terms of the ``ui``
446 484 class, each atom corresponds to a ``ui.write()``.
447 485 """
448 486 atomdicts = []
449 487
450 488 for (formatting, args, labels) in atoms:
451 489 # TODO look for localstr, other types here?
452 490
453 491 if not isinstance(formatting, bytes):
454 492 raise ValueError('must use bytes formatting strings')
455 493 for arg in args:
456 494 if not isinstance(arg, bytes):
457 495 raise ValueError('must use bytes for arguments')
458 496 for label in labels:
459 497 if not isinstance(label, bytes):
460 498 raise ValueError('must use bytes for labels')
461 499
462 500 # Formatting string must be ASCII.
463 501 formatting = formatting.decode(r'ascii', r'replace').encode(r'ascii')
464 502
465 503 # Arguments must be UTF-8.
466 504 args = [a.decode(r'utf-8', r'replace').encode(r'utf-8') for a in args]
467 505
468 506 # Labels must be ASCII.
469 507 labels = [l.decode(r'ascii', r'strict').encode(r'ascii')
470 508 for l in labels]
471 509
472 510 atom = {b'msg': formatting}
473 511 if args:
474 512 atom[b'args'] = args
475 513 if labels:
476 514 atom[b'labels'] = labels
477 515
478 516 atomdicts.append(atom)
479 517
480 518 payload = b''.join(cborutil.streamencode(atomdicts))
481 519
482 520 if len(payload) > maxframesize:
483 521 raise ValueError('cannot encode data in a single frame')
484 522
485 523 yield stream.makeframe(requestid=requestid,
486 524 typeid=FRAME_TYPE_TEXT_OUTPUT,
487 525 flags=0,
488 526 payload=payload)
489 527
490 528 class bufferingcommandresponseemitter(object):
491 529 """Helper object to emit command response frames intelligently.
492 530
493 531 Raw command response data is likely emitted in chunks much smaller
494 532 than what can fit in a single frame. This class exists to buffer
495 533 chunks until enough data is available to fit in a single frame.
496 534
497 535 TODO we'll need something like this when compression is supported.
498 536 So it might make sense to implement this functionality at the stream
499 537 level.
500 538 """
501 539 def __init__(self, stream, requestid, maxframesize=DEFAULT_MAX_FRAME_SIZE):
502 540 self._stream = stream
503 541 self._requestid = requestid
504 542 self._maxsize = maxframesize
505 543 self._chunks = []
506 544 self._chunkssize = 0
507 545
508 546 def send(self, data):
509 547 """Send new data for emission.
510 548
511 549 Is a generator of new frames that were derived from the new input.
512 550
513 551 If the special input ``None`` is received, flushes all buffered
514 552 data to frames.
515 553 """
516 554
517 555 if data is None:
518 556 for frame in self._flush():
519 557 yield frame
520 558 return
521 559
560 data = self._stream.encode(data)
561
522 562 # There is a ton of potential to do more complicated things here.
523 563 # Our immediate goal is to coalesce small chunks into big frames,
524 564 # not achieve the fewest number of frames possible. So we go with
525 565 # a simple implementation:
526 566 #
527 567 # * If a chunk is too large for a frame, we flush and emit frames
528 568 # for the new chunk.
529 569 # * If a chunk can be buffered without total buffered size limits
530 570 # being exceeded, we do that.
531 571 # * If a chunk causes us to go over our buffering limit, we flush
532 572 # and then buffer the new chunk.
533 573
534 574 if not data:
535 575 return
536 576
537 577 if len(data) > self._maxsize:
538 578 for frame in self._flush():
539 579 yield frame
540 580
541 581 # Now emit frames for the big chunk.
542 582 offset = 0
543 583 while True:
544 584 chunk = data[offset:offset + self._maxsize]
545 585 offset += len(chunk)
546 586
547 587 yield self._stream.makeframe(
548 588 self._requestid,
549 589 typeid=FRAME_TYPE_COMMAND_RESPONSE,
550 590 flags=FLAG_COMMAND_RESPONSE_CONTINUATION,
551 payload=chunk)
591 payload=chunk,
592 encoded=True)
552 593
553 594 if offset == len(data):
554 595 return
555 596
556 597 # If we don't have enough to constitute a full frame, buffer and
557 598 # return.
558 599 if len(data) + self._chunkssize < self._maxsize:
559 600 self._chunks.append(data)
560 601 self._chunkssize += len(data)
561 602 return
562 603
563 604 # Else flush what we have and buffer the new chunk. We could do
564 605 # something more intelligent here, like break the chunk. Let's
565 606 # keep things simple for now.
566 607 for frame in self._flush():
567 608 yield frame
568 609
569 610 self._chunks.append(data)
570 611 self._chunkssize = len(data)
571 612
572 613 def _flush(self):
573 614 payload = b''.join(self._chunks)
574 615 assert len(payload) <= self._maxsize
575 616
576 617 self._chunks[:] = []
577 618 self._chunkssize = 0
578 619
579 620 if not payload:
580 621 return
581 622
582 623 yield self._stream.makeframe(
583 624 self._requestid,
584 625 typeid=FRAME_TYPE_COMMAND_RESPONSE,
585 626 flags=FLAG_COMMAND_RESPONSE_CONTINUATION,
586 payload=payload)
627 payload=payload,
628 encoded=True)
587 629
588 630 # TODO consider defining encoders/decoders using the util.compressionengine
589 631 # mechanism.
590 632
591 633 class identityencoder(object):
592 634 """Encoder for the "identity" stream encoding profile."""
593 635 def __init__(self, ui):
594 636 pass
595 637
596 638 def encode(self, data):
597 639 return data
598 640
599 641 def flush(self):
600 642 return b''
601 643
602 644 def finish(self):
603 645 return b''
604 646
605 647 class identitydecoder(object):
606 648 """Decoder for the "identity" stream encoding profile."""
607 649
608 650 def __init__(self, ui, extraobjs):
609 651 if extraobjs:
610 652 raise error.Abort(_('identity decoder received unexpected '
611 653 'additional values'))
612 654
613 655 def decode(self, data):
614 656 return data
615 657
616 658 class zlibencoder(object):
617 659 def __init__(self, ui):
618 660 import zlib
619 661 self._zlib = zlib
620 662 self._compressor = zlib.compressobj()
621 663
622 664 def encode(self, data):
623 665 return self._compressor.compress(data)
624 666
625 667 def flush(self):
626 668 # Z_SYNC_FLUSH doesn't reset compression context, which is
627 669 # what we want.
628 670 return self._compressor.flush(self._zlib.Z_SYNC_FLUSH)
629 671
630 672 def finish(self):
631 673 res = self._compressor.flush(self._zlib.Z_FINISH)
632 674 self._compressor = None
633 675 return res
634 676
635 677 class zlibdecoder(object):
636 678 def __init__(self, ui, extraobjs):
637 679 import zlib
638 680
639 681 if extraobjs:
640 682 raise error.Abort(_('zlib decoder received unexpected '
641 683 'additional values'))
642 684
643 685 self._decompressor = zlib.decompressobj()
644 686
645 687 def decode(self, data):
646 688 # Python 2's zlib module doesn't use the buffer protocol and can't
647 689 # handle all bytes-like types.
648 690 if not pycompat.ispy3 and isinstance(data, bytearray):
649 691 data = bytes(data)
650 692
651 693 return self._decompressor.decompress(data)
652 694
653 695 class zstdbaseencoder(object):
654 696 def __init__(self, level):
655 697 from . import zstd
656 698
657 699 self._zstd = zstd
658 700 cctx = zstd.ZstdCompressor(level=level)
659 701 self._compressor = cctx.compressobj()
660 702
661 703 def encode(self, data):
662 704 return self._compressor.compress(data)
663 705
664 706 def flush(self):
665 707 # COMPRESSOBJ_FLUSH_BLOCK flushes all data previously fed into the
666 708 # compressor and allows a decompressor to access all encoded data
667 709 # up to this point.
668 710 return self._compressor.flush(self._zstd.COMPRESSOBJ_FLUSH_BLOCK)
669 711
670 712 def finish(self):
671 713 res = self._compressor.flush(self._zstd.COMPRESSOBJ_FLUSH_FINISH)
672 714 self._compressor = None
673 715 return res
674 716
675 717 class zstd8mbencoder(zstdbaseencoder):
676 718 def __init__(self, ui):
677 719 super(zstd8mbencoder, self).__init__(3)
678 720
679 721 class zstdbasedecoder(object):
680 722 def __init__(self, maxwindowsize):
681 723 from . import zstd
682 724 dctx = zstd.ZstdDecompressor(max_window_size=maxwindowsize)
683 725 self._decompressor = dctx.decompressobj()
684 726
685 727 def decode(self, data):
686 728 return self._decompressor.decompress(data)
687 729
688 730 class zstd8mbdecoder(zstdbasedecoder):
689 731 def __init__(self, ui, extraobjs):
690 732 if extraobjs:
691 733 raise error.Abort(_('zstd8mb decoder received unexpected '
692 734 'additional values'))
693 735
694 736 super(zstd8mbdecoder, self).__init__(maxwindowsize=8 * 1048576)
695 737
696 738 # We lazily populate this to avoid excessive module imports when importing
697 739 # this module.
698 740 STREAM_ENCODERS = {}
699 741 STREAM_ENCODERS_ORDER = []
700 742
701 743 def populatestreamencoders():
702 744 if STREAM_ENCODERS:
703 745 return
704 746
705 747 try:
706 748 from . import zstd
707 749 zstd.__version__
708 750 except ImportError:
709 751 zstd = None
710 752
711 753 # zstandard is fastest and is preferred.
712 754 if zstd:
713 755 STREAM_ENCODERS[b'zstd-8mb'] = (zstd8mbencoder, zstd8mbdecoder)
714 756 STREAM_ENCODERS_ORDER.append(b'zstd-8mb')
715 757
716 758 STREAM_ENCODERS[b'zlib'] = (zlibencoder, zlibdecoder)
717 759 STREAM_ENCODERS_ORDER.append(b'zlib')
718 760
719 761 STREAM_ENCODERS[b'identity'] = (identityencoder, identitydecoder)
720 762 STREAM_ENCODERS_ORDER.append(b'identity')
721 763
722 764 class stream(object):
723 765 """Represents a logical unidirectional series of frames."""
724 766
725 767 def __init__(self, streamid, active=False):
726 768 self.streamid = streamid
727 769 self._active = active
728 770
729 771 def makeframe(self, requestid, typeid, flags, payload):
730 772 """Create a frame to be sent out over this stream.
731 773
732 774 Only returns the frame instance. Does not actually send it.
733 775 """
734 776 streamflags = 0
735 777 if not self._active:
736 778 streamflags |= STREAM_FLAG_BEGIN_STREAM
737 779 self._active = True
738 780
739 781 return makeframe(requestid, self.streamid, streamflags, typeid, flags,
740 782 payload)
741 783
742 784 class inputstream(stream):
743 785 """Represents a stream used for receiving data."""
744 786
745 787 def __init__(self, streamid, active=False):
746 788 super(inputstream, self).__init__(streamid, active=active)
747 789 self._decoder = None
748 790
749 791 def setdecoder(self, ui, name, extraobjs):
750 792 """Set the decoder for this stream.
751 793
752 794 Receives the stream profile name and any additional CBOR objects
753 795 decoded from the stream encoding settings frame payloads.
754 796 """
755 797 if name not in STREAM_ENCODERS:
756 798 raise error.Abort(_('unknown stream decoder: %s') % name)
757 799
758 800 self._decoder = STREAM_ENCODERS[name][1](ui, extraobjs)
759 801
760 802 def decode(self, data):
761 803 # Default is identity decoder. We don't bother instantiating one
762 804 # because it is trivial.
763 805 if not self._decoder:
764 806 return data
765 807
766 808 return self._decoder.decode(data)
767 809
768 810 def flush(self):
769 811 if not self._decoder:
770 812 return b''
771 813
772 814 return self._decoder.flush()
773 815
774 816 class outputstream(stream):
775 817 """Represents a stream used for sending data."""
776 818
777 819 def __init__(self, streamid, active=False):
778 820 super(outputstream, self).__init__(streamid, active=active)
821 self.streamsettingssent = False
779 822 self._encoder = None
823 self._encodername = None
780 824
781 825 def setencoder(self, ui, name):
782 826 """Set the encoder for this stream.
783 827
784 828 Receives the stream profile name.
785 829 """
786 830 if name not in STREAM_ENCODERS:
787 831 raise error.Abort(_('unknown stream encoder: %s') % name)
788 832
789 833 self._encoder = STREAM_ENCODERS[name][0](ui)
834 self._encodername = name
790 835
791 836 def encode(self, data):
792 837 if not self._encoder:
793 838 return data
794 839
795 840 return self._encoder.encode(data)
796 841
797 842 def flush(self):
798 843 if not self._encoder:
799 844 return b''
800 845
801 846 return self._encoder.flush()
802 847
803 848 def finish(self):
804 849 if not self._encoder:
805 850 return b''
806 851
807 852 self._encoder.finish()
808 853
854 def makeframe(self, requestid, typeid, flags, payload,
855 encoded=False):
856 """Create a frame to be sent out over this stream.
857
858 Only returns the frame instance. Does not actually send it.
859 """
860 streamflags = 0
861 if not self._active:
862 streamflags |= STREAM_FLAG_BEGIN_STREAM
863 self._active = True
864
865 if encoded:
866 if not self.streamsettingssent:
867 raise error.ProgrammingError(
868 b'attempting to send encoded frame without sending stream '
869 b'settings')
870
871 streamflags |= STREAM_FLAG_ENCODING_APPLIED
872
873 if (typeid == FRAME_TYPE_STREAM_SETTINGS
874 and flags & FLAG_STREAM_ENCODING_SETTINGS_EOS):
875 self.streamsettingssent = True
876
877 return makeframe(requestid, self.streamid, streamflags, typeid, flags,
878 payload)
879
880 def makestreamsettingsframe(self, requestid):
881 """Create a stream settings frame for this stream.
882
883 Returns frame data or None if no stream settings frame is needed or has
884 already been sent.
885 """
886 if not self._encoder or self.streamsettingssent:
887 return None
888
889 payload = b''.join(cborutil.streamencode(self._encodername))
890 return self.makeframe(requestid, FRAME_TYPE_STREAM_SETTINGS,
891 FLAG_STREAM_ENCODING_SETTINGS_EOS, payload)
892
809 893 def ensureserverstream(stream):
810 894 if stream.streamid % 2:
811 895 raise error.ProgrammingError('server should only write to even '
812 896 'numbered streams; %d is not even' %
813 897 stream.streamid)
814 898
815 899 DEFAULT_PROTOCOL_SETTINGS = {
816 900 'contentencodings': [b'identity'],
817 901 }
818 902
819 903 class serverreactor(object):
820 904 """Holds state of a server handling frame-based protocol requests.
821 905
822 906 This class is the "brain" of the unified frame-based protocol server
823 907 component. While the protocol is stateless from the perspective of
824 908 requests/commands, something needs to track which frames have been
825 909 received, what frames to expect, etc. This class is that thing.
826 910
827 911 Instances are modeled as a state machine of sorts. Instances are also
828 912 reactionary to external events. The point of this class is to encapsulate
829 913 the state of the connection and the exchange of frames, not to perform
830 914 work. Instead, callers tell this class when something occurs, like a
831 915 frame arriving. If that activity is worthy of a follow-up action (say
832 916 *run a command*), the return value of that handler will say so.
833 917
834 918 I/O and CPU intensive operations are purposefully delegated outside of
835 919 this class.
836 920
837 921 Consumers are expected to tell instances when events occur. They do so by
838 922 calling the various ``on*`` methods. These methods return a 2-tuple
839 923 describing any follow-up action(s) to take. The first element is the
840 924 name of an action to perform. The second is a data structure (usually
841 925 a dict) specific to that action that contains more information. e.g.
842 926 if the server wants to send frames back to the client, the data structure
843 927 will contain a reference to those frames.
844 928
845 929 Valid actions that consumers can be instructed to take are:
846 930
847 931 sendframes
848 932 Indicates that frames should be sent to the client. The ``framegen``
849 933 key contains a generator of frames that should be sent. The server
850 934 assumes that all frames are sent to the client.
851 935
852 936 error
853 937 Indicates that an error occurred. Consumer should probably abort.
854 938
855 939 runcommand
856 940 Indicates that the consumer should run a wire protocol command. Details
857 941 of the command to run are given in the data structure.
858 942
859 943 wantframe
860 944 Indicates that nothing of interest happened and the server is waiting on
861 945 more frames from the client before anything interesting can be done.
862 946
863 947 noop
864 948 Indicates no additional action is required.
865 949
866 950 Known Issues
867 951 ------------
868 952
869 953 There are no limits to the number of partially received commands or their
870 954 size. A malicious client could stream command request data and exhaust the
871 955 server's memory.
872 956
873 957 Partially received commands are not acted upon when end of input is
874 958 reached. Should the server error if it receives a partial request?
875 959 Should the client send a message to abort a partially transmitted request
876 960 to facilitate graceful shutdown?
877 961
878 962 Active requests that haven't been responded to aren't tracked. This means
879 963 that if we receive a command and instruct its dispatch, another command
880 964 with its request ID can come in over the wire and there will be a race
881 965 between who responds to what.
882 966 """
883 967
884 968 def __init__(self, ui, deferoutput=False):
885 969 """Construct a new server reactor.
886 970
887 971 ``deferoutput`` can be used to indicate that no output frames should be
888 972 instructed to be sent until input has been exhausted. In this mode,
889 973 events that would normally generate output frames (such as a command
890 974 response being ready) will instead defer instructing the consumer to
891 975 send those frames. This is useful for half-duplex transports where the
892 976 sender cannot receive until all data has been transmitted.
893 977 """
894 978 self._ui = ui
895 979 self._deferoutput = deferoutput
896 980 self._state = 'initial'
897 981 self._nextoutgoingstreamid = 2
898 982 self._bufferedframegens = []
899 983 # stream id -> stream instance for all active streams from the client.
900 984 self._incomingstreams = {}
901 985 self._outgoingstreams = {}
902 986 # request id -> dict of commands that are actively being received.
903 987 self._receivingcommands = {}
904 988 # Request IDs that have been received and are actively being processed.
905 989 # Once all output for a request has been sent, it is removed from this
906 990 # set.
907 991 self._activecommands = set()
908 992
909 993 self._protocolsettingsdecoder = None
910 994
911 995 # Sender protocol settings are optional. Set implied default values.
912 996 self._sendersettings = dict(DEFAULT_PROTOCOL_SETTINGS)
913 997
914 998 populatestreamencoders()
915 999
916 1000 def onframerecv(self, frame):
917 1001 """Process a frame that has been received off the wire.
918 1002
919 1003 Returns a dict with an ``action`` key that details what action,
920 1004 if any, the consumer should take next.
921 1005 """
922 1006 if not frame.streamid % 2:
923 1007 self._state = 'errored'
924 1008 return self._makeerrorresult(
925 1009 _('received frame with even numbered stream ID: %d') %
926 1010 frame.streamid)
927 1011
928 1012 if frame.streamid not in self._incomingstreams:
929 1013 if not frame.streamflags & STREAM_FLAG_BEGIN_STREAM:
930 1014 self._state = 'errored'
931 1015 return self._makeerrorresult(
932 1016 _('received frame on unknown inactive stream without '
933 1017 'beginning of stream flag set'))
934 1018
935 1019 self._incomingstreams[frame.streamid] = inputstream(frame.streamid)
936 1020
937 1021 if frame.streamflags & STREAM_FLAG_ENCODING_APPLIED:
938 1022 # TODO handle decoding frames
939 1023 self._state = 'errored'
940 1024 raise error.ProgrammingError('support for decoding stream payloads '
941 1025 'not yet implemented')
942 1026
943 1027 if frame.streamflags & STREAM_FLAG_END_STREAM:
944 1028 del self._incomingstreams[frame.streamid]
945 1029
946 1030 handlers = {
947 1031 'initial': self._onframeinitial,
948 1032 'protocol-settings-receiving': self._onframeprotocolsettings,
949 1033 'idle': self._onframeidle,
950 1034 'command-receiving': self._onframecommandreceiving,
951 1035 'errored': self._onframeerrored,
952 1036 }
953 1037
954 1038 meth = handlers.get(self._state)
955 1039 if not meth:
956 1040 raise error.ProgrammingError('unhandled state: %s' % self._state)
957 1041
958 1042 return meth(frame)
959 1043
960 1044 def oncommandresponsereadyobjects(self, stream, requestid, objs):
961 1045 """Signal that objects are ready to be sent to the client.
962 1046
963 1047 ``objs`` is an iterable of objects (typically a generator) that will
964 1048 be encoded via CBOR and added to frames, which will be sent to the
965 1049 client.
966 1050 """
967 1051 ensureserverstream(stream)
968 1052
969 1053 # A more robust solution would be to check for objs.{next,__next__}.
970 1054 if isinstance(objs, list):
971 1055 objs = iter(objs)
972 1056
973 1057 # We need to take care over exception handling. Uncaught exceptions
974 1058 # when generating frames could lead to premature end of the frame
975 1059 # stream and the possibility of the server or client process getting
976 1060 # in a bad state.
977 1061 #
978 1062 # Keep in mind that if ``objs`` is a generator, advancing it could
979 1063 # raise exceptions that originated in e.g. wire protocol command
980 1064 # functions. That is why we differentiate between exceptions raised
981 1065 # when iterating versus other exceptions that occur.
982 1066 #
983 1067 # In all cases, when the function finishes, the request is fully
984 1068 # handled and no new frames for it should be seen.
985 1069
986 1070 def sendframes():
987 1071 emitted = False
988 1072 alternatelocationsent = False
989 1073 emitter = bufferingcommandresponseemitter(stream, requestid)
990 1074 while True:
991 1075 try:
992 1076 o = next(objs)
993 1077 except StopIteration:
994 1078 for frame in emitter.send(None):
995 1079 yield frame
996 1080
997 1081 if emitted:
998 yield createcommandresponseeosframe(stream, requestid)
1082 for frame in createcommandresponseeosframes(
1083 stream, requestid):
1084 yield frame
999 1085 break
1000 1086
1001 1087 except error.WireprotoCommandError as e:
1002 1088 for frame in createcommanderrorresponse(
1003 1089 stream, requestid, e.message, e.messageargs):
1004 1090 yield frame
1005 1091 break
1006 1092
1007 1093 except Exception as e:
1008 1094 for frame in createerrorframe(
1009 1095 stream, requestid, '%s' % stringutil.forcebytestr(e),
1010 1096 errtype='server'):
1011 1097
1012 1098 yield frame
1013 1099
1014 1100 break
1015 1101
1016 1102 try:
1017 1103 # Alternate location responses can only be the first and
1018 1104 # only object in the output stream.
1019 1105 if isinstance(o, wireprototypes.alternatelocationresponse):
1020 1106 if emitted:
1021 1107 raise error.ProgrammingError(
1022 1108 'alternatelocationresponse seen after initial '
1023 1109 'output object')
1024 1110
1111 frame = stream.makestreamsettingsframe(requestid)
1112 if frame:
1113 yield frame
1114
1025 1115 yield createalternatelocationresponseframe(
1026 1116 stream, requestid, o)
1027 1117
1028 1118 alternatelocationsent = True
1029 1119 emitted = True
1030 1120 continue
1031 1121
1032 1122 if alternatelocationsent:
1033 1123 raise error.ProgrammingError(
1034 1124 'object follows alternatelocationresponse')
1035 1125
1036 1126 if not emitted:
1037 yield createcommandresponseokframe(stream, requestid)
1127 # Frame is optional.
1128 frame = stream.makestreamsettingsframe(requestid)
1129 if frame:
1130 yield frame
1131
1132 # May be None if empty frame (due to encoding).
1133 frame = createcommandresponseokframe(stream, requestid)
1134 if frame:
1135 yield frame
1136
1038 1137 emitted = True
1039 1138
1040 1139 # Objects emitted by command functions can be serializable
1041 1140 # data structures or special types.
1042 1141 # TODO consider extracting the content normalization to a
1043 1142 # standalone function, as it may be useful for e.g. cachers.
1044 1143
1045 1144 # A pre-encoded object is sent directly to the emitter.
1046 1145 if isinstance(o, wireprototypes.encodedresponse):
1047 1146 for frame in emitter.send(o.data):
1048 1147 yield frame
1049 1148
1050 1149 # A regular object is CBOR encoded.
1051 1150 else:
1052 1151 for chunk in cborutil.streamencode(o):
1053 1152 for frame in emitter.send(chunk):
1054 1153 yield frame
1055 1154
1056 1155 except Exception as e:
1057 1156 for frame in createerrorframe(stream, requestid,
1058 1157 '%s' % e,
1059 1158 errtype='server'):
1060 1159 yield frame
1061 1160
1062 1161 break
1063 1162
1064 1163 self._activecommands.remove(requestid)
1065 1164
1066 1165 return self._handlesendframes(sendframes())
1067 1166
1068 1167 def oninputeof(self):
1069 1168 """Signals that end of input has been received.
1070 1169
1071 1170 No more frames will be received. All pending activity should be
1072 1171 completed.
1073 1172 """
1074 1173 # TODO should we do anything about in-flight commands?
1075 1174
1076 1175 if not self._deferoutput or not self._bufferedframegens:
1077 1176 return 'noop', {}
1078 1177
1079 1178 # If we buffered all our responses, emit those.
1080 1179 def makegen():
1081 1180 for gen in self._bufferedframegens:
1082 1181 for frame in gen:
1083 1182 yield frame
1084 1183
1085 1184 return 'sendframes', {
1086 1185 'framegen': makegen(),
1087 1186 }
1088 1187
1089 1188 def _handlesendframes(self, framegen):
1090 1189 if self._deferoutput:
1091 1190 self._bufferedframegens.append(framegen)
1092 1191 return 'noop', {}
1093 1192 else:
1094 1193 return 'sendframes', {
1095 1194 'framegen': framegen,
1096 1195 }
1097 1196
1098 1197 def onservererror(self, stream, requestid, msg):
1099 1198 ensureserverstream(stream)
1100 1199
1101 1200 def sendframes():
1102 1201 for frame in createerrorframe(stream, requestid, msg,
1103 1202 errtype='server'):
1104 1203 yield frame
1105 1204
1106 1205 self._activecommands.remove(requestid)
1107 1206
1108 1207 return self._handlesendframes(sendframes())
1109 1208
1110 1209 def oncommanderror(self, stream, requestid, message, args=None):
1111 1210 """Called when a command encountered an error before sending output."""
1112 1211 ensureserverstream(stream)
1113 1212
1114 1213 def sendframes():
1115 1214 for frame in createcommanderrorresponse(stream, requestid, message,
1116 1215 args):
1117 1216 yield frame
1118 1217
1119 1218 self._activecommands.remove(requestid)
1120 1219
1121 1220 return self._handlesendframes(sendframes())
1122 1221
1123 1222 def makeoutputstream(self):
1124 """Create a stream to be used for sending data to the client."""
1223 """Create a stream to be used for sending data to the client.
1224
1225 If this is called before protocol settings frames are received, we
1226 don't know what stream encodings are supported by the client and
1227 we will default to identity.
1228 """
1125 1229 streamid = self._nextoutgoingstreamid
1126 1230 self._nextoutgoingstreamid += 2
1127 1231
1128 1232 s = outputstream(streamid)
1129 1233 self._outgoingstreams[streamid] = s
1130 1234
1235 # Always use the *server's* preferred encoder over the client's,
1236 # as servers have more to lose from sub-optimal encoders being used.
1237 for name in STREAM_ENCODERS_ORDER:
1238 if name in self._sendersettings['contentencodings']:
1239 s.setencoder(self._ui, name)
1240 break
1241
1131 1242 return s
1132 1243
1133 1244 def _makeerrorresult(self, msg):
1134 1245 return 'error', {
1135 1246 'message': msg,
1136 1247 }
1137 1248
1138 1249 def _makeruncommandresult(self, requestid):
1139 1250 entry = self._receivingcommands[requestid]
1140 1251
1141 1252 if not entry['requestdone']:
1142 1253 self._state = 'errored'
1143 1254 raise error.ProgrammingError('should not be called without '
1144 1255 'requestdone set')
1145 1256
1146 1257 del self._receivingcommands[requestid]
1147 1258
1148 1259 if self._receivingcommands:
1149 1260 self._state = 'command-receiving'
1150 1261 else:
1151 1262 self._state = 'idle'
1152 1263
1153 1264 # Decode the payloads as CBOR.
1154 1265 entry['payload'].seek(0)
1155 1266 request = cborutil.decodeall(entry['payload'].getvalue())[0]
1156 1267
1157 1268 if b'name' not in request:
1158 1269 self._state = 'errored'
1159 1270 return self._makeerrorresult(
1160 1271 _('command request missing "name" field'))
1161 1272
1162 1273 if b'args' not in request:
1163 1274 request[b'args'] = {}
1164 1275
1165 1276 assert requestid not in self._activecommands
1166 1277 self._activecommands.add(requestid)
1167 1278
1168 1279 return 'runcommand', {
1169 1280 'requestid': requestid,
1170 1281 'command': request[b'name'],
1171 1282 'args': request[b'args'],
1172 1283 'redirect': request.get(b'redirect'),
1173 1284 'data': entry['data'].getvalue() if entry['data'] else None,
1174 1285 }
1175 1286
1176 1287 def _makewantframeresult(self):
1177 1288 return 'wantframe', {
1178 1289 'state': self._state,
1179 1290 }
1180 1291
1181 1292 def _validatecommandrequestframe(self, frame):
1182 1293 new = frame.flags & FLAG_COMMAND_REQUEST_NEW
1183 1294 continuation = frame.flags & FLAG_COMMAND_REQUEST_CONTINUATION
1184 1295
1185 1296 if new and continuation:
1186 1297 self._state = 'errored'
1187 1298 return self._makeerrorresult(
1188 1299 _('received command request frame with both new and '
1189 1300 'continuation flags set'))
1190 1301
1191 1302 if not new and not continuation:
1192 1303 self._state = 'errored'
1193 1304 return self._makeerrorresult(
1194 1305 _('received command request frame with neither new nor '
1195 1306 'continuation flags set'))
1196 1307
1197 1308 def _onframeinitial(self, frame):
1198 1309 # Called when we receive a frame when in the "initial" state.
1199 1310 if frame.typeid == FRAME_TYPE_SENDER_PROTOCOL_SETTINGS:
1200 1311 self._state = 'protocol-settings-receiving'
1201 1312 self._protocolsettingsdecoder = cborutil.bufferingdecoder()
1202 1313 return self._onframeprotocolsettings(frame)
1203 1314
1204 1315 elif frame.typeid == FRAME_TYPE_COMMAND_REQUEST:
1205 1316 self._state = 'idle'
1206 1317 return self._onframeidle(frame)
1207 1318
1208 1319 else:
1209 1320 self._state = 'errored'
1210 1321 return self._makeerrorresult(
1211 1322 _('expected sender protocol settings or command request '
1212 1323 'frame; got %d') % frame.typeid)
1213 1324
1214 1325 def _onframeprotocolsettings(self, frame):
1215 1326 assert self._state == 'protocol-settings-receiving'
1216 1327 assert self._protocolsettingsdecoder is not None
1217 1328
1218 1329 if frame.typeid != FRAME_TYPE_SENDER_PROTOCOL_SETTINGS:
1219 1330 self._state = 'errored'
1220 1331 return self._makeerrorresult(
1221 1332 _('expected sender protocol settings frame; got %d') %
1222 1333 frame.typeid)
1223 1334
1224 1335 more = frame.flags & FLAG_SENDER_PROTOCOL_SETTINGS_CONTINUATION
1225 1336 eos = frame.flags & FLAG_SENDER_PROTOCOL_SETTINGS_EOS
1226 1337
1227 1338 if more and eos:
1228 1339 self._state = 'errored'
1229 1340 return self._makeerrorresult(
1230 1341 _('sender protocol settings frame cannot have both '
1231 1342 'continuation and end of stream flags set'))
1232 1343
1233 1344 if not more and not eos:
1234 1345 self._state = 'errored'
1235 1346 return self._makeerrorresult(
1236 1347 _('sender protocol settings frame must have continuation or '
1237 1348 'end of stream flag set'))
1238 1349
1239 1350 # TODO establish limits for maximum amount of data that can be
1240 1351 # buffered.
1241 1352 try:
1242 1353 self._protocolsettingsdecoder.decode(frame.payload)
1243 1354 except Exception as e:
1244 1355 self._state = 'errored'
1245 1356 return self._makeerrorresult(
1246 1357 _('error decoding CBOR from sender protocol settings frame: %s')
1247 1358 % stringutil.forcebytestr(e))
1248 1359
1249 1360 if more:
1250 1361 return self._makewantframeresult()
1251 1362
1252 1363 assert eos
1253 1364
1254 1365 decoded = self._protocolsettingsdecoder.getavailable()
1255 1366 self._protocolsettingsdecoder = None
1256 1367
1257 1368 if not decoded:
1258 1369 self._state = 'errored'
1259 1370 return self._makeerrorresult(
1260 1371 _('sender protocol settings frame did not contain CBOR data'))
1261 1372 elif len(decoded) > 1:
1262 1373 self._state = 'errored'
1263 1374 return self._makeerrorresult(
1264 1375 _('sender protocol settings frame contained multiple CBOR '
1265 1376 'values'))
1266 1377
1267 1378 d = decoded[0]
1268 1379
1269 1380 if b'contentencodings' in d:
1270 1381 self._sendersettings['contentencodings'] = d[b'contentencodings']
1271 1382
1272 1383 self._state = 'idle'
1273 1384
1274 1385 return self._makewantframeresult()
1275 1386
1276 1387 def _onframeidle(self, frame):
1277 1388 # The only frame type that should be received in this state is a
1278 1389 # command request.
1279 1390 if frame.typeid != FRAME_TYPE_COMMAND_REQUEST:
1280 1391 self._state = 'errored'
1281 1392 return self._makeerrorresult(
1282 1393 _('expected command request frame; got %d') % frame.typeid)
1283 1394
1284 1395 res = self._validatecommandrequestframe(frame)
1285 1396 if res:
1286 1397 return res
1287 1398
1288 1399 if frame.requestid in self._receivingcommands:
1289 1400 self._state = 'errored'
1290 1401 return self._makeerrorresult(
1291 1402 _('request with ID %d already received') % frame.requestid)
1292 1403
1293 1404 if frame.requestid in self._activecommands:
1294 1405 self._state = 'errored'
1295 1406 return self._makeerrorresult(
1296 1407 _('request with ID %d is already active') % frame.requestid)
1297 1408
1298 1409 new = frame.flags & FLAG_COMMAND_REQUEST_NEW
1299 1410 moreframes = frame.flags & FLAG_COMMAND_REQUEST_MORE_FRAMES
1300 1411 expectingdata = frame.flags & FLAG_COMMAND_REQUEST_EXPECT_DATA
1301 1412
1302 1413 if not new:
1303 1414 self._state = 'errored'
1304 1415 return self._makeerrorresult(
1305 1416 _('received command request frame without new flag set'))
1306 1417
1307 1418 payload = util.bytesio()
1308 1419 payload.write(frame.payload)
1309 1420
1310 1421 self._receivingcommands[frame.requestid] = {
1311 1422 'payload': payload,
1312 1423 'data': None,
1313 1424 'requestdone': not moreframes,
1314 1425 'expectingdata': bool(expectingdata),
1315 1426 }
1316 1427
1317 1428 # This is the final frame for this request. Dispatch it.
1318 1429 if not moreframes and not expectingdata:
1319 1430 return self._makeruncommandresult(frame.requestid)
1320 1431
1321 1432 assert moreframes or expectingdata
1322 1433 self._state = 'command-receiving'
1323 1434 return self._makewantframeresult()
1324 1435
1325 1436 def _onframecommandreceiving(self, frame):
1326 1437 if frame.typeid == FRAME_TYPE_COMMAND_REQUEST:
1327 1438 # Process new command requests as such.
1328 1439 if frame.flags & FLAG_COMMAND_REQUEST_NEW:
1329 1440 return self._onframeidle(frame)
1330 1441
1331 1442 res = self._validatecommandrequestframe(frame)
1332 1443 if res:
1333 1444 return res
1334 1445
1335 1446 # All other frames should be related to a command that is currently
1336 1447 # receiving but is not active.
1337 1448 if frame.requestid in self._activecommands:
1338 1449 self._state = 'errored'
1339 1450 return self._makeerrorresult(
1340 1451 _('received frame for request that is still active: %d') %
1341 1452 frame.requestid)
1342 1453
1343 1454 if frame.requestid not in self._receivingcommands:
1344 1455 self._state = 'errored'
1345 1456 return self._makeerrorresult(
1346 1457 _('received frame for request that is not receiving: %d') %
1347 1458 frame.requestid)
1348 1459
1349 1460 entry = self._receivingcommands[frame.requestid]
1350 1461
1351 1462 if frame.typeid == FRAME_TYPE_COMMAND_REQUEST:
1352 1463 moreframes = frame.flags & FLAG_COMMAND_REQUEST_MORE_FRAMES
1353 1464 expectingdata = bool(frame.flags & FLAG_COMMAND_REQUEST_EXPECT_DATA)
1354 1465
1355 1466 if entry['requestdone']:
1356 1467 self._state = 'errored'
1357 1468 return self._makeerrorresult(
1358 1469 _('received command request frame when request frames '
1359 1470 'were supposedly done'))
1360 1471
1361 1472 if expectingdata != entry['expectingdata']:
1362 1473 self._state = 'errored'
1363 1474 return self._makeerrorresult(
1364 1475 _('mismatch between expect data flag and previous frame'))
1365 1476
1366 1477 entry['payload'].write(frame.payload)
1367 1478
1368 1479 if not moreframes:
1369 1480 entry['requestdone'] = True
1370 1481
1371 1482 if not moreframes and not expectingdata:
1372 1483 return self._makeruncommandresult(frame.requestid)
1373 1484
1374 1485 return self._makewantframeresult()
1375 1486
1376 1487 elif frame.typeid == FRAME_TYPE_COMMAND_DATA:
1377 1488 if not entry['expectingdata']:
1378 1489 self._state = 'errored'
1379 1490 return self._makeerrorresult(_(
1380 1491 'received command data frame for request that is not '
1381 1492 'expecting data: %d') % frame.requestid)
1382 1493
1383 1494 if entry['data'] is None:
1384 1495 entry['data'] = util.bytesio()
1385 1496
1386 1497 return self._handlecommanddataframe(frame, entry)
1387 1498 else:
1388 1499 self._state = 'errored'
1389 1500 return self._makeerrorresult(_(
1390 1501 'received unexpected frame type: %d') % frame.typeid)
1391 1502
1392 1503 def _handlecommanddataframe(self, frame, entry):
1393 1504 assert frame.typeid == FRAME_TYPE_COMMAND_DATA
1394 1505
1395 1506 # TODO support streaming data instead of buffering it.
1396 1507 entry['data'].write(frame.payload)
1397 1508
1398 1509 if frame.flags & FLAG_COMMAND_DATA_CONTINUATION:
1399 1510 return self._makewantframeresult()
1400 1511 elif frame.flags & FLAG_COMMAND_DATA_EOS:
1401 1512 entry['data'].seek(0)
1402 1513 return self._makeruncommandresult(frame.requestid)
1403 1514 else:
1404 1515 self._state = 'errored'
1405 1516 return self._makeerrorresult(_('command data frame without '
1406 1517 'flags'))
1407 1518
1408 1519 def _onframeerrored(self, frame):
1409 1520 return self._makeerrorresult(_('server already errored'))
1410 1521
1411 1522 class commandrequest(object):
1412 1523 """Represents a request to run a command."""
1413 1524
1414 1525 def __init__(self, requestid, name, args, datafh=None, redirect=None):
1415 1526 self.requestid = requestid
1416 1527 self.name = name
1417 1528 self.args = args
1418 1529 self.datafh = datafh
1419 1530 self.redirect = redirect
1420 1531 self.state = 'pending'
1421 1532
1422 1533 class clientreactor(object):
1423 1534 """Holds state of a client issuing frame-based protocol requests.
1424 1535
1425 1536 This is like ``serverreactor`` but for client-side state.
1426 1537
1427 1538 Each instance is bound to the lifetime of a connection. For persistent
1428 1539 connection transports using e.g. TCP sockets and speaking the raw
1429 1540 framing protocol, there will be a single instance for the lifetime of
1430 1541 the TCP socket. For transports where there are multiple discrete
1431 1542 interactions (say tunneled within in HTTP request), there will be a
1432 1543 separate instance for each distinct interaction.
1433 1544
1434 1545 Consumers are expected to tell instances when events occur by calling
1435 1546 various methods. These methods return a 2-tuple describing any follow-up
1436 1547 action(s) to take. The first element is the name of an action to
1437 1548 perform. The second is a data structure (usually a dict) specific to
1438 1549 that action that contains more information. e.g. if the reactor wants
1439 1550 to send frames to the server, the data structure will contain a reference
1440 1551 to those frames.
1441 1552
1442 1553 Valid actions that consumers can be instructed to take are:
1443 1554
1444 1555 noop
1445 1556 Indicates no additional action is required.
1446 1557
1447 1558 sendframes
1448 1559 Indicates that frames should be sent to the server. The ``framegen``
1449 1560 key contains a generator of frames that should be sent. The reactor
1450 1561 assumes that all frames in this generator are sent to the server.
1451 1562
1452 1563 error
1453 1564 Indicates that an error occurred. The ``message`` key contains an
1454 1565 error message describing the failure.
1455 1566
1456 1567 responsedata
1457 1568 Indicates a response to a previously-issued command was received.
1458 1569
1459 1570 The ``request`` key contains the ``commandrequest`` instance that
1460 1571 represents the request this data is for.
1461 1572
1462 1573 The ``data`` key contains the decoded data from the server.
1463 1574
1464 1575 ``expectmore`` and ``eos`` evaluate to True when more response data
1465 1576 is expected to follow or we're at the end of the response stream,
1466 1577 respectively.
1467 1578 """
1468 1579 def __init__(self, ui, hasmultiplesend=False, buffersends=True,
1469 1580 clientcontentencoders=None):
1470 1581 """Create a new instance.
1471 1582
1472 1583 ``hasmultiplesend`` indicates whether multiple sends are supported
1473 1584 by the transport. When True, it is possible to send commands immediately
1474 1585 instead of buffering until the caller signals an intent to finish a
1475 1586 send operation.
1476 1587
1477 1588 ``buffercommands`` indicates whether sends should be buffered until the
1478 1589 last request has been issued.
1479 1590
1480 1591 ``clientcontentencoders`` is an iterable of content encoders the client
1481 1592 will advertise to the server and that the server can use for encoding
1482 1593 data. If not defined, the client will not advertise content encoders
1483 1594 to the server.
1484 1595 """
1485 1596 self._ui = ui
1486 1597 self._hasmultiplesend = hasmultiplesend
1487 1598 self._buffersends = buffersends
1488 1599 self._clientcontentencoders = clientcontentencoders
1489 1600
1490 1601 self._canissuecommands = True
1491 1602 self._cansend = True
1492 1603 self._protocolsettingssent = False
1493 1604
1494 1605 self._nextrequestid = 1
1495 1606 # We only support a single outgoing stream for now.
1496 1607 self._outgoingstream = outputstream(1)
1497 1608 self._pendingrequests = collections.deque()
1498 1609 self._activerequests = {}
1499 1610 self._incomingstreams = {}
1500 1611 self._streamsettingsdecoders = {}
1501 1612
1502 1613 populatestreamencoders()
1503 1614
1504 1615 def callcommand(self, name, args, datafh=None, redirect=None):
1505 1616 """Request that a command be executed.
1506 1617
1507 1618 Receives the command name, a dict of arguments to pass to the command,
1508 1619 and an optional file object containing the raw data for the command.
1509 1620
1510 1621 Returns a 3-tuple of (request, action, action data).
1511 1622 """
1512 1623 if not self._canissuecommands:
1513 1624 raise error.ProgrammingError('cannot issue new commands')
1514 1625
1515 1626 requestid = self._nextrequestid
1516 1627 self._nextrequestid += 2
1517 1628
1518 1629 request = commandrequest(requestid, name, args, datafh=datafh,
1519 1630 redirect=redirect)
1520 1631
1521 1632 if self._buffersends:
1522 1633 self._pendingrequests.append(request)
1523 1634 return request, 'noop', {}
1524 1635 else:
1525 1636 if not self._cansend:
1526 1637 raise error.ProgrammingError('sends cannot be performed on '
1527 1638 'this instance')
1528 1639
1529 1640 if not self._hasmultiplesend:
1530 1641 self._cansend = False
1531 1642 self._canissuecommands = False
1532 1643
1533 1644 return request, 'sendframes', {
1534 1645 'framegen': self._makecommandframes(request),
1535 1646 }
1536 1647
1537 1648 def flushcommands(self):
1538 1649 """Request that all queued commands be sent.
1539 1650
1540 1651 If any commands are buffered, this will instruct the caller to send
1541 1652 them over the wire. If no commands are buffered it instructs the client
1542 1653 to no-op.
1543 1654
1544 1655 If instances aren't configured for multiple sends, no new command
1545 1656 requests are allowed after this is called.
1546 1657 """
1547 1658 if not self._pendingrequests:
1548 1659 return 'noop', {}
1549 1660
1550 1661 if not self._cansend:
1551 1662 raise error.ProgrammingError('sends cannot be performed on this '
1552 1663 'instance')
1553 1664
1554 1665 # If the instance only allows sending once, mark that we have fired
1555 1666 # our one shot.
1556 1667 if not self._hasmultiplesend:
1557 1668 self._canissuecommands = False
1558 1669 self._cansend = False
1559 1670
1560 1671 def makeframes():
1561 1672 while self._pendingrequests:
1562 1673 request = self._pendingrequests.popleft()
1563 1674 for frame in self._makecommandframes(request):
1564 1675 yield frame
1565 1676
1566 1677 return 'sendframes', {
1567 1678 'framegen': makeframes(),
1568 1679 }
1569 1680
1570 1681 def _makecommandframes(self, request):
1571 1682 """Emit frames to issue a command request.
1572 1683
1573 1684 As a side-effect, update request accounting to reflect its changed
1574 1685 state.
1575 1686 """
1576 1687 self._activerequests[request.requestid] = request
1577 1688 request.state = 'sending'
1578 1689
1579 1690 if not self._protocolsettingssent and self._clientcontentencoders:
1580 1691 self._protocolsettingssent = True
1581 1692
1582 1693 payload = b''.join(cborutil.streamencode({
1583 1694 b'contentencodings': self._clientcontentencoders,
1584 1695 }))
1585 1696
1586 1697 yield self._outgoingstream.makeframe(
1587 1698 requestid=request.requestid,
1588 1699 typeid=FRAME_TYPE_SENDER_PROTOCOL_SETTINGS,
1589 1700 flags=FLAG_SENDER_PROTOCOL_SETTINGS_EOS,
1590 1701 payload=payload)
1591 1702
1592 1703 res = createcommandframes(self._outgoingstream,
1593 1704 request.requestid,
1594 1705 request.name,
1595 1706 request.args,
1596 1707 datafh=request.datafh,
1597 1708 redirect=request.redirect)
1598 1709
1599 1710 for frame in res:
1600 1711 yield frame
1601 1712
1602 1713 request.state = 'sent'
1603 1714
1604 1715 def onframerecv(self, frame):
1605 1716 """Process a frame that has been received off the wire.
1606 1717
1607 1718 Returns a 2-tuple of (action, meta) describing further action the
1608 1719 caller needs to take as a result of receiving this frame.
1609 1720 """
1610 1721 if frame.streamid % 2:
1611 1722 return 'error', {
1612 1723 'message': (
1613 1724 _('received frame with odd numbered stream ID: %d') %
1614 1725 frame.streamid),
1615 1726 }
1616 1727
1617 1728 if frame.streamid not in self._incomingstreams:
1618 1729 if not frame.streamflags & STREAM_FLAG_BEGIN_STREAM:
1619 1730 return 'error', {
1620 1731 'message': _('received frame on unknown stream '
1621 1732 'without beginning of stream flag set'),
1622 1733 }
1623 1734
1624 1735 self._incomingstreams[frame.streamid] = inputstream(
1625 1736 frame.streamid)
1626 1737
1627 1738 stream = self._incomingstreams[frame.streamid]
1628 1739
1629 1740 # If the payload is encoded, ask the stream to decode it. We
1630 1741 # merely substitute the decoded result into the frame payload as
1631 1742 # if it had been transferred all along.
1632 1743 if frame.streamflags & STREAM_FLAG_ENCODING_APPLIED:
1633 1744 frame.payload = stream.decode(frame.payload)
1634 1745
1635 1746 if frame.streamflags & STREAM_FLAG_END_STREAM:
1636 1747 del self._incomingstreams[frame.streamid]
1637 1748
1638 1749 if frame.typeid == FRAME_TYPE_STREAM_SETTINGS:
1639 1750 return self._onstreamsettingsframe(frame)
1640 1751
1641 1752 if frame.requestid not in self._activerequests:
1642 1753 return 'error', {
1643 1754 'message': (_('received frame for inactive request ID: %d') %
1644 1755 frame.requestid),
1645 1756 }
1646 1757
1647 1758 request = self._activerequests[frame.requestid]
1648 1759 request.state = 'receiving'
1649 1760
1650 1761 handlers = {
1651 1762 FRAME_TYPE_COMMAND_RESPONSE: self._oncommandresponseframe,
1652 1763 FRAME_TYPE_ERROR_RESPONSE: self._onerrorresponseframe,
1653 1764 }
1654 1765
1655 1766 meth = handlers.get(frame.typeid)
1656 1767 if not meth:
1657 1768 raise error.ProgrammingError('unhandled frame type: %d' %
1658 1769 frame.typeid)
1659 1770
1660 1771 return meth(request, frame)
1661 1772
1662 1773 def _onstreamsettingsframe(self, frame):
1663 1774 assert frame.typeid == FRAME_TYPE_STREAM_SETTINGS
1664 1775
1665 1776 more = frame.flags & FLAG_STREAM_ENCODING_SETTINGS_CONTINUATION
1666 1777 eos = frame.flags & FLAG_STREAM_ENCODING_SETTINGS_EOS
1667 1778
1668 1779 if more and eos:
1669 1780 return 'error', {
1670 1781 'message': (_('stream encoding settings frame cannot have both '
1671 1782 'continuation and end of stream flags set')),
1672 1783 }
1673 1784
1674 1785 if not more and not eos:
1675 1786 return 'error', {
1676 1787 'message': _('stream encoding settings frame must have '
1677 1788 'continuation or end of stream flag set'),
1678 1789 }
1679 1790
1680 1791 if frame.streamid not in self._streamsettingsdecoders:
1681 1792 decoder = cborutil.bufferingdecoder()
1682 1793 self._streamsettingsdecoders[frame.streamid] = decoder
1683 1794
1684 1795 decoder = self._streamsettingsdecoders[frame.streamid]
1685 1796
1686 1797 try:
1687 1798 decoder.decode(frame.payload)
1688 1799 except Exception as e:
1689 1800 return 'error', {
1690 1801 'message': (_('error decoding CBOR from stream encoding '
1691 1802 'settings frame: %s') %
1692 1803 stringutil.forcebytestr(e)),
1693 1804 }
1694 1805
1695 1806 if more:
1696 1807 return 'noop', {}
1697 1808
1698 1809 assert eos
1699 1810
1700 1811 decoded = decoder.getavailable()
1701 1812 del self._streamsettingsdecoders[frame.streamid]
1702 1813
1703 1814 if not decoded:
1704 1815 return 'error', {
1705 1816 'message': _('stream encoding settings frame did not contain '
1706 1817 'CBOR data'),
1707 1818 }
1708 1819
1709 1820 try:
1710 1821 self._incomingstreams[frame.streamid].setdecoder(self._ui,
1711 1822 decoded[0],
1712 1823 decoded[1:])
1713 1824 except Exception as e:
1714 1825 return 'error', {
1715 1826 'message': (_('error setting stream decoder: %s') %
1716 1827 stringutil.forcebytestr(e)),
1717 1828 }
1718 1829
1719 1830 return 'noop', {}
1720 1831
1721 1832 def _oncommandresponseframe(self, request, frame):
1722 1833 if frame.flags & FLAG_COMMAND_RESPONSE_EOS:
1723 1834 request.state = 'received'
1724 1835 del self._activerequests[request.requestid]
1725 1836
1726 1837 return 'responsedata', {
1727 1838 'request': request,
1728 1839 'expectmore': frame.flags & FLAG_COMMAND_RESPONSE_CONTINUATION,
1729 1840 'eos': frame.flags & FLAG_COMMAND_RESPONSE_EOS,
1730 1841 'data': frame.payload,
1731 1842 }
1732 1843
1733 1844 def _onerrorresponseframe(self, request, frame):
1734 1845 request.state = 'errored'
1735 1846 del self._activerequests[request.requestid]
1736 1847
1737 1848 # The payload should be a CBOR map.
1738 1849 m = cborutil.decodeall(frame.payload)[0]
1739 1850
1740 1851 return 'error', {
1741 1852 'request': request,
1742 1853 'type': m['type'],
1743 1854 'message': m['message'],
1744 1855 }
@@ -1,1187 +1,1192 b''
1 1 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
2 2 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 3 #
4 4 # This software may be used and distributed according to the terms of the
5 5 # GNU General Public License version 2 or any later version.
6 6
7 7 from __future__ import absolute_import
8 8
9 9 import contextlib
10 10 import hashlib
11 11
12 12 from .i18n import _
13 13 from .node import (
14 14 hex,
15 15 nullid,
16 16 )
17 17 from . import (
18 18 discovery,
19 19 encoding,
20 20 error,
21 21 narrowspec,
22 22 pycompat,
23 23 wireprotoframing,
24 24 wireprototypes,
25 25 )
26 26 from .utils import (
27 27 cborutil,
28 28 interfaceutil,
29 29 stringutil,
30 30 )
31 31
32 32 FRAMINGTYPE = b'application/mercurial-exp-framing-0006'
33 33
34 34 HTTP_WIREPROTO_V2 = wireprototypes.HTTP_WIREPROTO_V2
35 35
36 36 COMMANDS = wireprototypes.commanddict()
37 37
38 38 # Value inserted into cache key computation function. Change the value to
39 39 # force new cache keys for every command request. This should be done when
40 40 # there is a change to how caching works, etc.
41 41 GLOBAL_CACHE_VERSION = 1
42 42
43 43 def handlehttpv2request(rctx, req, res, checkperm, urlparts):
44 44 from .hgweb import common as hgwebcommon
45 45
46 46 # URL space looks like: <permissions>/<command>, where <permission> can
47 47 # be ``ro`` or ``rw`` to signal read-only or read-write, respectively.
48 48
49 49 # Root URL does nothing meaningful... yet.
50 50 if not urlparts:
51 51 res.status = b'200 OK'
52 52 res.headers[b'Content-Type'] = b'text/plain'
53 53 res.setbodybytes(_('HTTP version 2 API handler'))
54 54 return
55 55
56 56 if len(urlparts) == 1:
57 57 res.status = b'404 Not Found'
58 58 res.headers[b'Content-Type'] = b'text/plain'
59 59 res.setbodybytes(_('do not know how to process %s\n') %
60 60 req.dispatchpath)
61 61 return
62 62
63 63 permission, command = urlparts[0:2]
64 64
65 65 if permission not in (b'ro', b'rw'):
66 66 res.status = b'404 Not Found'
67 67 res.headers[b'Content-Type'] = b'text/plain'
68 68 res.setbodybytes(_('unknown permission: %s') % permission)
69 69 return
70 70
71 71 if req.method != 'POST':
72 72 res.status = b'405 Method Not Allowed'
73 73 res.headers[b'Allow'] = b'POST'
74 74 res.setbodybytes(_('commands require POST requests'))
75 75 return
76 76
77 77 # At some point we'll want to use our own API instead of recycling the
78 78 # behavior of version 1 of the wire protocol...
79 79 # TODO return reasonable responses - not responses that overload the
80 80 # HTTP status line message for error reporting.
81 81 try:
82 82 checkperm(rctx, req, 'pull' if permission == b'ro' else 'push')
83 83 except hgwebcommon.ErrorResponse as e:
84 84 res.status = hgwebcommon.statusmessage(e.code, pycompat.bytestr(e))
85 85 for k, v in e.headers:
86 86 res.headers[k] = v
87 87 res.setbodybytes('permission denied')
88 88 return
89 89
90 90 # We have a special endpoint to reflect the request back at the client.
91 91 if command == b'debugreflect':
92 92 _processhttpv2reflectrequest(rctx.repo.ui, rctx.repo, req, res)
93 93 return
94 94
95 95 # Extra commands that we handle that aren't really wire protocol
96 96 # commands. Think extra hard before making this hackery available to
97 97 # extension.
98 98 extracommands = {'multirequest'}
99 99
100 100 if command not in COMMANDS and command not in extracommands:
101 101 res.status = b'404 Not Found'
102 102 res.headers[b'Content-Type'] = b'text/plain'
103 103 res.setbodybytes(_('unknown wire protocol command: %s\n') % command)
104 104 return
105 105
106 106 repo = rctx.repo
107 107 ui = repo.ui
108 108
109 109 proto = httpv2protocolhandler(req, ui)
110 110
111 111 if (not COMMANDS.commandavailable(command, proto)
112 112 and command not in extracommands):
113 113 res.status = b'404 Not Found'
114 114 res.headers[b'Content-Type'] = b'text/plain'
115 115 res.setbodybytes(_('invalid wire protocol command: %s') % command)
116 116 return
117 117
118 118 # TODO consider cases where proxies may add additional Accept headers.
119 119 if req.headers.get(b'Accept') != FRAMINGTYPE:
120 120 res.status = b'406 Not Acceptable'
121 121 res.headers[b'Content-Type'] = b'text/plain'
122 122 res.setbodybytes(_('client MUST specify Accept header with value: %s\n')
123 123 % FRAMINGTYPE)
124 124 return
125 125
126 126 if req.headers.get(b'Content-Type') != FRAMINGTYPE:
127 127 res.status = b'415 Unsupported Media Type'
128 128 # TODO we should send a response with appropriate media type,
129 129 # since client does Accept it.
130 130 res.headers[b'Content-Type'] = b'text/plain'
131 131 res.setbodybytes(_('client MUST send Content-Type header with '
132 132 'value: %s\n') % FRAMINGTYPE)
133 133 return
134 134
135 135 _processhttpv2request(ui, repo, req, res, permission, command, proto)
136 136
137 137 def _processhttpv2reflectrequest(ui, repo, req, res):
138 138 """Reads unified frame protocol request and dumps out state to client.
139 139
140 140 This special endpoint can be used to help debug the wire protocol.
141 141
142 142 Instead of routing the request through the normal dispatch mechanism,
143 143 we instead read all frames, decode them, and feed them into our state
144 144 tracker. We then dump the log of all that activity back out to the
145 145 client.
146 146 """
147 147 import json
148 148
149 149 # Reflection APIs have a history of being abused, accidentally disclosing
150 150 # sensitive data, etc. So we have a config knob.
151 151 if not ui.configbool('experimental', 'web.api.debugreflect'):
152 152 res.status = b'404 Not Found'
153 153 res.headers[b'Content-Type'] = b'text/plain'
154 154 res.setbodybytes(_('debugreflect service not available'))
155 155 return
156 156
157 157 # We assume we have a unified framing protocol request body.
158 158
159 159 reactor = wireprotoframing.serverreactor(ui)
160 160 states = []
161 161
162 162 while True:
163 163 frame = wireprotoframing.readframe(req.bodyfh)
164 164
165 165 if not frame:
166 166 states.append(b'received: <no frame>')
167 167 break
168 168
169 169 states.append(b'received: %d %d %d %s' % (frame.typeid, frame.flags,
170 170 frame.requestid,
171 171 frame.payload))
172 172
173 173 action, meta = reactor.onframerecv(frame)
174 174 states.append(json.dumps((action, meta), sort_keys=True,
175 175 separators=(', ', ': ')))
176 176
177 177 action, meta = reactor.oninputeof()
178 178 meta['action'] = action
179 179 states.append(json.dumps(meta, sort_keys=True, separators=(', ',': ')))
180 180
181 181 res.status = b'200 OK'
182 182 res.headers[b'Content-Type'] = b'text/plain'
183 183 res.setbodybytes(b'\n'.join(states))
184 184
185 185 def _processhttpv2request(ui, repo, req, res, authedperm, reqcommand, proto):
186 186 """Post-validation handler for HTTPv2 requests.
187 187
188 188 Called when the HTTP request contains unified frame-based protocol
189 189 frames for evaluation.
190 190 """
191 191 # TODO Some HTTP clients are full duplex and can receive data before
192 192 # the entire request is transmitted. Figure out a way to indicate support
193 193 # for that so we can opt into full duplex mode.
194 194 reactor = wireprotoframing.serverreactor(ui, deferoutput=True)
195 195 seencommand = False
196 196
197 outstream = reactor.makeoutputstream()
197 outstream = None
198 198
199 199 while True:
200 200 frame = wireprotoframing.readframe(req.bodyfh)
201 201 if not frame:
202 202 break
203 203
204 204 action, meta = reactor.onframerecv(frame)
205 205
206 206 if action == 'wantframe':
207 207 # Need more data before we can do anything.
208 208 continue
209 209 elif action == 'runcommand':
210 # Defer creating output stream because we need to wait for
211 # protocol settings frames so proper encoding can be applied.
212 if not outstream:
213 outstream = reactor.makeoutputstream()
214
210 215 sentoutput = _httpv2runcommand(ui, repo, req, res, authedperm,
211 216 reqcommand, reactor, outstream,
212 217 meta, issubsequent=seencommand)
213 218
214 219 if sentoutput:
215 220 return
216 221
217 222 seencommand = True
218 223
219 224 elif action == 'error':
220 225 # TODO define proper error mechanism.
221 226 res.status = b'200 OK'
222 227 res.headers[b'Content-Type'] = b'text/plain'
223 228 res.setbodybytes(meta['message'] + b'\n')
224 229 return
225 230 else:
226 231 raise error.ProgrammingError(
227 232 'unhandled action from frame processor: %s' % action)
228 233
229 234 action, meta = reactor.oninputeof()
230 235 if action == 'sendframes':
231 236 # We assume we haven't started sending the response yet. If we're
232 237 # wrong, the response type will raise an exception.
233 238 res.status = b'200 OK'
234 239 res.headers[b'Content-Type'] = FRAMINGTYPE
235 240 res.setbodygen(meta['framegen'])
236 241 elif action == 'noop':
237 242 pass
238 243 else:
239 244 raise error.ProgrammingError('unhandled action from frame processor: %s'
240 245 % action)
241 246
242 247 def _httpv2runcommand(ui, repo, req, res, authedperm, reqcommand, reactor,
243 248 outstream, command, issubsequent):
244 249 """Dispatch a wire protocol command made from HTTPv2 requests.
245 250
246 251 The authenticated permission (``authedperm``) along with the original
247 252 command from the URL (``reqcommand``) are passed in.
248 253 """
249 254 # We already validated that the session has permissions to perform the
250 255 # actions in ``authedperm``. In the unified frame protocol, the canonical
251 256 # command to run is expressed in a frame. However, the URL also requested
252 257 # to run a specific command. We need to be careful that the command we
253 258 # run doesn't have permissions requirements greater than what was granted
254 259 # by ``authedperm``.
255 260 #
256 261 # Our rule for this is we only allow one command per HTTP request and
257 262 # that command must match the command in the URL. However, we make
258 263 # an exception for the ``multirequest`` URL. This URL is allowed to
259 264 # execute multiple commands. We double check permissions of each command
260 265 # as it is invoked to ensure there is no privilege escalation.
261 266 # TODO consider allowing multiple commands to regular command URLs
262 267 # iff each command is the same.
263 268
264 269 proto = httpv2protocolhandler(req, ui, args=command['args'])
265 270
266 271 if reqcommand == b'multirequest':
267 272 if not COMMANDS.commandavailable(command['command'], proto):
268 273 # TODO proper error mechanism
269 274 res.status = b'200 OK'
270 275 res.headers[b'Content-Type'] = b'text/plain'
271 276 res.setbodybytes(_('wire protocol command not available: %s') %
272 277 command['command'])
273 278 return True
274 279
275 280 # TODO don't use assert here, since it may be elided by -O.
276 281 assert authedperm in (b'ro', b'rw')
277 282 wirecommand = COMMANDS[command['command']]
278 283 assert wirecommand.permission in ('push', 'pull')
279 284
280 285 if authedperm == b'ro' and wirecommand.permission != 'pull':
281 286 # TODO proper error mechanism
282 287 res.status = b'403 Forbidden'
283 288 res.headers[b'Content-Type'] = b'text/plain'
284 289 res.setbodybytes(_('insufficient permissions to execute '
285 290 'command: %s') % command['command'])
286 291 return True
287 292
288 293 # TODO should we also call checkperm() here? Maybe not if we're going
289 294 # to overhaul that API. The granted scope from the URL check should
290 295 # be good enough.
291 296
292 297 else:
293 298 # Don't allow multiple commands outside of ``multirequest`` URL.
294 299 if issubsequent:
295 300 # TODO proper error mechanism
296 301 res.status = b'200 OK'
297 302 res.headers[b'Content-Type'] = b'text/plain'
298 303 res.setbodybytes(_('multiple commands cannot be issued to this '
299 304 'URL'))
300 305 return True
301 306
302 307 if reqcommand != command['command']:
303 308 # TODO define proper error mechanism
304 309 res.status = b'200 OK'
305 310 res.headers[b'Content-Type'] = b'text/plain'
306 311 res.setbodybytes(_('command in frame must match command in URL'))
307 312 return True
308 313
309 314 res.status = b'200 OK'
310 315 res.headers[b'Content-Type'] = FRAMINGTYPE
311 316
312 317 try:
313 318 objs = dispatch(repo, proto, command['command'], command['redirect'])
314 319
315 320 action, meta = reactor.oncommandresponsereadyobjects(
316 321 outstream, command['requestid'], objs)
317 322
318 323 except error.WireprotoCommandError as e:
319 324 action, meta = reactor.oncommanderror(
320 325 outstream, command['requestid'], e.message, e.messageargs)
321 326
322 327 except Exception as e:
323 328 action, meta = reactor.onservererror(
324 329 outstream, command['requestid'],
325 330 _('exception when invoking command: %s') %
326 331 stringutil.forcebytestr(e))
327 332
328 333 if action == 'sendframes':
329 334 res.setbodygen(meta['framegen'])
330 335 return True
331 336 elif action == 'noop':
332 337 return False
333 338 else:
334 339 raise error.ProgrammingError('unhandled event from reactor: %s' %
335 340 action)
336 341
337 342 def getdispatchrepo(repo, proto, command):
338 343 return repo.filtered('served')
339 344
340 345 def dispatch(repo, proto, command, redirect):
341 346 """Run a wire protocol command.
342 347
343 348 Returns an iterable of objects that will be sent to the client.
344 349 """
345 350 repo = getdispatchrepo(repo, proto, command)
346 351
347 352 entry = COMMANDS[command]
348 353 func = entry.func
349 354 spec = entry.args
350 355
351 356 args = proto.getargs(spec)
352 357
353 358 # There is some duplicate boilerplate code here for calling the command and
354 359 # emitting objects. It is either that or a lot of indented code that looks
355 360 # like a pyramid (since there are a lot of code paths that result in not
356 361 # using the cacher).
357 362 callcommand = lambda: func(repo, proto, **pycompat.strkwargs(args))
358 363
359 364 # Request is not cacheable. Don't bother instantiating a cacher.
360 365 if not entry.cachekeyfn:
361 366 for o in callcommand():
362 367 yield o
363 368 return
364 369
365 370 if redirect:
366 371 redirecttargets = redirect[b'targets']
367 372 redirecthashes = redirect[b'hashes']
368 373 else:
369 374 redirecttargets = []
370 375 redirecthashes = []
371 376
372 377 cacher = makeresponsecacher(repo, proto, command, args,
373 378 cborutil.streamencode,
374 379 redirecttargets=redirecttargets,
375 380 redirecthashes=redirecthashes)
376 381
377 382 # But we have no cacher. Do default handling.
378 383 if not cacher:
379 384 for o in callcommand():
380 385 yield o
381 386 return
382 387
383 388 with cacher:
384 389 cachekey = entry.cachekeyfn(repo, proto, cacher, **args)
385 390
386 391 # No cache key or the cacher doesn't like it. Do default handling.
387 392 if cachekey is None or not cacher.setcachekey(cachekey):
388 393 for o in callcommand():
389 394 yield o
390 395 return
391 396
392 397 # Serve it from the cache, if possible.
393 398 cached = cacher.lookup()
394 399
395 400 if cached:
396 401 for o in cached['objs']:
397 402 yield o
398 403 return
399 404
400 405 # Else call the command and feed its output into the cacher, allowing
401 406 # the cacher to buffer/mutate objects as it desires.
402 407 for o in callcommand():
403 408 for o in cacher.onobject(o):
404 409 yield o
405 410
406 411 for o in cacher.onfinished():
407 412 yield o
408 413
409 414 @interfaceutil.implementer(wireprototypes.baseprotocolhandler)
410 415 class httpv2protocolhandler(object):
411 416 def __init__(self, req, ui, args=None):
412 417 self._req = req
413 418 self._ui = ui
414 419 self._args = args
415 420
416 421 @property
417 422 def name(self):
418 423 return HTTP_WIREPROTO_V2
419 424
420 425 def getargs(self, args):
421 426 # First look for args that were passed but aren't registered on this
422 427 # command.
423 428 extra = set(self._args) - set(args)
424 429 if extra:
425 430 raise error.WireprotoCommandError(
426 431 'unsupported argument to command: %s' %
427 432 ', '.join(sorted(extra)))
428 433
429 434 # And look for required arguments that are missing.
430 435 missing = {a for a in args if args[a]['required']} - set(self._args)
431 436
432 437 if missing:
433 438 raise error.WireprotoCommandError(
434 439 'missing required arguments: %s' % ', '.join(sorted(missing)))
435 440
436 441 # Now derive the arguments to pass to the command, taking into
437 442 # account the arguments specified by the client.
438 443 data = {}
439 444 for k, meta in sorted(args.items()):
440 445 # This argument wasn't passed by the client.
441 446 if k not in self._args:
442 447 data[k] = meta['default']()
443 448 continue
444 449
445 450 v = self._args[k]
446 451
447 452 # Sets may be expressed as lists. Silently normalize.
448 453 if meta['type'] == 'set' and isinstance(v, list):
449 454 v = set(v)
450 455
451 456 # TODO consider more/stronger type validation.
452 457
453 458 data[k] = v
454 459
455 460 return data
456 461
457 462 def getprotocaps(self):
458 463 # Protocol capabilities are currently not implemented for HTTP V2.
459 464 return set()
460 465
461 466 def getpayload(self):
462 467 raise NotImplementedError
463 468
464 469 @contextlib.contextmanager
465 470 def mayberedirectstdio(self):
466 471 raise NotImplementedError
467 472
468 473 def client(self):
469 474 raise NotImplementedError
470 475
471 476 def addcapabilities(self, repo, caps):
472 477 return caps
473 478
474 479 def checkperm(self, perm):
475 480 raise NotImplementedError
476 481
477 482 def httpv2apidescriptor(req, repo):
478 483 proto = httpv2protocolhandler(req, repo.ui)
479 484
480 485 return _capabilitiesv2(repo, proto)
481 486
482 487 def _capabilitiesv2(repo, proto):
483 488 """Obtain the set of capabilities for version 2 transports.
484 489
485 490 These capabilities are distinct from the capabilities for version 1
486 491 transports.
487 492 """
488 493 caps = {
489 494 'commands': {},
490 495 'framingmediatypes': [FRAMINGTYPE],
491 496 'pathfilterprefixes': set(narrowspec.VALID_PREFIXES),
492 497 }
493 498
494 499 for command, entry in COMMANDS.items():
495 500 args = {}
496 501
497 502 for arg, meta in entry.args.items():
498 503 args[arg] = {
499 504 # TODO should this be a normalized type using CBOR's
500 505 # terminology?
501 506 b'type': meta['type'],
502 507 b'required': meta['required'],
503 508 }
504 509
505 510 if not meta['required']:
506 511 args[arg][b'default'] = meta['default']()
507 512
508 513 if meta['validvalues']:
509 514 args[arg][b'validvalues'] = meta['validvalues']
510 515
511 516 caps['commands'][command] = {
512 517 'args': args,
513 518 'permissions': [entry.permission],
514 519 }
515 520
516 521 caps['rawrepoformats'] = sorted(repo.requirements &
517 522 repo.supportedformats)
518 523
519 524 targets = getadvertisedredirecttargets(repo, proto)
520 525 if targets:
521 526 caps[b'redirect'] = {
522 527 b'targets': [],
523 528 b'hashes': [b'sha256', b'sha1'],
524 529 }
525 530
526 531 for target in targets:
527 532 entry = {
528 533 b'name': target['name'],
529 534 b'protocol': target['protocol'],
530 535 b'uris': target['uris'],
531 536 }
532 537
533 538 for key in ('snirequired', 'tlsversions'):
534 539 if key in target:
535 540 entry[key] = target[key]
536 541
537 542 caps[b'redirect'][b'targets'].append(entry)
538 543
539 544 return proto.addcapabilities(repo, caps)
540 545
541 546 def getadvertisedredirecttargets(repo, proto):
542 547 """Obtain a list of content redirect targets.
543 548
544 549 Returns a list containing potential redirect targets that will be
545 550 advertised in capabilities data. Each dict MUST have the following
546 551 keys:
547 552
548 553 name
549 554 The name of this redirect target. This is the identifier clients use
550 555 to refer to a target. It is transferred as part of every command
551 556 request.
552 557
553 558 protocol
554 559 Network protocol used by this target. Typically this is the string
555 560 in front of the ``://`` in a URL. e.g. ``https``.
556 561
557 562 uris
558 563 List of representative URIs for this target. Clients can use the
559 564 URIs to test parsing for compatibility or for ordering preference
560 565 for which target to use.
561 566
562 567 The following optional keys are recognized:
563 568
564 569 snirequired
565 570 Bool indicating if Server Name Indication (SNI) is required to
566 571 connect to this target.
567 572
568 573 tlsversions
569 574 List of bytes indicating which TLS versions are supported by this
570 575 target.
571 576
572 577 By default, clients reflect the target order advertised by servers
573 578 and servers will use the first client-advertised target when picking
574 579 a redirect target. So targets should be advertised in the order the
575 580 server prefers they be used.
576 581 """
577 582 return []
578 583
579 584 def wireprotocommand(name, args=None, permission='push', cachekeyfn=None):
580 585 """Decorator to declare a wire protocol command.
581 586
582 587 ``name`` is the name of the wire protocol command being provided.
583 588
584 589 ``args`` is a dict defining arguments accepted by the command. Keys are
585 590 the argument name. Values are dicts with the following keys:
586 591
587 592 ``type``
588 593 The argument data type. Must be one of the following string
589 594 literals: ``bytes``, ``int``, ``list``, ``dict``, ``set``,
590 595 or ``bool``.
591 596
592 597 ``default``
593 598 A callable returning the default value for this argument. If not
594 599 specified, ``None`` will be the default value.
595 600
596 601 ``example``
597 602 An example value for this argument.
598 603
599 604 ``validvalues``
600 605 Set of recognized values for this argument.
601 606
602 607 ``permission`` defines the permission type needed to run this command.
603 608 Can be ``push`` or ``pull``. These roughly map to read-write and read-only,
604 609 respectively. Default is to assume command requires ``push`` permissions
605 610 because otherwise commands not declaring their permissions could modify
606 611 a repository that is supposed to be read-only.
607 612
608 613 ``cachekeyfn`` defines an optional callable that can derive the
609 614 cache key for this request.
610 615
611 616 Wire protocol commands are generators of objects to be serialized and
612 617 sent to the client.
613 618
614 619 If a command raises an uncaught exception, this will be translated into
615 620 a command error.
616 621
617 622 All commands can opt in to being cacheable by defining a function
618 623 (``cachekeyfn``) that is called to derive a cache key. This function
619 624 receives the same arguments as the command itself plus a ``cacher``
620 625 argument containing the active cacher for the request and returns a bytes
621 626 containing the key in a cache the response to this command may be cached
622 627 under.
623 628 """
624 629 transports = {k for k, v in wireprototypes.TRANSPORTS.items()
625 630 if v['version'] == 2}
626 631
627 632 if permission not in ('push', 'pull'):
628 633 raise error.ProgrammingError('invalid wire protocol permission; '
629 634 'got %s; expected "push" or "pull"' %
630 635 permission)
631 636
632 637 if args is None:
633 638 args = {}
634 639
635 640 if not isinstance(args, dict):
636 641 raise error.ProgrammingError('arguments for version 2 commands '
637 642 'must be declared as dicts')
638 643
639 644 for arg, meta in args.items():
640 645 if arg == '*':
641 646 raise error.ProgrammingError('* argument name not allowed on '
642 647 'version 2 commands')
643 648
644 649 if not isinstance(meta, dict):
645 650 raise error.ProgrammingError('arguments for version 2 commands '
646 651 'must declare metadata as a dict')
647 652
648 653 if 'type' not in meta:
649 654 raise error.ProgrammingError('%s argument for command %s does not '
650 655 'declare type field' % (arg, name))
651 656
652 657 if meta['type'] not in ('bytes', 'int', 'list', 'dict', 'set', 'bool'):
653 658 raise error.ProgrammingError('%s argument for command %s has '
654 659 'illegal type: %s' % (arg, name,
655 660 meta['type']))
656 661
657 662 if 'example' not in meta:
658 663 raise error.ProgrammingError('%s argument for command %s does not '
659 664 'declare example field' % (arg, name))
660 665
661 666 meta['required'] = 'default' not in meta
662 667
663 668 meta.setdefault('default', lambda: None)
664 669 meta.setdefault('validvalues', None)
665 670
666 671 def register(func):
667 672 if name in COMMANDS:
668 673 raise error.ProgrammingError('%s command already registered '
669 674 'for version 2' % name)
670 675
671 676 COMMANDS[name] = wireprototypes.commandentry(
672 677 func, args=args, transports=transports, permission=permission,
673 678 cachekeyfn=cachekeyfn)
674 679
675 680 return func
676 681
677 682 return register
678 683
679 684 def makecommandcachekeyfn(command, localversion=None, allargs=False):
680 685 """Construct a cache key derivation function with common features.
681 686
682 687 By default, the cache key is a hash of:
683 688
684 689 * The command name.
685 690 * A global cache version number.
686 691 * A local cache version number (passed via ``localversion``).
687 692 * All the arguments passed to the command.
688 693 * The media type used.
689 694 * Wire protocol version string.
690 695 * The repository path.
691 696 """
692 697 if not allargs:
693 698 raise error.ProgrammingError('only allargs=True is currently supported')
694 699
695 700 if localversion is None:
696 701 raise error.ProgrammingError('must set localversion argument value')
697 702
698 703 def cachekeyfn(repo, proto, cacher, **args):
699 704 spec = COMMANDS[command]
700 705
701 706 # Commands that mutate the repo can not be cached.
702 707 if spec.permission == 'push':
703 708 return None
704 709
705 710 # TODO config option to disable caching.
706 711
707 712 # Our key derivation strategy is to construct a data structure
708 713 # holding everything that could influence cacheability and to hash
709 714 # the CBOR representation of that. Using CBOR seems like it might
710 715 # be overkill. However, simpler hashing mechanisms are prone to
711 716 # duplicate input issues. e.g. if you just concatenate two values,
712 717 # "foo"+"bar" is identical to "fo"+"obar". Using CBOR provides
713 718 # "padding" between values and prevents these problems.
714 719
715 720 # Seed the hash with various data.
716 721 state = {
717 722 # To invalidate all cache keys.
718 723 b'globalversion': GLOBAL_CACHE_VERSION,
719 724 # More granular cache key invalidation.
720 725 b'localversion': localversion,
721 726 # Cache keys are segmented by command.
722 727 b'command': pycompat.sysbytes(command),
723 728 # Throw in the media type and API version strings so changes
724 729 # to exchange semantics invalid cache.
725 730 b'mediatype': FRAMINGTYPE,
726 731 b'version': HTTP_WIREPROTO_V2,
727 732 # So same requests for different repos don't share cache keys.
728 733 b'repo': repo.root,
729 734 }
730 735
731 736 # The arguments passed to us will have already been normalized.
732 737 # Default values will be set, etc. This is important because it
733 738 # means that it doesn't matter if clients send an explicit argument
734 739 # or rely on the default value: it will all normalize to the same
735 740 # set of arguments on the server and therefore the same cache key.
736 741 #
737 742 # Arguments by their very nature must support being encoded to CBOR.
738 743 # And the CBOR encoder is deterministic. So we hash the arguments
739 744 # by feeding the CBOR of their representation into the hasher.
740 745 if allargs:
741 746 state[b'args'] = pycompat.byteskwargs(args)
742 747
743 748 cacher.adjustcachekeystate(state)
744 749
745 750 hasher = hashlib.sha1()
746 751 for chunk in cborutil.streamencode(state):
747 752 hasher.update(chunk)
748 753
749 754 return pycompat.sysbytes(hasher.hexdigest())
750 755
751 756 return cachekeyfn
752 757
753 758 def makeresponsecacher(repo, proto, command, args, objencoderfn,
754 759 redirecttargets, redirecthashes):
755 760 """Construct a cacher for a cacheable command.
756 761
757 762 Returns an ``iwireprotocolcommandcacher`` instance.
758 763
759 764 Extensions can monkeypatch this function to provide custom caching
760 765 backends.
761 766 """
762 767 return None
763 768
764 769 @wireprotocommand('branchmap', permission='pull')
765 770 def branchmapv2(repo, proto):
766 771 yield {encoding.fromlocal(k): v
767 772 for k, v in repo.branchmap().iteritems()}
768 773
769 774 @wireprotocommand('capabilities', permission='pull')
770 775 def capabilitiesv2(repo, proto):
771 776 yield _capabilitiesv2(repo, proto)
772 777
773 778 @wireprotocommand(
774 779 'changesetdata',
775 780 args={
776 781 'noderange': {
777 782 'type': 'list',
778 783 'default': lambda: None,
779 784 'example': [[b'0123456...'], [b'abcdef...']],
780 785 },
781 786 'nodes': {
782 787 'type': 'list',
783 788 'default': lambda: None,
784 789 'example': [b'0123456...'],
785 790 },
786 791 'nodesdepth': {
787 792 'type': 'int',
788 793 'default': lambda: None,
789 794 'example': 10,
790 795 },
791 796 'fields': {
792 797 'type': 'set',
793 798 'default': set,
794 799 'example': {b'parents', b'revision'},
795 800 'validvalues': {b'bookmarks', b'parents', b'phase', b'revision'},
796 801 },
797 802 },
798 803 permission='pull')
799 804 def changesetdata(repo, proto, noderange, nodes, nodesdepth, fields):
800 805 # TODO look for unknown fields and abort when they can't be serviced.
801 806 # This could probably be validated by dispatcher using validvalues.
802 807
803 808 if noderange is None and nodes is None:
804 809 raise error.WireprotoCommandError(
805 810 'noderange or nodes must be defined')
806 811
807 812 if nodesdepth is not None and nodes is None:
808 813 raise error.WireprotoCommandError(
809 814 'nodesdepth requires the nodes argument')
810 815
811 816 if noderange is not None:
812 817 if len(noderange) != 2:
813 818 raise error.WireprotoCommandError(
814 819 'noderange must consist of 2 elements')
815 820
816 821 if not noderange[1]:
817 822 raise error.WireprotoCommandError(
818 823 'heads in noderange request cannot be empty')
819 824
820 825 cl = repo.changelog
821 826 hasnode = cl.hasnode
822 827
823 828 seen = set()
824 829 outgoing = []
825 830
826 831 if nodes is not None:
827 832 outgoing = [n for n in nodes if hasnode(n)]
828 833
829 834 if nodesdepth:
830 835 outgoing = [cl.node(r) for r in
831 836 repo.revs(b'ancestors(%ln, %d)', outgoing,
832 837 nodesdepth - 1)]
833 838
834 839 seen |= set(outgoing)
835 840
836 841 if noderange is not None:
837 842 if noderange[0]:
838 843 common = [n for n in noderange[0] if hasnode(n)]
839 844 else:
840 845 common = [nullid]
841 846
842 847 for n in discovery.outgoing(repo, common, noderange[1]).missing:
843 848 if n not in seen:
844 849 outgoing.append(n)
845 850 # Don't need to add to seen here because this is the final
846 851 # source of nodes and there should be no duplicates in this
847 852 # list.
848 853
849 854 seen.clear()
850 855 publishing = repo.publishing()
851 856
852 857 if outgoing:
853 858 repo.hook('preoutgoing', throw=True, source='serve')
854 859
855 860 yield {
856 861 b'totalitems': len(outgoing),
857 862 }
858 863
859 864 # The phases of nodes already transferred to the client may have changed
860 865 # since the client last requested data. We send phase-only records
861 866 # for these revisions, if requested.
862 867 if b'phase' in fields and noderange is not None:
863 868 # TODO skip nodes whose phase will be reflected by a node in the
864 869 # outgoing set. This is purely an optimization to reduce data
865 870 # size.
866 871 for node in noderange[0]:
867 872 yield {
868 873 b'node': node,
869 874 b'phase': b'public' if publishing else repo[node].phasestr()
870 875 }
871 876
872 877 nodebookmarks = {}
873 878 for mark, node in repo._bookmarks.items():
874 879 nodebookmarks.setdefault(node, set()).add(mark)
875 880
876 881 # It is already topologically sorted by revision number.
877 882 for node in outgoing:
878 883 d = {
879 884 b'node': node,
880 885 }
881 886
882 887 if b'parents' in fields:
883 888 d[b'parents'] = cl.parents(node)
884 889
885 890 if b'phase' in fields:
886 891 if publishing:
887 892 d[b'phase'] = b'public'
888 893 else:
889 894 ctx = repo[node]
890 895 d[b'phase'] = ctx.phasestr()
891 896
892 897 if b'bookmarks' in fields and node in nodebookmarks:
893 898 d[b'bookmarks'] = sorted(nodebookmarks[node])
894 899 del nodebookmarks[node]
895 900
896 901 followingmeta = []
897 902 followingdata = []
898 903
899 904 if b'revision' in fields:
900 905 revisiondata = cl.revision(node, raw=True)
901 906 followingmeta.append((b'revision', len(revisiondata)))
902 907 followingdata.append(revisiondata)
903 908
904 909 # TODO make it possible for extensions to wrap a function or register
905 910 # a handler to service custom fields.
906 911
907 912 if followingmeta:
908 913 d[b'fieldsfollowing'] = followingmeta
909 914
910 915 yield d
911 916
912 917 for extra in followingdata:
913 918 yield extra
914 919
915 920 # If requested, send bookmarks from nodes that didn't have revision
916 921 # data sent so receiver is aware of any bookmark updates.
917 922 if b'bookmarks' in fields:
918 923 for node, marks in sorted(nodebookmarks.iteritems()):
919 924 yield {
920 925 b'node': node,
921 926 b'bookmarks': sorted(marks),
922 927 }
923 928
924 929 class FileAccessError(Exception):
925 930 """Represents an error accessing a specific file."""
926 931
927 932 def __init__(self, path, msg, args):
928 933 self.path = path
929 934 self.msg = msg
930 935 self.args = args
931 936
932 937 def getfilestore(repo, proto, path):
933 938 """Obtain a file storage object for use with wire protocol.
934 939
935 940 Exists as a standalone function so extensions can monkeypatch to add
936 941 access control.
937 942 """
938 943 # This seems to work even if the file doesn't exist. So catch
939 944 # "empty" files and return an error.
940 945 fl = repo.file(path)
941 946
942 947 if not len(fl):
943 948 raise FileAccessError(path, 'unknown file: %s', (path,))
944 949
945 950 return fl
946 951
947 952 @wireprotocommand(
948 953 'filedata',
949 954 args={
950 955 'haveparents': {
951 956 'type': 'bool',
952 957 'default': lambda: False,
953 958 'example': True,
954 959 },
955 960 'nodes': {
956 961 'type': 'list',
957 962 'example': [b'0123456...'],
958 963 },
959 964 'fields': {
960 965 'type': 'set',
961 966 'default': set,
962 967 'example': {b'parents', b'revision'},
963 968 'validvalues': {b'parents', b'revision'},
964 969 },
965 970 'path': {
966 971 'type': 'bytes',
967 972 'example': b'foo.txt',
968 973 }
969 974 },
970 975 permission='pull',
971 976 # TODO censoring a file revision won't invalidate the cache.
972 977 # Figure out a way to take censoring into account when deriving
973 978 # the cache key.
974 979 cachekeyfn=makecommandcachekeyfn('filedata', 1, allargs=True))
975 980 def filedata(repo, proto, haveparents, nodes, fields, path):
976 981 try:
977 982 # Extensions may wish to access the protocol handler.
978 983 store = getfilestore(repo, proto, path)
979 984 except FileAccessError as e:
980 985 raise error.WireprotoCommandError(e.msg, e.args)
981 986
982 987 # Validate requested nodes.
983 988 for node in nodes:
984 989 try:
985 990 store.rev(node)
986 991 except error.LookupError:
987 992 raise error.WireprotoCommandError('unknown file node: %s',
988 993 (hex(node),))
989 994
990 995 revisions = store.emitrevisions(nodes,
991 996 revisiondata=b'revision' in fields,
992 997 assumehaveparentrevisions=haveparents)
993 998
994 999 yield {
995 1000 b'totalitems': len(nodes),
996 1001 }
997 1002
998 1003 for revision in revisions:
999 1004 d = {
1000 1005 b'node': revision.node,
1001 1006 }
1002 1007
1003 1008 if b'parents' in fields:
1004 1009 d[b'parents'] = [revision.p1node, revision.p2node]
1005 1010
1006 1011 followingmeta = []
1007 1012 followingdata = []
1008 1013
1009 1014 if b'revision' in fields:
1010 1015 if revision.revision is not None:
1011 1016 followingmeta.append((b'revision', len(revision.revision)))
1012 1017 followingdata.append(revision.revision)
1013 1018 else:
1014 1019 d[b'deltabasenode'] = revision.basenode
1015 1020 followingmeta.append((b'delta', len(revision.delta)))
1016 1021 followingdata.append(revision.delta)
1017 1022
1018 1023 if followingmeta:
1019 1024 d[b'fieldsfollowing'] = followingmeta
1020 1025
1021 1026 yield d
1022 1027
1023 1028 for extra in followingdata:
1024 1029 yield extra
1025 1030
1026 1031 @wireprotocommand(
1027 1032 'heads',
1028 1033 args={
1029 1034 'publiconly': {
1030 1035 'type': 'bool',
1031 1036 'default': lambda: False,
1032 1037 'example': False,
1033 1038 },
1034 1039 },
1035 1040 permission='pull')
1036 1041 def headsv2(repo, proto, publiconly):
1037 1042 if publiconly:
1038 1043 repo = repo.filtered('immutable')
1039 1044
1040 1045 yield repo.heads()
1041 1046
1042 1047 @wireprotocommand(
1043 1048 'known',
1044 1049 args={
1045 1050 'nodes': {
1046 1051 'type': 'list',
1047 1052 'default': list,
1048 1053 'example': [b'deadbeef'],
1049 1054 },
1050 1055 },
1051 1056 permission='pull')
1052 1057 def knownv2(repo, proto, nodes):
1053 1058 result = b''.join(b'1' if n else b'0' for n in repo.known(nodes))
1054 1059 yield result
1055 1060
1056 1061 @wireprotocommand(
1057 1062 'listkeys',
1058 1063 args={
1059 1064 'namespace': {
1060 1065 'type': 'bytes',
1061 1066 'example': b'ns',
1062 1067 },
1063 1068 },
1064 1069 permission='pull')
1065 1070 def listkeysv2(repo, proto, namespace):
1066 1071 keys = repo.listkeys(encoding.tolocal(namespace))
1067 1072 keys = {encoding.fromlocal(k): encoding.fromlocal(v)
1068 1073 for k, v in keys.iteritems()}
1069 1074
1070 1075 yield keys
1071 1076
1072 1077 @wireprotocommand(
1073 1078 'lookup',
1074 1079 args={
1075 1080 'key': {
1076 1081 'type': 'bytes',
1077 1082 'example': b'foo',
1078 1083 },
1079 1084 },
1080 1085 permission='pull')
1081 1086 def lookupv2(repo, proto, key):
1082 1087 key = encoding.tolocal(key)
1083 1088
1084 1089 # TODO handle exception.
1085 1090 node = repo.lookup(key)
1086 1091
1087 1092 yield node
1088 1093
1089 1094 @wireprotocommand(
1090 1095 'manifestdata',
1091 1096 args={
1092 1097 'nodes': {
1093 1098 'type': 'list',
1094 1099 'example': [b'0123456...'],
1095 1100 },
1096 1101 'haveparents': {
1097 1102 'type': 'bool',
1098 1103 'default': lambda: False,
1099 1104 'example': True,
1100 1105 },
1101 1106 'fields': {
1102 1107 'type': 'set',
1103 1108 'default': set,
1104 1109 'example': {b'parents', b'revision'},
1105 1110 'validvalues': {b'parents', b'revision'},
1106 1111 },
1107 1112 'tree': {
1108 1113 'type': 'bytes',
1109 1114 'example': b'',
1110 1115 },
1111 1116 },
1112 1117 permission='pull',
1113 1118 cachekeyfn=makecommandcachekeyfn('manifestdata', 1, allargs=True))
1114 1119 def manifestdata(repo, proto, haveparents, nodes, fields, tree):
1115 1120 store = repo.manifestlog.getstorage(tree)
1116 1121
1117 1122 # Validate the node is known and abort on unknown revisions.
1118 1123 for node in nodes:
1119 1124 try:
1120 1125 store.rev(node)
1121 1126 except error.LookupError:
1122 1127 raise error.WireprotoCommandError(
1123 1128 'unknown node: %s', (node,))
1124 1129
1125 1130 revisions = store.emitrevisions(nodes,
1126 1131 revisiondata=b'revision' in fields,
1127 1132 assumehaveparentrevisions=haveparents)
1128 1133
1129 1134 yield {
1130 1135 b'totalitems': len(nodes),
1131 1136 }
1132 1137
1133 1138 for revision in revisions:
1134 1139 d = {
1135 1140 b'node': revision.node,
1136 1141 }
1137 1142
1138 1143 if b'parents' in fields:
1139 1144 d[b'parents'] = [revision.p1node, revision.p2node]
1140 1145
1141 1146 followingmeta = []
1142 1147 followingdata = []
1143 1148
1144 1149 if b'revision' in fields:
1145 1150 if revision.revision is not None:
1146 1151 followingmeta.append((b'revision', len(revision.revision)))
1147 1152 followingdata.append(revision.revision)
1148 1153 else:
1149 1154 d[b'deltabasenode'] = revision.basenode
1150 1155 followingmeta.append((b'delta', len(revision.delta)))
1151 1156 followingdata.append(revision.delta)
1152 1157
1153 1158 if followingmeta:
1154 1159 d[b'fieldsfollowing'] = followingmeta
1155 1160
1156 1161 yield d
1157 1162
1158 1163 for extra in followingdata:
1159 1164 yield extra
1160 1165
1161 1166 @wireprotocommand(
1162 1167 'pushkey',
1163 1168 args={
1164 1169 'namespace': {
1165 1170 'type': 'bytes',
1166 1171 'example': b'ns',
1167 1172 },
1168 1173 'key': {
1169 1174 'type': 'bytes',
1170 1175 'example': b'key',
1171 1176 },
1172 1177 'old': {
1173 1178 'type': 'bytes',
1174 1179 'example': b'old',
1175 1180 },
1176 1181 'new': {
1177 1182 'type': 'bytes',
1178 1183 'example': 'new',
1179 1184 },
1180 1185 },
1181 1186 permission='push')
1182 1187 def pushkeyv2(repo, proto, namespace, key, old, new):
1183 1188 # TODO handle ui output redirection
1184 1189 yield repo.pushkey(encoding.tolocal(namespace),
1185 1190 encoding.tolocal(key),
1186 1191 encoding.tolocal(old),
1187 1192 encoding.tolocal(new))
@@ -1,711 +1,732 b''
1 1 #require no-chg
2 2
3 3 $ . $TESTDIR/wireprotohelpers.sh
4 4 $ enabledummycommands
5 5
6 6 $ hg init server
7 7 $ cat > server/.hg/hgrc << EOF
8 8 > [experimental]
9 9 > web.apiserver = true
10 10 > EOF
11 11 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid
12 12 $ cat hg.pid > $DAEMON_PIDS
13 13
14 14 HTTP v2 protocol not enabled by default
15 15
16 16 $ sendhttpraw << EOF
17 17 > httprequest GET api/$HTTPV2
18 18 > user-agent: test
19 19 > EOF
20 20 using raw connection to peer
21 21 s> GET /api/exp-http-v2-0002 HTTP/1.1\r\n
22 22 s> Accept-Encoding: identity\r\n
23 23 s> user-agent: test\r\n
24 24 s> host: $LOCALIP:$HGPORT\r\n (glob)
25 25 s> \r\n
26 26 s> makefile('rb', None)
27 27 s> HTTP/1.1 404 Not Found\r\n
28 28 s> Server: testing stub value\r\n
29 29 s> Date: $HTTP_DATE$\r\n
30 30 s> Content-Type: text/plain\r\n
31 31 s> Content-Length: 33\r\n
32 32 s> \r\n
33 33 s> API exp-http-v2-0002 not enabled\n
34 34
35 35 Restart server with support for HTTP v2 API
36 36
37 37 $ killdaemons.py
38 38 $ enablehttpv2 server
39 39 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid
40 40 $ cat hg.pid > $DAEMON_PIDS
41 41
42 42 Request to unknown command yields 404
43 43
44 44 $ sendhttpraw << EOF
45 45 > httprequest POST api/$HTTPV2/ro/badcommand
46 46 > user-agent: test
47 47 > EOF
48 48 using raw connection to peer
49 49 s> POST /api/exp-http-v2-0002/ro/badcommand HTTP/1.1\r\n
50 50 s> Accept-Encoding: identity\r\n
51 51 s> user-agent: test\r\n
52 52 s> host: $LOCALIP:$HGPORT\r\n (glob)
53 53 s> \r\n
54 54 s> makefile('rb', None)
55 55 s> HTTP/1.1 404 Not Found\r\n
56 56 s> Server: testing stub value\r\n
57 57 s> Date: $HTTP_DATE$\r\n
58 58 s> Content-Type: text/plain\r\n
59 59 s> Content-Length: 42\r\n
60 60 s> \r\n
61 61 s> unknown wire protocol command: badcommand\n
62 62
63 63 GET to read-only command yields a 405
64 64
65 65 $ sendhttpraw << EOF
66 66 > httprequest GET api/$HTTPV2/ro/customreadonly
67 67 > user-agent: test
68 68 > EOF
69 69 using raw connection to peer
70 70 s> GET /api/exp-http-v2-0002/ro/customreadonly HTTP/1.1\r\n
71 71 s> Accept-Encoding: identity\r\n
72 72 s> user-agent: test\r\n
73 73 s> host: $LOCALIP:$HGPORT\r\n (glob)
74 74 s> \r\n
75 75 s> makefile('rb', None)
76 76 s> HTTP/1.1 405 Method Not Allowed\r\n
77 77 s> Server: testing stub value\r\n
78 78 s> Date: $HTTP_DATE$\r\n
79 79 s> Allow: POST\r\n
80 80 s> Content-Length: 30\r\n
81 81 s> \r\n
82 82 s> commands require POST requests
83 83
84 84 Missing Accept header results in 406
85 85
86 86 $ sendhttpraw << EOF
87 87 > httprequest POST api/$HTTPV2/ro/customreadonly
88 88 > user-agent: test
89 89 > EOF
90 90 using raw connection to peer
91 91 s> POST /api/exp-http-v2-0002/ro/customreadonly HTTP/1.1\r\n
92 92 s> Accept-Encoding: identity\r\n
93 93 s> user-agent: test\r\n
94 94 s> host: $LOCALIP:$HGPORT\r\n (glob)
95 95 s> \r\n
96 96 s> makefile('rb', None)
97 97 s> HTTP/1.1 406 Not Acceptable\r\n
98 98 s> Server: testing stub value\r\n
99 99 s> Date: $HTTP_DATE$\r\n
100 100 s> Content-Type: text/plain\r\n
101 101 s> Content-Length: 85\r\n
102 102 s> \r\n
103 103 s> client MUST specify Accept header with value: application/mercurial-exp-framing-0006\n
104 104
105 105 Bad Accept header results in 406
106 106
107 107 $ sendhttpraw << EOF
108 108 > httprequest POST api/$HTTPV2/ro/customreadonly
109 109 > accept: invalid
110 110 > user-agent: test
111 111 > EOF
112 112 using raw connection to peer
113 113 s> POST /api/exp-http-v2-0002/ro/customreadonly HTTP/1.1\r\n
114 114 s> Accept-Encoding: identity\r\n
115 115 s> accept: invalid\r\n
116 116 s> user-agent: test\r\n
117 117 s> host: $LOCALIP:$HGPORT\r\n (glob)
118 118 s> \r\n
119 119 s> makefile('rb', None)
120 120 s> HTTP/1.1 406 Not Acceptable\r\n
121 121 s> Server: testing stub value\r\n
122 122 s> Date: $HTTP_DATE$\r\n
123 123 s> Content-Type: text/plain\r\n
124 124 s> Content-Length: 85\r\n
125 125 s> \r\n
126 126 s> client MUST specify Accept header with value: application/mercurial-exp-framing-0006\n
127 127
128 128 Bad Content-Type header results in 415
129 129
130 130 $ sendhttpraw << EOF
131 131 > httprequest POST api/$HTTPV2/ro/customreadonly
132 132 > accept: $MEDIATYPE
133 133 > user-agent: test
134 134 > content-type: badmedia
135 135 > EOF
136 136 using raw connection to peer
137 137 s> POST /api/exp-http-v2-0002/ro/customreadonly HTTP/1.1\r\n
138 138 s> Accept-Encoding: identity\r\n
139 139 s> accept: application/mercurial-exp-framing-0006\r\n
140 140 s> content-type: badmedia\r\n
141 141 s> user-agent: test\r\n
142 142 s> host: $LOCALIP:$HGPORT\r\n (glob)
143 143 s> \r\n
144 144 s> makefile('rb', None)
145 145 s> HTTP/1.1 415 Unsupported Media Type\r\n
146 146 s> Server: testing stub value\r\n
147 147 s> Date: $HTTP_DATE$\r\n
148 148 s> Content-Type: text/plain\r\n
149 149 s> Content-Length: 88\r\n
150 150 s> \r\n
151 151 s> client MUST send Content-Type header with value: application/mercurial-exp-framing-0006\n
152 152
153 153 Request to read-only command works out of the box
154 154
155 155 $ sendhttpraw << EOF
156 156 > httprequest POST api/$HTTPV2/ro/customreadonly
157 157 > accept: $MEDIATYPE
158 158 > content-type: $MEDIATYPE
159 159 > user-agent: test
160 160 > frame 1 1 stream-begin command-request new cbor:{b'name': b'customreadonly'}
161 161 > EOF
162 162 using raw connection to peer
163 163 s> POST /api/exp-http-v2-0002/ro/customreadonly HTTP/1.1\r\n
164 164 s> Accept-Encoding: identity\r\n
165 165 s> *\r\n (glob)
166 166 s> content-type: application/mercurial-exp-framing-0006\r\n
167 167 s> user-agent: test\r\n
168 168 s> content-length: 29\r\n
169 169 s> host: $LOCALIP:$HGPORT\r\n (glob)
170 170 s> \r\n
171 171 s> \x15\x00\x00\x01\x00\x01\x01\x11\xa1DnameNcustomreadonly
172 172 s> makefile('rb', None)
173 173 s> HTTP/1.1 200 OK\r\n
174 174 s> Server: testing stub value\r\n
175 175 s> Date: $HTTP_DATE$\r\n
176 176 s> Content-Type: application/mercurial-exp-framing-0006\r\n
177 177 s> Transfer-Encoding: chunked\r\n
178 178 s> \r\n
179 s> 11\r\n
180 s> \t\x00\x00\x01\x00\x02\x01\x92Hidentity
181 s> \r\n
179 182 s> 13\r\n
180 s> \x0b\x00\x00\x01\x00\x02\x011\xa1FstatusBok
183 s> \x0b\x00\x00\x01\x00\x02\x041\xa1FstatusBok
181 184 s> \r\n
182 185 s> 27\r\n
183 s> \x1f\x00\x00\x01\x00\x02\x001X\x1dcustomreadonly bytes response
186 s> \x1f\x00\x00\x01\x00\x02\x041X\x1dcustomreadonly bytes response
184 187 s> \r\n
185 188 s> 8\r\n
186 189 s> \x00\x00\x00\x01\x00\x02\x002
187 190 s> \r\n
188 191 s> 0\r\n
189 192 s> \r\n
190 193
191 194 $ sendhttpv2peerverbose << EOF
192 195 > command customreadonly
193 196 > EOF
194 197 creating http peer for wire protocol version 2
195 198 sending customreadonly command
196 199 s> POST /api/exp-http-v2-0002/ro/customreadonly HTTP/1.1\r\n
197 200 s> Accept-Encoding: identity\r\n
198 201 s> accept: application/mercurial-exp-framing-0006\r\n
199 202 s> content-type: application/mercurial-exp-framing-0006\r\n
200 203 s> content-length: 65\r\n
201 204 s> host: $LOCALIP:$HGPORT\r\n (glob)
202 205 s> user-agent: Mercurial debugwireproto\r\n
203 206 s> \r\n
204 207 s> \x1c\x00\x00\x01\x00\x01\x01\x82\xa1Pcontentencodings\x81Hidentity\x15\x00\x00\x01\x00\x01\x00\x11\xa1DnameNcustomreadonly
205 208 s> makefile('rb', None)
206 209 s> HTTP/1.1 200 OK\r\n
207 210 s> Server: testing stub value\r\n
208 211 s> Date: $HTTP_DATE$\r\n
209 212 s> Content-Type: application/mercurial-exp-framing-0006\r\n
210 213 s> Transfer-Encoding: chunked\r\n
211 214 s> \r\n
215 s> 11\r\n
216 s> \t\x00\x00\x01\x00\x02\x01\x92
217 s> Hidentity
218 s> \r\n
219 received frame(size=9; request=1; stream=2; streamflags=stream-begin; type=stream-settings; flags=eos)
212 220 s> 13\r\n
213 s> \x0b\x00\x00\x01\x00\x02\x011
221 s> \x0b\x00\x00\x01\x00\x02\x041
214 222 s> \xa1FstatusBok
215 223 s> \r\n
216 received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
224 received frame(size=11; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation)
217 225 s> 27\r\n
218 s> \x1f\x00\x00\x01\x00\x02\x001
226 s> \x1f\x00\x00\x01\x00\x02\x041
219 227 s> X\x1dcustomreadonly bytes response
220 228 s> \r\n
221 received frame(size=31; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
229 received frame(size=31; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation)
222 230 s> 8\r\n
223 231 s> \x00\x00\x00\x01\x00\x02\x002
224 232 s> \r\n
225 233 s> 0\r\n
226 234 s> \r\n
227 235 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
228 236 response: gen[
229 237 b'customreadonly bytes response'
230 238 ]
231 239 (sent 2 HTTP requests and * bytes; received * bytes in responses) (glob)
232 240
233 241 Request to read-write command fails because server is read-only by default
234 242
235 243 GET to read-write request yields 405
236 244
237 245 $ sendhttpraw << EOF
238 246 > httprequest GET api/$HTTPV2/rw/customreadonly
239 247 > user-agent: test
240 248 > EOF
241 249 using raw connection to peer
242 250 s> GET /api/exp-http-v2-0002/rw/customreadonly HTTP/1.1\r\n
243 251 s> Accept-Encoding: identity\r\n
244 252 s> user-agent: test\r\n
245 253 s> host: $LOCALIP:$HGPORT\r\n (glob)
246 254 s> \r\n
247 255 s> makefile('rb', None)
248 256 s> HTTP/1.1 405 Method Not Allowed\r\n
249 257 s> Server: testing stub value\r\n
250 258 s> Date: $HTTP_DATE$\r\n
251 259 s> Allow: POST\r\n
252 260 s> Content-Length: 30\r\n
253 261 s> \r\n
254 262 s> commands require POST requests
255 263
256 264 Even for unknown commands
257 265
258 266 $ sendhttpraw << EOF
259 267 > httprequest GET api/$HTTPV2/rw/badcommand
260 268 > user-agent: test
261 269 > EOF
262 270 using raw connection to peer
263 271 s> GET /api/exp-http-v2-0002/rw/badcommand HTTP/1.1\r\n
264 272 s> Accept-Encoding: identity\r\n
265 273 s> user-agent: test\r\n
266 274 s> host: $LOCALIP:$HGPORT\r\n (glob)
267 275 s> \r\n
268 276 s> makefile('rb', None)
269 277 s> HTTP/1.1 405 Method Not Allowed\r\n
270 278 s> Server: testing stub value\r\n
271 279 s> Date: $HTTP_DATE$\r\n
272 280 s> Allow: POST\r\n
273 281 s> Content-Length: 30\r\n
274 282 s> \r\n
275 283 s> commands require POST requests
276 284
277 285 SSL required by default
278 286
279 287 $ sendhttpraw << EOF
280 288 > httprequest POST api/$HTTPV2/rw/customreadonly
281 289 > user-agent: test
282 290 > EOF
283 291 using raw connection to peer
284 292 s> POST /api/exp-http-v2-0002/rw/customreadonly HTTP/1.1\r\n
285 293 s> Accept-Encoding: identity\r\n
286 294 s> user-agent: test\r\n
287 295 s> host: $LOCALIP:$HGPORT\r\n (glob)
288 296 s> \r\n
289 297 s> makefile('rb', None)
290 298 s> HTTP/1.1 403 ssl required\r\n
291 299 s> Server: testing stub value\r\n
292 300 s> Date: $HTTP_DATE$\r\n
293 301 s> Content-Length: 17\r\n
294 302 s> \r\n
295 303 s> permission denied
296 304
297 305 Restart server to allow non-ssl read-write operations
298 306
299 307 $ killdaemons.py
300 308 $ cat > server/.hg/hgrc << EOF
301 309 > [experimental]
302 310 > web.apiserver = true
303 311 > web.api.http-v2 = true
304 312 > [web]
305 313 > push_ssl = false
306 314 > allow-push = *
307 315 > EOF
308 316
309 317 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid -E error.log
310 318 $ cat hg.pid > $DAEMON_PIDS
311 319
312 320 Authorized request for valid read-write command works
313 321
314 322 $ sendhttpraw << EOF
315 323 > httprequest POST api/$HTTPV2/rw/customreadonly
316 324 > user-agent: test
317 325 > accept: $MEDIATYPE
318 326 > content-type: $MEDIATYPE
319 327 > frame 1 1 stream-begin command-request new cbor:{b'name': b'customreadonly'}
320 328 > EOF
321 329 using raw connection to peer
322 330 s> POST /api/exp-http-v2-0002/rw/customreadonly HTTP/1.1\r\n
323 331 s> Accept-Encoding: identity\r\n
324 332 s> accept: application/mercurial-exp-framing-0006\r\n
325 333 s> content-type: application/mercurial-exp-framing-0006\r\n
326 334 s> user-agent: test\r\n
327 335 s> content-length: 29\r\n
328 336 s> host: $LOCALIP:$HGPORT\r\n (glob)
329 337 s> \r\n
330 338 s> \x15\x00\x00\x01\x00\x01\x01\x11\xa1DnameNcustomreadonly
331 339 s> makefile('rb', None)
332 340 s> HTTP/1.1 200 OK\r\n
333 341 s> Server: testing stub value\r\n
334 342 s> Date: $HTTP_DATE$\r\n
335 343 s> Content-Type: application/mercurial-exp-framing-0006\r\n
336 344 s> Transfer-Encoding: chunked\r\n
337 345 s> \r\n
346 s> 11\r\n
347 s> \t\x00\x00\x01\x00\x02\x01\x92Hidentity
348 s> \r\n
338 349 s> 13\r\n
339 s> \x0b\x00\x00\x01\x00\x02\x011\xa1FstatusBok
350 s> \x0b\x00\x00\x01\x00\x02\x041\xa1FstatusBok
340 351 s> \r\n
341 352 s> 27\r\n
342 s> \x1f\x00\x00\x01\x00\x02\x001X\x1dcustomreadonly bytes response
353 s> \x1f\x00\x00\x01\x00\x02\x041X\x1dcustomreadonly bytes response
343 354 s> \r\n
344 355 s> 8\r\n
345 356 s> \x00\x00\x00\x01\x00\x02\x002
346 357 s> \r\n
347 358 s> 0\r\n
348 359 s> \r\n
349 360
350 361 Authorized request for unknown command is rejected
351 362
352 363 $ sendhttpraw << EOF
353 364 > httprequest POST api/$HTTPV2/rw/badcommand
354 365 > user-agent: test
355 366 > accept: $MEDIATYPE
356 367 > EOF
357 368 using raw connection to peer
358 369 s> POST /api/exp-http-v2-0002/rw/badcommand HTTP/1.1\r\n
359 370 s> Accept-Encoding: identity\r\n
360 371 s> accept: application/mercurial-exp-framing-0006\r\n
361 372 s> user-agent: test\r\n
362 373 s> host: $LOCALIP:$HGPORT\r\n (glob)
363 374 s> \r\n
364 375 s> makefile('rb', None)
365 376 s> HTTP/1.1 404 Not Found\r\n
366 377 s> Server: testing stub value\r\n
367 378 s> Date: $HTTP_DATE$\r\n
368 379 s> Content-Type: text/plain\r\n
369 380 s> Content-Length: 42\r\n
370 381 s> \r\n
371 382 s> unknown wire protocol command: badcommand\n
372 383
373 384 debugreflect isn't enabled by default
374 385
375 386 $ sendhttpraw << EOF
376 387 > httprequest POST api/$HTTPV2/ro/debugreflect
377 388 > user-agent: test
378 389 > EOF
379 390 using raw connection to peer
380 391 s> POST /api/exp-http-v2-0002/ro/debugreflect HTTP/1.1\r\n
381 392 s> Accept-Encoding: identity\r\n
382 393 s> user-agent: test\r\n
383 394 s> host: $LOCALIP:$HGPORT\r\n (glob)
384 395 s> \r\n
385 396 s> makefile('rb', None)
386 397 s> HTTP/1.1 404 Not Found\r\n
387 398 s> Server: testing stub value\r\n
388 399 s> Date: $HTTP_DATE$\r\n
389 400 s> Content-Type: text/plain\r\n
390 401 s> Content-Length: 34\r\n
391 402 s> \r\n
392 403 s> debugreflect service not available
393 404
394 405 Restart server to get debugreflect endpoint
395 406
396 407 $ killdaemons.py
397 408 $ cat > server/.hg/hgrc << EOF
398 409 > [experimental]
399 410 > web.apiserver = true
400 411 > web.api.debugreflect = true
401 412 > web.api.http-v2 = true
402 413 > [web]
403 414 > push_ssl = false
404 415 > allow-push = *
405 416 > EOF
406 417
407 418 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid -E error.log
408 419 $ cat hg.pid > $DAEMON_PIDS
409 420
410 421 Command frames can be reflected via debugreflect
411 422
412 423 $ sendhttpraw << EOF
413 424 > httprequest POST api/$HTTPV2/ro/debugreflect
414 425 > accept: $MEDIATYPE
415 426 > content-type: $MEDIATYPE
416 427 > user-agent: test
417 428 > frame 1 1 stream-begin command-request new cbor:{b'name': b'command1', b'args': {b'foo': b'val1', b'bar1': b'val'}}
418 429 > EOF
419 430 using raw connection to peer
420 431 s> POST /api/exp-http-v2-0002/ro/debugreflect HTTP/1.1\r\n
421 432 s> Accept-Encoding: identity\r\n
422 433 s> accept: application/mercurial-exp-framing-0006\r\n
423 434 s> content-type: application/mercurial-exp-framing-0006\r\n
424 435 s> user-agent: test\r\n
425 436 s> content-length: 47\r\n
426 437 s> host: $LOCALIP:$HGPORT\r\n (glob)
427 438 s> \r\n
428 439 s> \'\x00\x00\x01\x00\x01\x01\x11\xa2Dargs\xa2Dbar1CvalCfooDval1DnameHcommand1
429 440 s> makefile('rb', None)
430 441 s> HTTP/1.1 200 OK\r\n
431 442 s> Server: testing stub value\r\n
432 443 s> Date: $HTTP_DATE$\r\n
433 444 s> Content-Type: text/plain\r\n
434 445 s> Content-Length: 223\r\n
435 446 s> \r\n
436 447 s> received: 1 1 1 \xa2Dargs\xa2Dbar1CvalCfooDval1DnameHcommand1\n
437 448 s> ["runcommand", {"args": {"bar1": "val", "foo": "val1"}, "command": "command1", "data": null, "redirect": null, "requestid": 1}]\n
438 449 s> received: <no frame>\n
439 450 s> {"action": "noop"}
440 451
441 452 Multiple requests to regular command URL are not allowed
442 453
443 454 $ sendhttpraw << EOF
444 455 > httprequest POST api/$HTTPV2/ro/customreadonly
445 456 > accept: $MEDIATYPE
446 457 > content-type: $MEDIATYPE
447 458 > user-agent: test
448 459 > frame 1 1 stream-begin command-request new cbor:{b'name': b'customreadonly'}
449 460 > EOF
450 461 using raw connection to peer
451 462 s> POST /api/exp-http-v2-0002/ro/customreadonly HTTP/1.1\r\n
452 463 s> Accept-Encoding: identity\r\n
453 464 s> accept: application/mercurial-exp-framing-0006\r\n
454 465 s> content-type: application/mercurial-exp-framing-0006\r\n
455 466 s> user-agent: test\r\n
456 467 s> content-length: 29\r\n
457 468 s> host: $LOCALIP:$HGPORT\r\n (glob)
458 469 s> \r\n
459 470 s> \x15\x00\x00\x01\x00\x01\x01\x11\xa1DnameNcustomreadonly
460 471 s> makefile('rb', None)
461 472 s> HTTP/1.1 200 OK\r\n
462 473 s> Server: testing stub value\r\n
463 474 s> Date: $HTTP_DATE$\r\n
464 475 s> Content-Type: application/mercurial-exp-framing-0006\r\n
465 476 s> Transfer-Encoding: chunked\r\n
466 477 s> \r\n
478 s> 11\r\n
479 s> \t\x00\x00\x01\x00\x02\x01\x92Hidentity
480 s> \r\n
467 481 s> 13\r\n
468 s> \x0b\x00\x00\x01\x00\x02\x011\xa1FstatusBok
482 s> \x0b\x00\x00\x01\x00\x02\x041\xa1FstatusBok
469 483 s> \r\n
470 484 s> 27\r\n
471 s> \x1f\x00\x00\x01\x00\x02\x001X\x1dcustomreadonly bytes response
485 s> \x1f\x00\x00\x01\x00\x02\x041X\x1dcustomreadonly bytes response
472 486 s> \r\n
473 487 s> 8\r\n
474 488 s> \x00\x00\x00\x01\x00\x02\x002
475 489 s> \r\n
476 490 s> 0\r\n
477 491 s> \r\n
478 492
479 493 Multiple requests to "multirequest" URL are allowed
480 494
481 495 $ sendhttpraw << EOF
482 496 > httprequest POST api/$HTTPV2/ro/multirequest
483 497 > accept: $MEDIATYPE
484 498 > content-type: $MEDIATYPE
485 499 > user-agent: test
486 500 > frame 1 1 stream-begin command-request new cbor:{b'name': b'customreadonly'}
487 501 > frame 3 1 0 command-request new cbor:{b'name': b'customreadonly'}
488 502 > EOF
489 503 using raw connection to peer
490 504 s> POST /api/exp-http-v2-0002/ro/multirequest HTTP/1.1\r\n
491 505 s> Accept-Encoding: identity\r\n
492 506 s> *\r\n (glob)
493 507 s> *\r\n (glob)
494 508 s> user-agent: test\r\n
495 509 s> content-length: 58\r\n
496 510 s> host: $LOCALIP:$HGPORT\r\n (glob)
497 511 s> \r\n
498 512 s> \x15\x00\x00\x01\x00\x01\x01\x11\xa1DnameNcustomreadonly\x15\x00\x00\x03\x00\x01\x00\x11\xa1DnameNcustomreadonly
499 513 s> makefile('rb', None)
500 514 s> HTTP/1.1 200 OK\r\n
501 515 s> Server: testing stub value\r\n
502 516 s> Date: $HTTP_DATE$\r\n
503 517 s> Content-Type: application/mercurial-exp-framing-0006\r\n
504 518 s> Transfer-Encoding: chunked\r\n
505 519 s> \r\n
520 s> 11\r\n
521 s> \t\x00\x00\x01\x00\x02\x01\x92Hidentity
522 s> \r\n
506 523 s> 13\r\n
507 s> \x0b\x00\x00\x01\x00\x02\x011\xa1FstatusBok
524 s> \x0b\x00\x00\x01\x00\x02\x041\xa1FstatusBok
508 525 s> \r\n
509 526 s> 27\r\n
510 s> \x1f\x00\x00\x01\x00\x02\x001X\x1dcustomreadonly bytes response
527 s> \x1f\x00\x00\x01\x00\x02\x041X\x1dcustomreadonly bytes response
511 528 s> \r\n
512 529 s> 8\r\n
513 530 s> \x00\x00\x00\x01\x00\x02\x002
514 531 s> \r\n
515 532 s> 13\r\n
516 s> \x0b\x00\x00\x03\x00\x02\x001\xa1FstatusBok
533 s> \x0b\x00\x00\x03\x00\x02\x041\xa1FstatusBok
517 534 s> \r\n
518 535 s> 27\r\n
519 s> \x1f\x00\x00\x03\x00\x02\x001X\x1dcustomreadonly bytes response
536 s> \x1f\x00\x00\x03\x00\x02\x041X\x1dcustomreadonly bytes response
520 537 s> \r\n
521 538 s> 8\r\n
522 539 s> \x00\x00\x00\x03\x00\x02\x002
523 540 s> \r\n
524 541 s> 0\r\n
525 542 s> \r\n
526 543
527 544 Interleaved requests to "multirequest" are processed
528 545
529 546 $ sendhttpraw << EOF
530 547 > httprequest POST api/$HTTPV2/ro/multirequest
531 548 > accept: $MEDIATYPE
532 549 > content-type: $MEDIATYPE
533 550 > user-agent: test
534 551 > frame 1 1 stream-begin command-request new|more \xa2Dargs\xa1Inamespace
535 552 > frame 3 1 0 command-request new|more \xa2Dargs\xa1Inamespace
536 553 > frame 3 1 0 command-request continuation JnamespacesDnameHlistkeys
537 554 > frame 1 1 0 command-request continuation IbookmarksDnameHlistkeys
538 555 > EOF
539 556 using raw connection to peer
540 557 s> POST /api/exp-http-v2-0002/ro/multirequest HTTP/1.1\r\n
541 558 s> Accept-Encoding: identity\r\n
542 559 s> accept: application/mercurial-exp-framing-0006\r\n
543 560 s> content-type: application/mercurial-exp-framing-0006\r\n
544 561 s> user-agent: test\r\n
545 562 s> content-length: 115\r\n
546 563 s> host: $LOCALIP:$HGPORT\r\n (glob)
547 564 s> \r\n
548 565 s> \x11\x00\x00\x01\x00\x01\x01\x15\xa2Dargs\xa1Inamespace\x11\x00\x00\x03\x00\x01\x00\x15\xa2Dargs\xa1Inamespace\x19\x00\x00\x03\x00\x01\x00\x12JnamespacesDnameHlistkeys\x18\x00\x00\x01\x00\x01\x00\x12IbookmarksDnameHlistkeys
549 566 s> makefile('rb', None)
550 567 s> HTTP/1.1 200 OK\r\n
551 568 s> Server: testing stub value\r\n
552 569 s> Date: $HTTP_DATE$\r\n
553 570 s> Content-Type: application/mercurial-exp-framing-0006\r\n
554 571 s> Transfer-Encoding: chunked\r\n
555 572 s> \r\n
573 s> 11\r\n
574 s> \t\x00\x00\x03\x00\x02\x01\x92Hidentity
575 s> \r\n
556 576 s> 13\r\n
557 s> \x0b\x00\x00\x03\x00\x02\x011\xa1FstatusBok
577 s> \x0b\x00\x00\x03\x00\x02\x041\xa1FstatusBok
558 578 s> \r\n
559 579 s> 28\r\n
560 s> \x00\x00\x03\x00\x02\x001\xa3Ibookmarks@Jnamespaces@Fphases@
580 s> \x00\x00\x03\x00\x02\x041\xa3Ibookmarks@Jnamespaces@Fphases@
561 581 s> \r\n
562 582 s> 8\r\n
563 583 s> \x00\x00\x00\x03\x00\x02\x002
564 584 s> \r\n
565 585 s> 13\r\n
566 s> \x0b\x00\x00\x01\x00\x02\x001\xa1FstatusBok
586 s> \x0b\x00\x00\x01\x00\x02\x041\xa1FstatusBok
567 587 s> \r\n
568 588 s> 9\r\n
569 s> \x01\x00\x00\x01\x00\x02\x001\xa0
589 s> \x01\x00\x00\x01\x00\x02\x041\xa0
570 590 s> \r\n
571 591 s> 8\r\n
572 592 s> \x00\x00\x00\x01\x00\x02\x002
573 593 s> \r\n
574 594 s> 0\r\n
575 595 s> \r\n
576 596
577 597 Restart server to disable read-write access
578 598
579 599 $ killdaemons.py
580 600 $ cat > server/.hg/hgrc << EOF
581 601 > [experimental]
582 602 > web.apiserver = true
583 603 > web.api.debugreflect = true
584 604 > web.api.http-v2 = true
585 605 > [web]
586 606 > push_ssl = false
587 607 > EOF
588 608
589 609 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid -E error.log
590 610 $ cat hg.pid > $DAEMON_PIDS
591 611
592 612 Attempting to run a read-write command via multirequest on read-only URL is not allowed
593 613
594 614 $ sendhttpraw << EOF
595 615 > httprequest POST api/$HTTPV2/ro/multirequest
596 616 > accept: $MEDIATYPE
597 617 > content-type: $MEDIATYPE
598 618 > user-agent: test
599 619 > frame 1 1 stream-begin command-request new cbor:{b'name': b'pushkey'}
600 620 > EOF
601 621 using raw connection to peer
602 622 s> POST /api/exp-http-v2-0002/ro/multirequest HTTP/1.1\r\n
603 623 s> Accept-Encoding: identity\r\n
604 624 s> accept: application/mercurial-exp-framing-0006\r\n
605 625 s> content-type: application/mercurial-exp-framing-0006\r\n
606 626 s> user-agent: test\r\n
607 627 s> content-length: 22\r\n
608 628 s> host: $LOCALIP:$HGPORT\r\n (glob)
609 629 s> \r\n
610 630 s> \x0e\x00\x00\x01\x00\x01\x01\x11\xa1DnameGpushkey
611 631 s> makefile('rb', None)
612 632 s> HTTP/1.1 403 Forbidden\r\n
613 633 s> Server: testing stub value\r\n
614 634 s> Date: $HTTP_DATE$\r\n
615 635 s> Content-Type: text/plain\r\n
616 636 s> Content-Length: 52\r\n
617 637 s> \r\n
618 638 s> insufficient permissions to execute command: pushkey
619 639
620 640 Defining an invalid content encoding results in warning
621 641
622 642 $ hg --config experimental.httppeer.v2-encoder-order=identity,badencoder --verbose debugwireproto --nologhandshake --peer http2 http://$LOCALIP:$HGPORT/ << EOF
623 643 > command heads
624 644 > EOF
625 645 creating http peer for wire protocol version 2
626 646 sending heads command
627 647 wire protocol version 2 encoder referenced in config (badencoder) is not known; ignoring
628 648 s> POST /api/exp-http-v2-0002/ro/heads HTTP/1.1\r\n
629 649 s> Accept-Encoding: identity\r\n
630 650 s> accept: application/mercurial-exp-framing-0006\r\n
631 651 s> content-type: application/mercurial-exp-framing-0006\r\n
632 652 s> content-length: 56\r\n
633 653 s> host: $LOCALIP:$HGPORT\r\n (glob)
634 654 s> user-agent: Mercurial debugwireproto\r\n
635 655 s> \r\n
636 656 s> \x1c\x00\x00\x01\x00\x01\x01\x82\xa1Pcontentencodings\x81Hidentity\x0c\x00\x00\x01\x00\x01\x00\x11\xa1DnameEheads
637 657 s> makefile('rb', None)
638 658 s> HTTP/1.1 200 OK\r\n
639 659 s> Server: testing stub value\r\n
640 660 s> Date: $HTTP_DATE$\r\n
641 661 s> Content-Type: application/mercurial-exp-framing-0006\r\n
642 662 s> Transfer-Encoding: chunked\r\n
643 663 s> \r\n
664 s> 11\r\n
665 s> \t\x00\x00\x01\x00\x02\x01\x92
666 s> Hidentity
667 s> \r\n
668 received frame(size=9; request=1; stream=2; streamflags=stream-begin; type=stream-settings; flags=eos)
644 669 s> 13\r\n
645 s> \x0b\x00\x00\x01\x00\x02\x011
670 s> \x0b\x00\x00\x01\x00\x02\x041
646 671 s> \xa1FstatusBok
647 672 s> \r\n
648 received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
673 received frame(size=11; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation)
649 674 s> 1e\r\n
650 s> \x16\x00\x00\x01\x00\x02\x001
675 s> \x16\x00\x00\x01\x00\x02\x041
651 676 s> \x81T\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00
652 677 s> \r\n
653 received frame(size=22; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
678 received frame(size=22; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation)
654 679 s> 8\r\n
655 680 s> \x00\x00\x00\x01\x00\x02\x002
656 681 s> \r\n
657 682 s> 0\r\n
658 683 s> \r\n
659 684 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
660 685 response: [
661 686 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
662 687 ]
663 688 (sent 2 HTTP requests and * bytes; received * bytes in responses) (glob)
664 689
665 690 #if zstd
666 691
667 692 $ hg --verbose debugwireproto --nologhandshake --peer http2 http://$LOCALIP:$HGPORT/ << EOF
668 693 > command heads
669 694 > EOF
670 695 creating http peer for wire protocol version 2
671 696 sending heads command
672 697 s> POST /api/exp-http-v2-0002/ro/heads HTTP/1.1\r\n
673 698 s> Accept-Encoding: identity\r\n
674 699 s> accept: application/mercurial-exp-framing-0006\r\n
675 700 s> content-type: application/mercurial-exp-framing-0006\r\n
676 701 s> content-length: 70\r\n
677 702 s> host: $LOCALIP:$HGPORT\r\n (glob)
678 703 s> user-agent: Mercurial debugwireproto\r\n
679 704 s> \r\n
680 705 s> *\x00\x00\x01\x00\x01\x01\x82\xa1Pcontentencodings\x83Hzstd-8mbDzlibHidentity\x0c\x00\x00\x01\x00\x01\x00\x11\xa1DnameEheads
681 706 s> makefile('rb', None)
682 707 s> HTTP/1.1 200 OK\r\n
683 708 s> Server: testing stub value\r\n
684 709 s> Date: $HTTP_DATE$\r\n
685 710 s> Content-Type: application/mercurial-exp-framing-0006\r\n
686 711 s> Transfer-Encoding: chunked\r\n
687 712 s> \r\n
688 s> 13\r\n
689 s> \x0b\x00\x00\x01\x00\x02\x011
690 s> \xa1FstatusBok
713 s> 11\r\n
714 s> \t\x00\x00\x01\x00\x02\x01\x92
715 s> Hzstd-8mb
691 716 s> \r\n
692 received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
693 s> 1e\r\n
694 s> \x16\x00\x00\x01\x00\x02\x001
695 s> \x81T\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00
717 received frame(size=9; request=1; stream=2; streamflags=stream-begin; type=stream-settings; flags=eos)
718 s> 25\r\n
719 s> \x1d\x00\x00\x01\x00\x02\x042
720 s> (\xb5/\xfd\x00P\xa4\x00\x00p\xa1FstatusBok\x81T\x00\x01\x00\tP\x02
696 721 s> \r\n
697 received frame(size=22; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
698 s> 8\r\n
699 s> \x00\x00\x00\x01\x00\x02\x002
700 s> \r\n
722 received frame(size=29; request=1; stream=2; streamflags=encoded; type=command-response; flags=eos)
701 723 s> 0\r\n
702 724 s> \r\n
703 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
704 725 response: [
705 726 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
706 727 ]
707 728 (sent 2 HTTP requests and * bytes; received * bytes in responses) (glob)
708 729
709 730 #endif
710 731
711 732 $ cat error.log
@@ -1,754 +1,759 b''
1 1 #require no-chg
2 2
3 3 $ . $TESTDIR/wireprotohelpers.sh
4 4
5 5 $ cat >> $HGRCPATH << EOF
6 6 > [web]
7 7 > push_ssl = false
8 8 > allow_push = *
9 9 > EOF
10 10
11 11 $ hg init server
12 12 $ cd server
13 13 $ touch a
14 14 $ hg -q commit -A -m initial
15 15 $ cd ..
16 16
17 17 $ hg serve -R server -p $HGPORT -d --pid-file hg.pid
18 18 $ cat hg.pid >> $DAEMON_PIDS
19 19
20 20 compression formats are advertised in compression capability
21 21
22 22 #if zstd
23 23 $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=capabilities' | tr ' ' '\n' | grep '^compression=zstd,zlib$' > /dev/null
24 24 #else
25 25 $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=capabilities' | tr ' ' '\n' | grep '^compression=zlib$' > /dev/null
26 26 #endif
27 27
28 28 $ killdaemons.py
29 29
30 30 server.compressionengines can replace engines list wholesale
31 31
32 32 $ hg serve --config server.compressionengines=none -R server -p $HGPORT -d --pid-file hg.pid
33 33 $ cat hg.pid > $DAEMON_PIDS
34 34 $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=capabilities' | tr ' ' '\n' | grep '^compression=none$' > /dev/null
35 35
36 36 $ killdaemons.py
37 37
38 38 Order of engines can also change
39 39
40 40 $ hg serve --config server.compressionengines=none,zlib -R server -p $HGPORT -d --pid-file hg.pid
41 41 $ cat hg.pid > $DAEMON_PIDS
42 42 $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=capabilities' | tr ' ' '\n' | grep '^compression=none,zlib$' > /dev/null
43 43
44 44 $ killdaemons.py
45 45
46 46 Start a default server again
47 47
48 48 $ hg serve -R server -p $HGPORT -d --pid-file hg.pid
49 49 $ cat hg.pid > $DAEMON_PIDS
50 50
51 51 Server should send application/mercurial-0.1 to clients if no Accept is used
52 52
53 53 $ get-with-headers.py --headeronly $LOCALIP:$HGPORT '?cmd=getbundle&heads=e93700bd72895c5addab234c56d4024b487a362f&common=0000000000000000000000000000000000000000' -
54 54 200 Script output follows
55 55 content-type: application/mercurial-0.1
56 56 date: $HTTP_DATE$
57 57 server: testing stub value
58 58 transfer-encoding: chunked
59 59
60 60 Server should send application/mercurial-0.1 when client says it wants it
61 61
62 62 $ get-with-headers.py --hgproto '0.1' --headeronly $LOCALIP:$HGPORT '?cmd=getbundle&heads=e93700bd72895c5addab234c56d4024b487a362f&common=0000000000000000000000000000000000000000' -
63 63 200 Script output follows
64 64 content-type: application/mercurial-0.1
65 65 date: $HTTP_DATE$
66 66 server: testing stub value
67 67 transfer-encoding: chunked
68 68
69 69 Server should send application/mercurial-0.2 when client says it wants it
70 70
71 71 $ get-with-headers.py --hgproto '0.2' --headeronly $LOCALIP:$HGPORT '?cmd=getbundle&heads=e93700bd72895c5addab234c56d4024b487a362f&common=0000000000000000000000000000000000000000' -
72 72 200 Script output follows
73 73 content-type: application/mercurial-0.2
74 74 date: $HTTP_DATE$
75 75 server: testing stub value
76 76 transfer-encoding: chunked
77 77
78 78 $ get-with-headers.py --hgproto '0.1 0.2' --headeronly $LOCALIP:$HGPORT '?cmd=getbundle&heads=e93700bd72895c5addab234c56d4024b487a362f&common=0000000000000000000000000000000000000000' -
79 79 200 Script output follows
80 80 content-type: application/mercurial-0.2
81 81 date: $HTTP_DATE$
82 82 server: testing stub value
83 83 transfer-encoding: chunked
84 84
85 85 Requesting a compression format that server doesn't support results will fall back to 0.1
86 86
87 87 $ get-with-headers.py --hgproto '0.2 comp=aa' --headeronly $LOCALIP:$HGPORT '?cmd=getbundle&heads=e93700bd72895c5addab234c56d4024b487a362f&common=0000000000000000000000000000000000000000' -
88 88 200 Script output follows
89 89 content-type: application/mercurial-0.1
90 90 date: $HTTP_DATE$
91 91 server: testing stub value
92 92 transfer-encoding: chunked
93 93
94 94 #if zstd
95 95 zstd is used if available
96 96
97 97 $ get-with-headers.py --hgproto '0.2 comp=zstd' $LOCALIP:$HGPORT '?cmd=getbundle&heads=e93700bd72895c5addab234c56d4024b487a362f&common=0000000000000000000000000000000000000000' > resp
98 98 $ f --size --hexdump --bytes 36 --sha1 resp
99 99 resp: size=248, sha1=4d8d8f87fb82bd542ce52881fdc94f850748
100 100 0000: 32 30 30 20 53 63 72 69 70 74 20 6f 75 74 70 75 |200 Script outpu|
101 101 0010: 74 20 66 6f 6c 6c 6f 77 73 0a 0a 04 7a 73 74 64 |t follows...zstd|
102 102 0020: 28 b5 2f fd |(./.|
103 103
104 104 #endif
105 105
106 106 application/mercurial-0.2 is not yet used on non-streaming responses
107 107
108 108 $ get-with-headers.py --hgproto '0.2' $LOCALIP:$HGPORT '?cmd=heads' -
109 109 200 Script output follows
110 110 content-length: 41
111 111 content-type: application/mercurial-0.1
112 112 date: $HTTP_DATE$
113 113 server: testing stub value
114 114
115 115 e93700bd72895c5addab234c56d4024b487a362f
116 116
117 117 Now test protocol preference usage
118 118
119 119 $ killdaemons.py
120 120 $ hg serve --config server.compressionengines=none,zlib -R server -p $HGPORT -d --pid-file hg.pid
121 121 $ cat hg.pid > $DAEMON_PIDS
122 122
123 123 No Accept will send 0.1+zlib, even though "none" is preferred b/c "none" isn't supported on 0.1
124 124
125 125 $ get-with-headers.py --headeronly $LOCALIP:$HGPORT '?cmd=getbundle&heads=e93700bd72895c5addab234c56d4024b487a362f&common=0000000000000000000000000000000000000000' Content-Type
126 126 200 Script output follows
127 127 content-type: application/mercurial-0.1
128 128
129 129 $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=getbundle&heads=e93700bd72895c5addab234c56d4024b487a362f&common=0000000000000000000000000000000000000000' > resp
130 130 $ f --size --hexdump --bytes 28 --sha1 resp
131 131 resp: size=227, sha1=35a4c074da74f32f5440da3cbf04
132 132 0000: 32 30 30 20 53 63 72 69 70 74 20 6f 75 74 70 75 |200 Script outpu|
133 133 0010: 74 20 66 6f 6c 6c 6f 77 73 0a 0a 78 |t follows..x|
134 134
135 135 Explicit 0.1 will send zlib because "none" isn't supported on 0.1
136 136
137 137 $ get-with-headers.py --hgproto '0.1' $LOCALIP:$HGPORT '?cmd=getbundle&heads=e93700bd72895c5addab234c56d4024b487a362f&common=0000000000000000000000000000000000000000' > resp
138 138 $ f --size --hexdump --bytes 28 --sha1 resp
139 139 resp: size=227, sha1=35a4c074da74f32f5440da3cbf04
140 140 0000: 32 30 30 20 53 63 72 69 70 74 20 6f 75 74 70 75 |200 Script outpu|
141 141 0010: 74 20 66 6f 6c 6c 6f 77 73 0a 0a 78 |t follows..x|
142 142
143 143 0.2 with no compression will get "none" because that is server's preference
144 144 (spec says ZL and UN are implicitly supported)
145 145
146 146 $ get-with-headers.py --hgproto '0.2' $LOCALIP:$HGPORT '?cmd=getbundle&heads=e93700bd72895c5addab234c56d4024b487a362f&common=0000000000000000000000000000000000000000' > resp
147 147 $ f --size --hexdump --bytes 32 --sha1 resp
148 148 resp: size=432, sha1=ac931b412ec185a02e0e5bcff98dac83
149 149 0000: 32 30 30 20 53 63 72 69 70 74 20 6f 75 74 70 75 |200 Script outpu|
150 150 0010: 74 20 66 6f 6c 6c 6f 77 73 0a 0a 04 6e 6f 6e 65 |t follows...none|
151 151
152 152 Client receives server preference even if local order doesn't match
153 153
154 154 $ get-with-headers.py --hgproto '0.2 comp=zlib,none' $LOCALIP:$HGPORT '?cmd=getbundle&heads=e93700bd72895c5addab234c56d4024b487a362f&common=0000000000000000000000000000000000000000' > resp
155 155 $ f --size --hexdump --bytes 32 --sha1 resp
156 156 resp: size=432, sha1=ac931b412ec185a02e0e5bcff98dac83
157 157 0000: 32 30 30 20 53 63 72 69 70 74 20 6f 75 74 70 75 |200 Script outpu|
158 158 0010: 74 20 66 6f 6c 6c 6f 77 73 0a 0a 04 6e 6f 6e 65 |t follows...none|
159 159
160 160 Client receives only supported format even if not server preferred format
161 161
162 162 $ get-with-headers.py --hgproto '0.2 comp=zlib' $LOCALIP:$HGPORT '?cmd=getbundle&heads=e93700bd72895c5addab234c56d4024b487a362f&common=0000000000000000000000000000000000000000' > resp
163 163 $ f --size --hexdump --bytes 33 --sha1 resp
164 164 resp: size=232, sha1=a1c727f0c9693ca15742a75c30419bc36
165 165 0000: 32 30 30 20 53 63 72 69 70 74 20 6f 75 74 70 75 |200 Script outpu|
166 166 0010: 74 20 66 6f 6c 6c 6f 77 73 0a 0a 04 7a 6c 69 62 |t follows...zlib|
167 167 0020: 78 |x|
168 168
169 169 $ killdaemons.py
170 170 $ cd ..
171 171
172 172 Test listkeys for listing namespaces
173 173
174 174 $ hg init empty
175 175 $ hg -R empty serve -p $HGPORT -d --pid-file hg.pid
176 176 $ cat hg.pid > $DAEMON_PIDS
177 177
178 178 $ hg --verbose debugwireproto http://$LOCALIP:$HGPORT << EOF
179 179 > command listkeys
180 180 > namespace namespaces
181 181 > EOF
182 182 s> GET /?cmd=capabilities HTTP/1.1\r\n
183 183 s> Accept-Encoding: identity\r\n
184 184 s> accept: application/mercurial-0.1\r\n
185 185 s> host: $LOCALIP:$HGPORT\r\n (glob)
186 186 s> user-agent: Mercurial debugwireproto\r\n
187 187 s> \r\n
188 188 s> makefile('rb', None)
189 189 s> HTTP/1.1 200 Script output follows\r\n
190 190 s> Server: testing stub value\r\n
191 191 s> Date: $HTTP_DATE$\r\n
192 192 s> Content-Type: application/mercurial-0.1\r\n
193 193 s> Content-Length: *\r\n (glob)
194 194 s> \r\n
195 195 s> batch 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
196 196 sending listkeys command
197 197 s> GET /?cmd=listkeys HTTP/1.1\r\n
198 198 s> Accept-Encoding: identity\r\n
199 199 s> vary: X-HgArg-1,X-HgProto-1\r\n
200 200 s> x-hgarg-1: namespace=namespaces\r\n
201 201 s> x-hgproto-1: 0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull\r\n
202 202 s> accept: application/mercurial-0.1\r\n
203 203 s> host: $LOCALIP:$HGPORT\r\n (glob)
204 204 s> user-agent: Mercurial debugwireproto\r\n
205 205 s> \r\n
206 206 s> makefile('rb', None)
207 207 s> HTTP/1.1 200 Script output follows\r\n
208 208 s> Server: testing stub value\r\n
209 209 s> Date: $HTTP_DATE$\r\n
210 210 s> Content-Type: application/mercurial-0.1\r\n
211 211 s> Content-Length: 30\r\n
212 212 s> \r\n
213 213 s> bookmarks\t\n
214 214 s> namespaces\t\n
215 215 s> phases\t
216 216 response: {
217 217 b'bookmarks': b'',
218 218 b'namespaces': b'',
219 219 b'phases': b''
220 220 }
221 221 (sent 2 HTTP requests and * bytes; received * bytes in responses) (glob)
222 222
223 223 Same thing, but with "httprequest" command
224 224
225 225 $ hg --verbose debugwireproto --peer raw http://$LOCALIP:$HGPORT << EOF
226 226 > httprequest GET ?cmd=listkeys
227 227 > user-agent: test
228 228 > x-hgarg-1: namespace=namespaces
229 229 > EOF
230 230 using raw connection to peer
231 231 s> GET /?cmd=listkeys HTTP/1.1\r\n
232 232 s> Accept-Encoding: identity\r\n
233 233 s> user-agent: test\r\n
234 234 s> x-hgarg-1: namespace=namespaces\r\n
235 235 s> host: $LOCALIP:$HGPORT\r\n (glob)
236 236 s> \r\n
237 237 s> makefile('rb', None)
238 238 s> HTTP/1.1 200 Script output follows\r\n
239 239 s> Server: testing stub value\r\n
240 240 s> Date: $HTTP_DATE$\r\n
241 241 s> Content-Type: application/mercurial-0.1\r\n
242 242 s> Content-Length: 30\r\n
243 243 s> \r\n
244 244 s> bookmarks\t\n
245 245 s> namespaces\t\n
246 246 s> phases\t
247 247
248 248 Client with HTTPv2 enabled advertises that and gets old capabilities response from old server
249 249
250 250 $ hg --config experimental.httppeer.advertise-v2=true --verbose debugwireproto http://$LOCALIP:$HGPORT << EOF
251 251 > command heads
252 252 > EOF
253 253 s> GET /?cmd=capabilities HTTP/1.1\r\n
254 254 s> Accept-Encoding: identity\r\n
255 255 s> vary: X-HgProto-1,X-HgUpgrade-1\r\n
256 256 s> x-hgproto-1: cbor\r\n
257 257 s> x-hgupgrade-1: exp-http-v2-0002\r\n
258 258 s> accept: application/mercurial-0.1\r\n
259 259 s> host: $LOCALIP:$HGPORT\r\n (glob)
260 260 s> user-agent: Mercurial debugwireproto\r\n
261 261 s> \r\n
262 262 s> makefile('rb', None)
263 263 s> HTTP/1.1 200 Script output follows\r\n
264 264 s> Server: testing stub value\r\n
265 265 s> Date: $HTTP_DATE$\r\n
266 266 s> Content-Type: application/mercurial-0.1\r\n
267 267 s> Content-Length: *\r\n (glob)
268 268 s> \r\n
269 269 s> batch 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
270 270 sending heads command
271 271 s> GET /?cmd=heads HTTP/1.1\r\n
272 272 s> Accept-Encoding: identity\r\n
273 273 s> vary: X-HgProto-1\r\n
274 274 s> x-hgproto-1: 0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull\r\n
275 275 s> accept: application/mercurial-0.1\r\n
276 276 s> host: $LOCALIP:$HGPORT\r\n (glob)
277 277 s> user-agent: Mercurial debugwireproto\r\n
278 278 s> \r\n
279 279 s> makefile('rb', None)
280 280 s> HTTP/1.1 200 Script output follows\r\n
281 281 s> Server: testing stub value\r\n
282 282 s> Date: $HTTP_DATE$\r\n
283 283 s> Content-Type: application/mercurial-0.1\r\n
284 284 s> Content-Length: 41\r\n
285 285 s> \r\n
286 286 s> 0000000000000000000000000000000000000000\n
287 287 response: [
288 288 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
289 289 ]
290 290 (sent 2 HTTP requests and * bytes; received * bytes in responses) (glob)
291 291
292 292 $ killdaemons.py
293 293 $ enablehttpv2 empty
294 294 $ hg --config server.compressionengines=zlib -R empty serve -p $HGPORT -d --pid-file hg.pid
295 295 $ cat hg.pid > $DAEMON_PIDS
296 296
297 297 Client with HTTPv2 enabled automatically upgrades if the server supports it
298 298
299 299 $ hg --config experimental.httppeer.advertise-v2=true --config experimental.httppeer.v2-encoder-order=identity --verbose debugwireproto http://$LOCALIP:$HGPORT << EOF
300 300 > command heads
301 301 > EOF
302 302 s> GET /?cmd=capabilities HTTP/1.1\r\n
303 303 s> Accept-Encoding: identity\r\n
304 304 s> vary: X-HgProto-1,X-HgUpgrade-1\r\n
305 305 s> x-hgproto-1: cbor\r\n
306 306 s> x-hgupgrade-1: exp-http-v2-0002\r\n
307 307 s> accept: application/mercurial-0.1\r\n
308 308 s> host: $LOCALIP:$HGPORT\r\n (glob)
309 309 s> user-agent: Mercurial debugwireproto\r\n
310 310 s> \r\n
311 311 s> makefile('rb', None)
312 312 s> HTTP/1.1 200 OK\r\n
313 313 s> Server: testing stub value\r\n
314 314 s> Date: $HTTP_DATE$\r\n
315 315 s> Content-Type: application/mercurial-cbor\r\n
316 316 s> Content-Length: *\r\n (glob)
317 317 s> \r\n
318 318 s> \xa3GapibaseDapi/Dapis\xa1Pexp-http-v2-0002\xa4Hcommands\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\x81DpushQframingmediatypes\x81X&application/mercurial-exp-framing-0006Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x82LgeneraldeltaHrevlogv1Nv1capabilitiesY\x01\xd3batch 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
319 319 sending heads command
320 320 s> POST /api/exp-http-v2-0002/ro/heads HTTP/1.1\r\n
321 321 s> Accept-Encoding: identity\r\n
322 322 s> accept: application/mercurial-exp-framing-0006\r\n
323 323 s> content-type: application/mercurial-exp-framing-0006\r\n
324 324 s> content-length: 56\r\n
325 325 s> host: $LOCALIP:$HGPORT\r\n (glob)
326 326 s> user-agent: Mercurial debugwireproto\r\n
327 327 s> \r\n
328 328 s> \x1c\x00\x00\x01\x00\x01\x01\x82\xa1Pcontentencodings\x81Hidentity\x0c\x00\x00\x01\x00\x01\x00\x11\xa1DnameEheads
329 329 s> makefile('rb', None)
330 330 s> HTTP/1.1 200 OK\r\n
331 331 s> Server: testing stub value\r\n
332 332 s> Date: $HTTP_DATE$\r\n
333 333 s> Content-Type: application/mercurial-exp-framing-0006\r\n
334 334 s> Transfer-Encoding: chunked\r\n
335 335 s> \r\n
336 s> 11\r\n
337 s> \t\x00\x00\x01\x00\x02\x01\x92
338 s> Hidentity
339 s> \r\n
340 received frame(size=9; request=1; stream=2; streamflags=stream-begin; type=stream-settings; flags=eos)
336 341 s> 13\r\n
337 s> \x0b\x00\x00\x01\x00\x02\x011
342 s> \x0b\x00\x00\x01\x00\x02\x041
338 343 s> \xa1FstatusBok
339 344 s> \r\n
340 received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
345 received frame(size=11; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation)
341 346 s> 1e\r\n
342 s> \x16\x00\x00\x01\x00\x02\x001
347 s> \x16\x00\x00\x01\x00\x02\x041
343 348 s> \x81T\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00
344 349 s> \r\n
345 received frame(size=22; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
350 received frame(size=22; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation)
346 351 s> 8\r\n
347 352 s> \x00\x00\x00\x01\x00\x02\x002
348 353 s> \r\n
349 354 s> 0\r\n
350 355 s> \r\n
351 356 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
352 357 response: [
353 358 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
354 359 ]
355 360 (sent 2 HTTP requests and * bytes; received * bytes in responses) (glob)
356 361
357 362 $ killdaemons.py
358 363
359 364 HTTP client follows HTTP redirect on handshake to new repo
360 365
361 366 $ cd $TESTTMP
362 367
363 368 $ hg init redirector
364 369 $ hg init redirected
365 370 $ cd redirected
366 371 $ touch foo
367 372 $ hg -q commit -A -m initial
368 373 $ cd ..
369 374
370 375 $ cat > paths.conf << EOF
371 376 > [paths]
372 377 > / = $TESTTMP/*
373 378 > EOF
374 379
375 380 $ cat > redirectext.py << EOF
376 381 > from mercurial import extensions, wireprotoserver
377 382 > def wrappedcallhttp(orig, repo, req, res, proto, cmd):
378 383 > path = req.advertisedurl[len(req.advertisedbaseurl):]
379 384 > if not path.startswith(b'/redirector'):
380 385 > return orig(repo, req, res, proto, cmd)
381 386 > relpath = path[len(b'/redirector'):]
382 387 > res.status = b'301 Redirect'
383 388 > newurl = b'%s/redirected%s' % (req.baseurl, relpath)
384 389 > if not repo.ui.configbool('testing', 'redirectqs', True) and b'?' in newurl:
385 390 > newurl = newurl[0:newurl.index(b'?')]
386 391 > res.headers[b'Location'] = newurl
387 392 > res.headers[b'Content-Type'] = b'text/plain'
388 393 > res.setbodybytes(b'redirected')
389 394 > return True
390 395 >
391 396 > extensions.wrapfunction(wireprotoserver, '_callhttp', wrappedcallhttp)
392 397 > EOF
393 398
394 399 $ hg --config extensions.redirect=$TESTTMP/redirectext.py \
395 400 > --config server.compressionengines=zlib \
396 401 > serve --web-conf paths.conf --pid-file hg.pid -p $HGPORT -d
397 402 $ cat hg.pid > $DAEMON_PIDS
398 403
399 404 Verify our HTTP 301 is served properly
400 405
401 406 $ hg --verbose debugwireproto --peer raw http://$LOCALIP:$HGPORT << EOF
402 407 > httprequest GET /redirector?cmd=capabilities
403 408 > user-agent: test
404 409 > EOF
405 410 using raw connection to peer
406 411 s> GET /redirector?cmd=capabilities HTTP/1.1\r\n
407 412 s> Accept-Encoding: identity\r\n
408 413 s> user-agent: test\r\n
409 414 s> host: $LOCALIP:$HGPORT\r\n (glob)
410 415 s> \r\n
411 416 s> makefile('rb', None)
412 417 s> HTTP/1.1 301 Redirect\r\n
413 418 s> Server: testing stub value\r\n
414 419 s> Date: $HTTP_DATE$\r\n
415 420 s> Location: http://$LOCALIP:$HGPORT/redirected?cmd=capabilities\r\n (glob)
416 421 s> Content-Type: text/plain\r\n
417 422 s> Content-Length: 10\r\n
418 423 s> \r\n
419 424 s> redirected
420 425 s> GET /redirected?cmd=capabilities HTTP/1.1\r\n
421 426 s> Accept-Encoding: identity\r\n
422 427 s> user-agent: test\r\n
423 428 s> host: $LOCALIP:$HGPORT\r\n (glob)
424 429 s> \r\n
425 430 s> makefile('rb', None)
426 431 s> HTTP/1.1 200 Script output follows\r\n
427 432 s> Server: testing stub value\r\n
428 433 s> Date: $HTTP_DATE$\r\n
429 434 s> Content-Type: application/mercurial-0.1\r\n
430 435 s> Content-Length: 467\r\n
431 436 s> \r\n
432 437 s> batch 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
433 438
434 439 Test with the HTTP peer
435 440
436 441 $ hg --verbose debugwireproto http://$LOCALIP:$HGPORT/redirector << EOF
437 442 > command heads
438 443 > EOF
439 444 s> GET /redirector?cmd=capabilities HTTP/1.1\r\n
440 445 s> Accept-Encoding: identity\r\n
441 446 s> accept: application/mercurial-0.1\r\n
442 447 s> host: $LOCALIP:$HGPORT\r\n (glob)
443 448 s> user-agent: Mercurial debugwireproto\r\n
444 449 s> \r\n
445 450 s> makefile('rb', None)
446 451 s> HTTP/1.1 301 Redirect\r\n
447 452 s> Server: testing stub value\r\n
448 453 s> Date: $HTTP_DATE$\r\n
449 454 s> Location: http://$LOCALIP:$HGPORT/redirected?cmd=capabilities\r\n (glob)
450 455 s> Content-Type: text/plain\r\n
451 456 s> Content-Length: 10\r\n
452 457 s> \r\n
453 458 s> redirected
454 459 s> GET /redirected?cmd=capabilities HTTP/1.1\r\n
455 460 s> Accept-Encoding: identity\r\n
456 461 s> accept: application/mercurial-0.1\r\n
457 462 s> host: $LOCALIP:$HGPORT\r\n (glob)
458 463 s> user-agent: Mercurial debugwireproto\r\n
459 464 s> \r\n
460 465 s> makefile('rb', None)
461 466 s> HTTP/1.1 200 Script output follows\r\n
462 467 s> Server: testing stub value\r\n
463 468 s> Date: $HTTP_DATE$\r\n
464 469 s> Content-Type: application/mercurial-0.1\r\n
465 470 s> Content-Length: 467\r\n
466 471 s> \r\n
467 472 real URL is http://$LOCALIP:$HGPORT/redirected (glob)
468 473 s> batch 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
469 474 sending heads command
470 475 s> GET /redirected?cmd=heads HTTP/1.1\r\n
471 476 s> Accept-Encoding: identity\r\n
472 477 s> vary: X-HgProto-1\r\n
473 478 s> x-hgproto-1: 0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull\r\n
474 479 s> accept: application/mercurial-0.1\r\n
475 480 s> host: $LOCALIP:$HGPORT\r\n (glob)
476 481 s> user-agent: Mercurial debugwireproto\r\n
477 482 s> \r\n
478 483 s> makefile('rb', None)
479 484 s> HTTP/1.1 200 Script output follows\r\n
480 485 s> Server: testing stub value\r\n
481 486 s> Date: $HTTP_DATE$\r\n
482 487 s> Content-Type: application/mercurial-0.1\r\n
483 488 s> Content-Length: 41\r\n
484 489 s> \r\n
485 490 s> 96ee1d7354c4ad7372047672c36a1f561e3a6a4c\n
486 491 response: [
487 492 b'\x96\xee\x1dsT\xc4\xadsr\x04vr\xc3j\x1fV\x1e:jL'
488 493 ]
489 494 (sent 3 HTTP requests and * bytes; received * bytes in responses) (glob)
490 495
491 496 $ killdaemons.py
492 497
493 498 Now test a variation where we strip the query string from the redirect URL.
494 499 (SCM Manager apparently did this and clients would recover from it)
495 500
496 501 $ hg --config extensions.redirect=$TESTTMP/redirectext.py \
497 502 > --config server.compressionengines=zlib \
498 503 > --config testing.redirectqs=false \
499 504 > serve --web-conf paths.conf --pid-file hg.pid -p $HGPORT -d
500 505 $ cat hg.pid > $DAEMON_PIDS
501 506
502 507 $ hg --verbose debugwireproto --peer raw http://$LOCALIP:$HGPORT << EOF
503 508 > httprequest GET /redirector?cmd=capabilities
504 509 > user-agent: test
505 510 > EOF
506 511 using raw connection to peer
507 512 s> GET /redirector?cmd=capabilities HTTP/1.1\r\n
508 513 s> Accept-Encoding: identity\r\n
509 514 s> user-agent: test\r\n
510 515 s> host: $LOCALIP:$HGPORT\r\n (glob)
511 516 s> \r\n
512 517 s> makefile('rb', None)
513 518 s> HTTP/1.1 301 Redirect\r\n
514 519 s> Server: testing stub value\r\n
515 520 s> Date: $HTTP_DATE$\r\n
516 521 s> Location: http://$LOCALIP:$HGPORT/redirected\r\n (glob)
517 522 s> Content-Type: text/plain\r\n
518 523 s> Content-Length: 10\r\n
519 524 s> \r\n
520 525 s> redirected
521 526 s> GET /redirected HTTP/1.1\r\n
522 527 s> Accept-Encoding: identity\r\n
523 528 s> user-agent: test\r\n
524 529 s> host: $LOCALIP:$HGPORT\r\n (glob)
525 530 s> \r\n
526 531 s> makefile('rb', None)
527 532 s> HTTP/1.1 200 Script output follows\r\n
528 533 s> Server: testing stub value\r\n
529 534 s> Date: $HTTP_DATE$\r\n
530 535 s> ETag: W/"*"\r\n (glob)
531 536 s> Content-Type: text/html; charset=ascii\r\n
532 537 s> Transfer-Encoding: chunked\r\n
533 538 s> \r\n
534 539 s> 414\r\n
535 540 s> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">\n
536 541 s> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US">\n
537 542 s> <head>\n
538 543 s> <link rel="icon" href="/redirected/static/hgicon.png" type="image/png" />\n
539 544 s> <meta name="robots" content="index, nofollow" />\n
540 545 s> <link rel="stylesheet" href="/redirected/static/style-paper.css" type="text/css" />\n
541 546 s> <script type="text/javascript" src="/redirected/static/mercurial.js"></script>\n
542 547 s> \n
543 548 s> <title>redirected: log</title>\n
544 549 s> <link rel="alternate" type="application/atom+xml"\n
545 550 s> href="/redirected/atom-log" title="Atom feed for redirected" />\n
546 551 s> <link rel="alternate" type="application/rss+xml"\n
547 552 s> href="/redirected/rss-log" title="RSS feed for redirected" />\n
548 553 s> </head>\n
549 554 s> <body>\n
550 555 s> \n
551 556 s> <div class="container">\n
552 557 s> <div class="menu">\n
553 558 s> <div class="logo">\n
554 559 s> <a href="https://mercurial-scm.org/">\n
555 560 s> <img src="/redirected/static/hglogo.png" alt="mercurial" /></a>\n
556 561 s> </div>\n
557 562 s> <ul>\n
558 563 s> <li class="active">log</li>\n
559 564 s> <li><a href="/redirected/graph/tip">graph</a></li>\n
560 565 s> <li><a href="/redirected/tags">tags</a></li>\n
561 566 s> <li><a href="
562 567 s> \r\n
563 568 s> 810\r\n
564 569 s> /redirected/bookmarks">bookmarks</a></li>\n
565 570 s> <li><a href="/redirected/branches">branches</a></li>\n
566 571 s> </ul>\n
567 572 s> <ul>\n
568 573 s> <li><a href="/redirected/rev/tip">changeset</a></li>\n
569 574 s> <li><a href="/redirected/file/tip">browse</a></li>\n
570 575 s> </ul>\n
571 576 s> <ul>\n
572 577 s> \n
573 578 s> </ul>\n
574 579 s> <ul>\n
575 580 s> <li><a href="/redirected/help">help</a></li>\n
576 581 s> </ul>\n
577 582 s> <div class="atom-logo">\n
578 583 s> <a href="/redirected/atom-log" title="subscribe to atom feed">\n
579 584 s> <img class="atom-logo" src="/redirected/static/feed-icon-14x14.png" alt="atom feed" />\n
580 585 s> </a>\n
581 586 s> </div>\n
582 587 s> </div>\n
583 588 s> \n
584 589 s> <div class="main">\n
585 590 s> <h2 class="breadcrumb"><a href="/">Mercurial</a> &gt; <a href="/redirected">redirected</a> </h2>\n
586 591 s> <h3>log</h3>\n
587 592 s> \n
588 593 s> \n
589 594 s> <form class="search" action="/redirected/log">\n
590 595 s> \n
591 596 s> <p><input name="rev" id="search1" type="text" size="30" value="" /></p>\n
592 597 s> <div id="hint">Find changesets by keywords (author, files, the commit message), revision\n
593 598 s> number or hash, or <a href="/redirected/help/revsets">revset expression</a>.</div>\n
594 599 s> </form>\n
595 600 s> \n
596 601 s> <div class="navigate">\n
597 602 s> <a href="/redirected/shortlog/tip?revcount=30">less</a>\n
598 603 s> <a href="/redirected/shortlog/tip?revcount=120">more</a>\n
599 604 s> | rev 0: <a href="/redirected/shortlog/96ee1d7354c4">(0)</a> <a href="/redirected/shortlog/tip">tip</a> \n
600 605 s> </div>\n
601 606 s> \n
602 607 s> <table class="bigtable">\n
603 608 s> <thead>\n
604 609 s> <tr>\n
605 610 s> <th class="age">age</th>\n
606 611 s> <th class="author">author</th>\n
607 612 s> <th class="description">description</th>\n
608 613 s> </tr>\n
609 614 s> </thead>\n
610 615 s> <tbody class="stripes2">\n
611 616 s> <tr>\n
612 617 s> <td class="age">Thu, 01 Jan 1970 00:00:00 +0000</td>\n
613 618 s> <td class="author">test</td>\n
614 619 s> <td class="description">\n
615 620 s> <a href="/redirected/rev/96ee1d7354c4">initial</a>\n
616 621 s> <span class="phase">draft</span> <span class="branchhead">default</span> <span class="tag">tip</span> \n
617 622 s> </td>\n
618 623 s> </tr>\n
619 624 s> \n
620 625 s> </tbody>\n
621 626 s> </table>\n
622 627 s> \n
623 628 s> <div class="navigate">\n
624 629 s> <a href="/redirected/shortlog/tip?revcount=30">less</a>\n
625 630 s> <a href="/redirected/shortlog/tip?revcount=120">more</a>\n
626 631 s> | rev 0: <a href="/redirected/shortlog/96ee1d7354c4">(0)</a> <a href="/redirected/shortlog/tip">tip</a> \n
627 632 s> </div>\n
628 633 s> \n
629 634 s> <script type="text/javascript">\n
630 635 s> ajaxScrollInit(\n
631 636 s> \'/redirected/shortlog/%next%\',\n
632 637 s> \'\', <!-- NEXTHASH\n
633 638 s> function (htmlText) {
634 639 s> \r\n
635 640 s> 14a\r\n
636 641 s> \n
637 642 s> var m = htmlText.match(/\'(\\w+)\', <!-- NEXTHASH/);\n
638 643 s> return m ? m[1] : null;\n
639 644 s> },\n
640 645 s> \'.bigtable > tbody\',\n
641 646 s> \'<tr class="%class%">\\\n
642 647 s> <td colspan="3" style="text-align: center;">%text%</td>\\\n
643 648 s> </tr>\'\n
644 649 s> );\n
645 650 s> </script>\n
646 651 s> \n
647 652 s> </div>\n
648 653 s> </div>\n
649 654 s> \n
650 655 s> \n
651 656 s> \n
652 657 s> </body>\n
653 658 s> </html>\n
654 659 s> \n
655 660 s> \r\n
656 661 s> 0\r\n
657 662 s> \r\n
658 663
659 664 $ hg --verbose debugwireproto http://$LOCALIP:$HGPORT/redirector << EOF
660 665 > command heads
661 666 > EOF
662 667 s> GET /redirector?cmd=capabilities HTTP/1.1\r\n
663 668 s> Accept-Encoding: identity\r\n
664 669 s> accept: application/mercurial-0.1\r\n
665 670 s> host: $LOCALIP:$HGPORT\r\n (glob)
666 671 s> user-agent: Mercurial debugwireproto\r\n
667 672 s> \r\n
668 673 s> makefile('rb', None)
669 674 s> HTTP/1.1 301 Redirect\r\n
670 675 s> Server: testing stub value\r\n
671 676 s> Date: $HTTP_DATE$\r\n
672 677 s> Location: http://$LOCALIP:$HGPORT/redirected\r\n (glob)
673 678 s> Content-Type: text/plain\r\n
674 679 s> Content-Length: 10\r\n
675 680 s> \r\n
676 681 s> redirected
677 682 s> GET /redirected HTTP/1.1\r\n
678 683 s> Accept-Encoding: identity\r\n
679 684 s> accept: application/mercurial-0.1\r\n
680 685 s> host: $LOCALIP:$HGPORT\r\n (glob)
681 686 s> user-agent: Mercurial debugwireproto\r\n
682 687 s> \r\n
683 688 s> makefile('rb', None)
684 689 s> HTTP/1.1 200 Script output follows\r\n
685 690 s> Server: testing stub value\r\n
686 691 s> Date: $HTTP_DATE$\r\n
687 692 s> ETag: W/"*"\r\n (glob)
688 693 s> Content-Type: text/html; charset=ascii\r\n
689 694 s> Transfer-Encoding: chunked\r\n
690 695 s> \r\n
691 696 real URL is http://$LOCALIP:$HGPORT/redirected (glob)
692 697 s> 414\r\n
693 698 s> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">\n
694 699 s> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US">\n
695 700 s> <head>\n
696 701 s> <link rel="icon" href="/redirected/static/hgicon.png" type="image/png" />\n
697 702 s> <meta name="robots" content="index, nofollow" />\n
698 703 s> <link rel="stylesheet" href="/redirected/static/style-paper.css" type="text/css" />\n
699 704 s> <script type="text/javascript" src="/redirected/static/mercurial.js"></script>\n
700 705 s> \n
701 706 s> <title>redirected: log</title>\n
702 707 s> <link rel="alternate" type="application/atom+xml"\n
703 708 s> href="/redirected/atom-log" title="Atom feed for redirected" />\n
704 709 s> <link rel="alternate" type="application/rss+xml"\n
705 710 s> href="/redirected/rss-log" title="RSS feed for redirected" />\n
706 711 s> </head>\n
707 712 s> <body>\n
708 713 s> \n
709 714 s> <div class="container">\n
710 715 s> <div class="menu">\n
711 716 s> <div class="logo">\n
712 717 s> <a href="https://mercurial-scm.org/">\n
713 718 s> <img src="/redirected/static/hglogo.png" alt="mercurial" /></a>\n
714 719 s> </div>\n
715 720 s> <ul>\n
716 721 s> <li class="active">log</li>\n
717 722 s> <li><a href="/redirected/graph/tip">graph</a></li>\n
718 723 s> <li><a href="/redirected/tags">tags</a
719 724 s> GET /redirected?cmd=capabilities HTTP/1.1\r\n
720 725 s> Accept-Encoding: identity\r\n
721 726 s> accept: application/mercurial-0.1\r\n
722 727 s> host: $LOCALIP:$HGPORT\r\n (glob)
723 728 s> user-agent: Mercurial debugwireproto\r\n
724 729 s> \r\n
725 730 s> makefile('rb', None)
726 731 s> HTTP/1.1 200 Script output follows\r\n
727 732 s> Server: testing stub value\r\n
728 733 s> Date: $HTTP_DATE$\r\n
729 734 s> Content-Type: application/mercurial-0.1\r\n
730 735 s> Content-Length: 467\r\n
731 736 s> \r\n
732 737 real URL is http://$LOCALIP:$HGPORT/redirected (glob)
733 738 s> batch 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
734 739 sending heads command
735 740 s> GET /redirected?cmd=heads HTTP/1.1\r\n
736 741 s> Accept-Encoding: identity\r\n
737 742 s> vary: X-HgProto-1\r\n
738 743 s> x-hgproto-1: 0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull\r\n
739 744 s> accept: application/mercurial-0.1\r\n
740 745 s> host: $LOCALIP:$HGPORT\r\n (glob)
741 746 s> user-agent: Mercurial debugwireproto\r\n
742 747 s> \r\n
743 748 s> makefile('rb', None)
744 749 s> HTTP/1.1 200 Script output follows\r\n
745 750 s> Server: testing stub value\r\n
746 751 s> Date: $HTTP_DATE$\r\n
747 752 s> Content-Type: application/mercurial-0.1\r\n
748 753 s> Content-Length: 41\r\n
749 754 s> \r\n
750 755 s> 96ee1d7354c4ad7372047672c36a1f561e3a6a4c\n
751 756 response: [
752 757 b'\x96\xee\x1dsT\xc4\xadsr\x04vr\xc3j\x1fV\x1e:jL'
753 758 ]
754 759 (sent 4 HTTP requests and * bytes; received * bytes in responses) (glob)
@@ -1,663 +1,668 b''
1 1 #require no-chg
2 2
3 3 $ . $TESTDIR/wireprotohelpers.sh
4 4
5 5 $ hg init server
6 6
7 7 zstd isn't present in plain builds. Make tests easier by removing
8 8 zstd from the equation.
9 9
10 10 $ cat >> server/.hg/hgrc << EOF
11 11 > [server]
12 12 > compressionengines = zlib
13 13 > EOF
14 14
15 15 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid -E error.log
16 16 $ cat hg.pid > $DAEMON_PIDS
17 17
18 18 A normal capabilities request is serviced for version 1
19 19
20 20 $ sendhttpraw << EOF
21 21 > httprequest GET ?cmd=capabilities
22 22 > user-agent: test
23 23 > EOF
24 24 using raw connection to peer
25 25 s> GET /?cmd=capabilities HTTP/1.1\r\n
26 26 s> Accept-Encoding: identity\r\n
27 27 s> user-agent: test\r\n
28 28 s> host: $LOCALIP:$HGPORT\r\n (glob)
29 29 s> \r\n
30 30 s> makefile('rb', None)
31 31 s> HTTP/1.1 200 Script output follows\r\n
32 32 s> Server: testing stub value\r\n
33 33 s> Date: $HTTP_DATE$\r\n
34 34 s> Content-Type: application/mercurial-0.1\r\n
35 35 s> Content-Length: *\r\n (glob)
36 36 s> \r\n
37 37 s> batch 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
38 38
39 39 A proper request without the API server enabled returns the legacy response
40 40
41 41 $ sendhttpraw << EOF
42 42 > httprequest GET ?cmd=capabilities
43 43 > user-agent: test
44 44 > x-hgupgrade-1: foo
45 45 > x-hgproto-1: cbor
46 46 > EOF
47 47 using raw connection to peer
48 48 s> GET /?cmd=capabilities HTTP/1.1\r\n
49 49 s> Accept-Encoding: identity\r\n
50 50 s> user-agent: test\r\n
51 51 s> x-hgproto-1: cbor\r\n
52 52 s> x-hgupgrade-1: foo\r\n
53 53 s> host: $LOCALIP:$HGPORT\r\n (glob)
54 54 s> \r\n
55 55 s> makefile('rb', None)
56 56 s> HTTP/1.1 200 Script output follows\r\n
57 57 s> Server: testing stub value\r\n
58 58 s> Date: $HTTP_DATE$\r\n
59 59 s> Content-Type: application/mercurial-0.1\r\n
60 60 s> Content-Length: *\r\n (glob)
61 61 s> \r\n
62 62 s> batch 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
63 63
64 64 Restart with just API server enabled. This enables serving the new format.
65 65
66 66 $ killdaemons.py
67 67 $ cat error.log
68 68
69 69 $ cat >> server/.hg/hgrc << EOF
70 70 > [experimental]
71 71 > web.apiserver = true
72 72 > EOF
73 73
74 74 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid -E error.log
75 75 $ cat hg.pid > $DAEMON_PIDS
76 76
77 77 X-HgUpgrade-<N> without CBOR advertisement uses legacy response
78 78
79 79 $ sendhttpraw << EOF
80 80 > httprequest GET ?cmd=capabilities
81 81 > user-agent: test
82 82 > x-hgupgrade-1: foo bar
83 83 > EOF
84 84 using raw connection to peer
85 85 s> GET /?cmd=capabilities HTTP/1.1\r\n
86 86 s> Accept-Encoding: identity\r\n
87 87 s> user-agent: test\r\n
88 88 s> x-hgupgrade-1: foo bar\r\n
89 89 s> host: $LOCALIP:$HGPORT\r\n (glob)
90 90 s> \r\n
91 91 s> makefile('rb', None)
92 92 s> HTTP/1.1 200 Script output follows\r\n
93 93 s> Server: testing stub value\r\n
94 94 s> Date: $HTTP_DATE$\r\n
95 95 s> Content-Type: application/mercurial-0.1\r\n
96 96 s> Content-Length: *\r\n (glob)
97 97 s> \r\n
98 98 s> batch 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
99 99
100 100 X-HgUpgrade-<N> without known serialization in X-HgProto-<N> uses legacy response
101 101
102 102 $ sendhttpraw << EOF
103 103 > httprequest GET ?cmd=capabilities
104 104 > user-agent: test
105 105 > x-hgupgrade-1: foo bar
106 106 > x-hgproto-1: some value
107 107 > EOF
108 108 using raw connection to peer
109 109 s> GET /?cmd=capabilities HTTP/1.1\r\n
110 110 s> Accept-Encoding: identity\r\n
111 111 s> user-agent: test\r\n
112 112 s> x-hgproto-1: some value\r\n
113 113 s> x-hgupgrade-1: foo bar\r\n
114 114 s> host: $LOCALIP:$HGPORT\r\n (glob)
115 115 s> \r\n
116 116 s> makefile('rb', None)
117 117 s> HTTP/1.1 200 Script output follows\r\n
118 118 s> Server: testing stub value\r\n
119 119 s> Date: $HTTP_DATE$\r\n
120 120 s> Content-Type: application/mercurial-0.1\r\n
121 121 s> Content-Length: *\r\n (glob)
122 122 s> \r\n
123 123 s> batch 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
124 124
125 125 X-HgUpgrade-<N> + X-HgProto-<N> headers trigger new response format
126 126
127 127 $ sendhttpraw << EOF
128 128 > httprequest GET ?cmd=capabilities
129 129 > user-agent: test
130 130 > x-hgupgrade-1: foo bar
131 131 > x-hgproto-1: cbor
132 132 > EOF
133 133 using raw connection to peer
134 134 s> GET /?cmd=capabilities HTTP/1.1\r\n
135 135 s> Accept-Encoding: identity\r\n
136 136 s> user-agent: test\r\n
137 137 s> x-hgproto-1: cbor\r\n
138 138 s> x-hgupgrade-1: foo bar\r\n
139 139 s> host: $LOCALIP:$HGPORT\r\n (glob)
140 140 s> \r\n
141 141 s> makefile('rb', None)
142 142 s> HTTP/1.1 200 OK\r\n
143 143 s> Server: testing stub value\r\n
144 144 s> Date: $HTTP_DATE$\r\n
145 145 s> Content-Type: application/mercurial-cbor\r\n
146 146 s> Content-Length: *\r\n (glob)
147 147 s> \r\n
148 148 s> \xa3GapibaseDapi/Dapis\xa0Nv1capabilitiesY\x01\xd3batch 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
149 149 cbor> [
150 150 {
151 151 b'apibase': b'api/',
152 152 b'apis': {},
153 153 b'v1capabilities': b'batch 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'
154 154 }
155 155 ]
156 156
157 157 Restart server to enable HTTPv2
158 158
159 159 $ killdaemons.py
160 160 $ enablehttpv2 server
161 161 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid -E error.log
162 162 $ cat hg.pid > $DAEMON_PIDS
163 163
164 164 Only requested API services are returned
165 165
166 166 $ sendhttpraw << EOF
167 167 > httprequest GET ?cmd=capabilities
168 168 > user-agent: test
169 169 > x-hgupgrade-1: foo bar
170 170 > x-hgproto-1: cbor
171 171 > EOF
172 172 using raw connection to peer
173 173 s> GET /?cmd=capabilities HTTP/1.1\r\n
174 174 s> Accept-Encoding: identity\r\n
175 175 s> user-agent: test\r\n
176 176 s> x-hgproto-1: cbor\r\n
177 177 s> x-hgupgrade-1: foo bar\r\n
178 178 s> host: $LOCALIP:$HGPORT\r\n (glob)
179 179 s> \r\n
180 180 s> makefile('rb', None)
181 181 s> HTTP/1.1 200 OK\r\n
182 182 s> Server: testing stub value\r\n
183 183 s> Date: $HTTP_DATE$\r\n
184 184 s> Content-Type: application/mercurial-cbor\r\n
185 185 s> Content-Length: *\r\n (glob)
186 186 s> \r\n
187 187 s> \xa3GapibaseDapi/Dapis\xa0Nv1capabilitiesY\x01\xd3batch 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
188 188 cbor> [
189 189 {
190 190 b'apibase': b'api/',
191 191 b'apis': {},
192 192 b'v1capabilities': b'batch 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'
193 193 }
194 194 ]
195 195
196 196 Request for HTTPv2 service returns information about it
197 197
198 198 $ sendhttpraw << EOF
199 199 > httprequest GET ?cmd=capabilities
200 200 > user-agent: test
201 201 > x-hgupgrade-1: exp-http-v2-0002 foo bar
202 202 > x-hgproto-1: cbor
203 203 > EOF
204 204 using raw connection to peer
205 205 s> GET /?cmd=capabilities HTTP/1.1\r\n
206 206 s> Accept-Encoding: identity\r\n
207 207 s> user-agent: test\r\n
208 208 s> x-hgproto-1: cbor\r\n
209 209 s> x-hgupgrade-1: exp-http-v2-0002 foo bar\r\n
210 210 s> host: $LOCALIP:$HGPORT\r\n (glob)
211 211 s> \r\n
212 212 s> makefile('rb', None)
213 213 s> HTTP/1.1 200 OK\r\n
214 214 s> Server: testing stub value\r\n
215 215 s> Date: $HTTP_DATE$\r\n
216 216 s> Content-Type: application/mercurial-cbor\r\n
217 217 s> Content-Length: *\r\n (glob)
218 218 s> \r\n
219 219 s> \xa3GapibaseDapi/Dapis\xa1Pexp-http-v2-0002\xa4Hcommands\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\x81DpushQframingmediatypes\x81X&application/mercurial-exp-framing-0006Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x82LgeneraldeltaHrevlogv1Nv1capabilitiesY\x01\xd3batch 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
220 220 cbor> [
221 221 {
222 222 b'apibase': b'api/',
223 223 b'apis': {
224 224 b'exp-http-v2-0002': {
225 225 b'commands': {
226 226 b'branchmap': {
227 227 b'args': {},
228 228 b'permissions': [
229 229 b'pull'
230 230 ]
231 231 },
232 232 b'capabilities': {
233 233 b'args': {},
234 234 b'permissions': [
235 235 b'pull'
236 236 ]
237 237 },
238 238 b'changesetdata': {
239 239 b'args': {
240 240 b'fields': {
241 241 b'default': set([]),
242 242 b'required': False,
243 243 b'type': b'set',
244 244 b'validvalues': set([
245 245 b'bookmarks',
246 246 b'parents',
247 247 b'phase',
248 248 b'revision'
249 249 ])
250 250 },
251 251 b'noderange': {
252 252 b'default': None,
253 253 b'required': False,
254 254 b'type': b'list'
255 255 },
256 256 b'nodes': {
257 257 b'default': None,
258 258 b'required': False,
259 259 b'type': b'list'
260 260 },
261 261 b'nodesdepth': {
262 262 b'default': None,
263 263 b'required': False,
264 264 b'type': b'int'
265 265 }
266 266 },
267 267 b'permissions': [
268 268 b'pull'
269 269 ]
270 270 },
271 271 b'filedata': {
272 272 b'args': {
273 273 b'fields': {
274 274 b'default': set([]),
275 275 b'required': False,
276 276 b'type': b'set',
277 277 b'validvalues': set([
278 278 b'parents',
279 279 b'revision'
280 280 ])
281 281 },
282 282 b'haveparents': {
283 283 b'default': False,
284 284 b'required': False,
285 285 b'type': b'bool'
286 286 },
287 287 b'nodes': {
288 288 b'required': True,
289 289 b'type': b'list'
290 290 },
291 291 b'path': {
292 292 b'required': True,
293 293 b'type': b'bytes'
294 294 }
295 295 },
296 296 b'permissions': [
297 297 b'pull'
298 298 ]
299 299 },
300 300 b'heads': {
301 301 b'args': {
302 302 b'publiconly': {
303 303 b'default': False,
304 304 b'required': False,
305 305 b'type': b'bool'
306 306 }
307 307 },
308 308 b'permissions': [
309 309 b'pull'
310 310 ]
311 311 },
312 312 b'known': {
313 313 b'args': {
314 314 b'nodes': {
315 315 b'default': [],
316 316 b'required': False,
317 317 b'type': b'list'
318 318 }
319 319 },
320 320 b'permissions': [
321 321 b'pull'
322 322 ]
323 323 },
324 324 b'listkeys': {
325 325 b'args': {
326 326 b'namespace': {
327 327 b'required': True,
328 328 b'type': b'bytes'
329 329 }
330 330 },
331 331 b'permissions': [
332 332 b'pull'
333 333 ]
334 334 },
335 335 b'lookup': {
336 336 b'args': {
337 337 b'key': {
338 338 b'required': True,
339 339 b'type': b'bytes'
340 340 }
341 341 },
342 342 b'permissions': [
343 343 b'pull'
344 344 ]
345 345 },
346 346 b'manifestdata': {
347 347 b'args': {
348 348 b'fields': {
349 349 b'default': set([]),
350 350 b'required': False,
351 351 b'type': b'set',
352 352 b'validvalues': set([
353 353 b'parents',
354 354 b'revision'
355 355 ])
356 356 },
357 357 b'haveparents': {
358 358 b'default': False,
359 359 b'required': False,
360 360 b'type': b'bool'
361 361 },
362 362 b'nodes': {
363 363 b'required': True,
364 364 b'type': b'list'
365 365 },
366 366 b'tree': {
367 367 b'required': True,
368 368 b'type': b'bytes'
369 369 }
370 370 },
371 371 b'permissions': [
372 372 b'pull'
373 373 ]
374 374 },
375 375 b'pushkey': {
376 376 b'args': {
377 377 b'key': {
378 378 b'required': True,
379 379 b'type': b'bytes'
380 380 },
381 381 b'namespace': {
382 382 b'required': True,
383 383 b'type': b'bytes'
384 384 },
385 385 b'new': {
386 386 b'required': True,
387 387 b'type': b'bytes'
388 388 },
389 389 b'old': {
390 390 b'required': True,
391 391 b'type': b'bytes'
392 392 }
393 393 },
394 394 b'permissions': [
395 395 b'push'
396 396 ]
397 397 }
398 398 },
399 399 b'framingmediatypes': [
400 400 b'application/mercurial-exp-framing-0006'
401 401 ],
402 402 b'pathfilterprefixes': set([
403 403 b'path:',
404 404 b'rootfilesin:'
405 405 ]),
406 406 b'rawrepoformats': [
407 407 b'generaldelta',
408 408 b'revlogv1'
409 409 ]
410 410 }
411 411 },
412 412 b'v1capabilities': b'batch 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'
413 413 }
414 414 ]
415 415
416 416 capabilities command returns expected info
417 417
418 418 $ sendhttpv2peerhandshake << EOF
419 419 > command capabilities
420 420 > EOF
421 421 creating http peer for wire protocol version 2
422 422 s> GET /?cmd=capabilities HTTP/1.1\r\n
423 423 s> Accept-Encoding: identity\r\n
424 424 s> vary: X-HgProto-1,X-HgUpgrade-1\r\n
425 425 s> x-hgproto-1: cbor\r\n
426 426 s> x-hgupgrade-1: exp-http-v2-0002\r\n
427 427 s> accept: application/mercurial-0.1\r\n
428 428 s> host: $LOCALIP:$HGPORT\r\n (glob)
429 429 s> user-agent: Mercurial debugwireproto\r\n
430 430 s> \r\n
431 431 s> makefile('rb', None)
432 432 s> HTTP/1.1 200 OK\r\n
433 433 s> Server: testing stub value\r\n
434 434 s> Date: $HTTP_DATE$\r\n
435 435 s> Content-Type: application/mercurial-cbor\r\n
436 436 s> Content-Length: *\r\n (glob)
437 437 s> \r\n
438 438 s> \xa3GapibaseDapi/Dapis\xa1Pexp-http-v2-0002\xa4Hcommands\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\x81DpushQframingmediatypes\x81X&application/mercurial-exp-framing-0006Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x82LgeneraldeltaHrevlogv1Nv1capabilitiesY\x01\xd3batch 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
439 439 sending capabilities command
440 440 s> POST /api/exp-http-v2-0002/ro/capabilities HTTP/1.1\r\n
441 441 s> Accept-Encoding: identity\r\n
442 442 s> accept: application/mercurial-exp-framing-0006\r\n
443 443 s> content-type: application/mercurial-exp-framing-0006\r\n
444 444 s> content-length: 63\r\n
445 445 s> host: $LOCALIP:$HGPORT\r\n (glob)
446 446 s> user-agent: Mercurial debugwireproto\r\n
447 447 s> \r\n
448 448 s> \x1c\x00\x00\x01\x00\x01\x01\x82\xa1Pcontentencodings\x81Hidentity\x13\x00\x00\x01\x00\x01\x00\x11\xa1DnameLcapabilities
449 449 s> makefile('rb', None)
450 450 s> HTTP/1.1 200 OK\r\n
451 451 s> Server: testing stub value\r\n
452 452 s> Date: $HTTP_DATE$\r\n
453 453 s> Content-Type: application/mercurial-exp-framing-0006\r\n
454 454 s> Transfer-Encoding: chunked\r\n
455 455 s> \r\n
456 s> 11\r\n
457 s> \t\x00\x00\x01\x00\x02\x01\x92
458 s> Hidentity
459 s> \r\n
460 received frame(size=9; request=1; stream=2; streamflags=stream-begin; type=stream-settings; flags=eos)
456 461 s> 13\r\n
457 s> \x0b\x00\x00\x01\x00\x02\x011
462 s> \x0b\x00\x00\x01\x00\x02\x041
458 463 s> \xa1FstatusBok
459 464 s> \r\n
460 received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
465 received frame(size=11; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation)
461 466 s> 508\r\n
462 s> \x00\x05\x00\x01\x00\x02\x001
467 s> \x00\x05\x00\x01\x00\x02\x041
463 468 s> \xa4Hcommands\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\x81DpushQframingmediatypes\x81X&application/mercurial-exp-framing-0006Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x82LgeneraldeltaHrevlogv1
464 469 s> \r\n
465 received frame(size=1280; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
470 received frame(size=1280; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation)
466 471 s> 8\r\n
467 472 s> \x00\x00\x00\x01\x00\x02\x002
468 473 s> \r\n
469 474 s> 0\r\n
470 475 s> \r\n
471 476 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
472 477 response: gen[
473 478 {
474 479 b'commands': {
475 480 b'branchmap': {
476 481 b'args': {},
477 482 b'permissions': [
478 483 b'pull'
479 484 ]
480 485 },
481 486 b'capabilities': {
482 487 b'args': {},
483 488 b'permissions': [
484 489 b'pull'
485 490 ]
486 491 },
487 492 b'changesetdata': {
488 493 b'args': {
489 494 b'fields': {
490 495 b'default': set([]),
491 496 b'required': False,
492 497 b'type': b'set',
493 498 b'validvalues': set([
494 499 b'bookmarks',
495 500 b'parents',
496 501 b'phase',
497 502 b'revision'
498 503 ])
499 504 },
500 505 b'noderange': {
501 506 b'default': None,
502 507 b'required': False,
503 508 b'type': b'list'
504 509 },
505 510 b'nodes': {
506 511 b'default': None,
507 512 b'required': False,
508 513 b'type': b'list'
509 514 },
510 515 b'nodesdepth': {
511 516 b'default': None,
512 517 b'required': False,
513 518 b'type': b'int'
514 519 }
515 520 },
516 521 b'permissions': [
517 522 b'pull'
518 523 ]
519 524 },
520 525 b'filedata': {
521 526 b'args': {
522 527 b'fields': {
523 528 b'default': set([]),
524 529 b'required': False,
525 530 b'type': b'set',
526 531 b'validvalues': set([
527 532 b'parents',
528 533 b'revision'
529 534 ])
530 535 },
531 536 b'haveparents': {
532 537 b'default': False,
533 538 b'required': False,
534 539 b'type': b'bool'
535 540 },
536 541 b'nodes': {
537 542 b'required': True,
538 543 b'type': b'list'
539 544 },
540 545 b'path': {
541 546 b'required': True,
542 547 b'type': b'bytes'
543 548 }
544 549 },
545 550 b'permissions': [
546 551 b'pull'
547 552 ]
548 553 },
549 554 b'heads': {
550 555 b'args': {
551 556 b'publiconly': {
552 557 b'default': False,
553 558 b'required': False,
554 559 b'type': b'bool'
555 560 }
556 561 },
557 562 b'permissions': [
558 563 b'pull'
559 564 ]
560 565 },
561 566 b'known': {
562 567 b'args': {
563 568 b'nodes': {
564 569 b'default': [],
565 570 b'required': False,
566 571 b'type': b'list'
567 572 }
568 573 },
569 574 b'permissions': [
570 575 b'pull'
571 576 ]
572 577 },
573 578 b'listkeys': {
574 579 b'args': {
575 580 b'namespace': {
576 581 b'required': True,
577 582 b'type': b'bytes'
578 583 }
579 584 },
580 585 b'permissions': [
581 586 b'pull'
582 587 ]
583 588 },
584 589 b'lookup': {
585 590 b'args': {
586 591 b'key': {
587 592 b'required': True,
588 593 b'type': b'bytes'
589 594 }
590 595 },
591 596 b'permissions': [
592 597 b'pull'
593 598 ]
594 599 },
595 600 b'manifestdata': {
596 601 b'args': {
597 602 b'fields': {
598 603 b'default': set([]),
599 604 b'required': False,
600 605 b'type': b'set',
601 606 b'validvalues': set([
602 607 b'parents',
603 608 b'revision'
604 609 ])
605 610 },
606 611 b'haveparents': {
607 612 b'default': False,
608 613 b'required': False,
609 614 b'type': b'bool'
610 615 },
611 616 b'nodes': {
612 617 b'required': True,
613 618 b'type': b'list'
614 619 },
615 620 b'tree': {
616 621 b'required': True,
617 622 b'type': b'bytes'
618 623 }
619 624 },
620 625 b'permissions': [
621 626 b'pull'
622 627 ]
623 628 },
624 629 b'pushkey': {
625 630 b'args': {
626 631 b'key': {
627 632 b'required': True,
628 633 b'type': b'bytes'
629 634 },
630 635 b'namespace': {
631 636 b'required': True,
632 637 b'type': b'bytes'
633 638 },
634 639 b'new': {
635 640 b'required': True,
636 641 b'type': b'bytes'
637 642 },
638 643 b'old': {
639 644 b'required': True,
640 645 b'type': b'bytes'
641 646 }
642 647 },
643 648 b'permissions': [
644 649 b'push'
645 650 ]
646 651 }
647 652 },
648 653 b'framingmediatypes': [
649 654 b'application/mercurial-exp-framing-0006'
650 655 ],
651 656 b'pathfilterprefixes': set([
652 657 b'path:',
653 658 b'rootfilesin:'
654 659 ]),
655 660 b'rawrepoformats': [
656 661 b'generaldelta',
657 662 b'revlogv1'
658 663 ]
659 664 }
660 665 ]
661 666 (sent 2 HTTP requests and * bytes; received * bytes in responses) (glob)
662 667
663 668 $ cat error.log
@@ -1,1290 +1,1310 b''
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 > [server]
15 15 > compressionengines = zlib
16 16 > [extensions]
17 17 > simplecache = $TESTDIR/wireprotosimplecache.py
18 18 > [simplecache]
19 19 > cacheapi = true
20 20 > EOF
21 21
22 22 $ echo a0 > a
23 23 $ echo b0 > b
24 24 $ hg -q commit -A -m 'commit 0'
25 25 $ echo a1 > a
26 26 $ hg commit -m 'commit 1'
27 27
28 28 $ hg --debug debugindex -m
29 29 rev linkrev nodeid p1 p2
30 30 0 0 992f4779029a3df8d0666d00bb924f69634e2641 0000000000000000000000000000000000000000 0000000000000000000000000000000000000000
31 31 1 1 a988fb43583e871d1ed5750ee074c6d840bbbfc8 992f4779029a3df8d0666d00bb924f69634e2641 0000000000000000000000000000000000000000
32 32
33 33 $ hg --config simplecache.redirectsfile=redirects.py serve -p $HGPORT -d --pid-file hg.pid -E error.log
34 34 $ cat hg.pid > $DAEMON_PIDS
35 35
36 36 $ cat > redirects.py << EOF
37 37 > [
38 38 > {
39 39 > b'name': b'target-a',
40 40 > b'protocol': b'http',
41 41 > b'snirequired': False,
42 42 > b'tlsversions': [b'1.2', b'1.3'],
43 43 > b'uris': [b'http://example.com/'],
44 44 > },
45 45 > ]
46 46 > EOF
47 47
48 48 Redirect targets advertised when configured
49 49
50 50 $ sendhttpv2peerhandshake << EOF
51 51 > command capabilities
52 52 > EOF
53 53 creating http peer for wire protocol version 2
54 54 s> GET /?cmd=capabilities HTTP/1.1\r\n
55 55 s> Accept-Encoding: identity\r\n
56 56 s> vary: X-HgProto-1,X-HgUpgrade-1\r\n
57 57 s> x-hgproto-1: cbor\r\n
58 58 s> x-hgupgrade-1: exp-http-v2-0002\r\n
59 59 s> accept: application/mercurial-0.1\r\n
60 60 s> host: $LOCALIP:$HGPORT\r\n (glob)
61 61 s> user-agent: Mercurial debugwireproto\r\n
62 62 s> \r\n
63 63 s> makefile('rb', None)
64 64 s> HTTP/1.1 200 OK\r\n
65 65 s> Server: testing stub value\r\n
66 66 s> Date: $HTTP_DATE$\r\n
67 67 s> Content-Type: application/mercurial-cbor\r\n
68 68 s> Content-Length: 1930\r\n
69 69 s> \r\n
70 70 s> \xa3GapibaseDapi/Dapis\xa1Pexp-http-v2-0002\xa5Hcommands\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\x81DpushQframingmediatypes\x81X&application/mercurial-exp-framing-0006Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x82LgeneraldeltaHrevlogv1Hredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x81\xa5DnameHtarget-aHprotocolDhttpKsnirequired\xf4Ktlsversions\x82C1.2C1.3Duris\x81Shttp://example.com/Nv1capabilitiesY\x01\xd3batch 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
71 71 (remote redirect target target-a is compatible)
72 72 sending capabilities command
73 73 s> POST /api/exp-http-v2-0002/ro/capabilities HTTP/1.1\r\n
74 74 s> Accept-Encoding: identity\r\n
75 75 s> accept: application/mercurial-exp-framing-0006\r\n
76 76 s> content-type: application/mercurial-exp-framing-0006\r\n
77 77 s> content-length: 111\r\n
78 78 s> host: $LOCALIP:$HGPORT\r\n (glob)
79 79 s> user-agent: Mercurial debugwireproto\r\n
80 80 s> \r\n
81 81 s> \x1c\x00\x00\x01\x00\x01\x01\x82\xa1Pcontentencodings\x81HidentityC\x00\x00\x01\x00\x01\x00\x11\xa2DnameLcapabilitiesHredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x81Htarget-a
82 82 s> makefile('rb', None)
83 83 s> HTTP/1.1 200 OK\r\n
84 84 s> Server: testing stub value\r\n
85 85 s> Date: $HTTP_DATE$\r\n
86 86 s> Content-Type: application/mercurial-exp-framing-0006\r\n
87 87 s> Transfer-Encoding: chunked\r\n
88 88 s> \r\n
89 s> 11\r\n
90 s> \t\x00\x00\x01\x00\x02\x01\x92
91 s> Hidentity
92 s> \r\n
93 received frame(size=9; request=1; stream=2; streamflags=stream-begin; type=stream-settings; flags=eos)
89 94 s> 13\r\n
90 s> \x0b\x00\x00\x01\x00\x02\x011
95 s> \x0b\x00\x00\x01\x00\x02\x041
91 96 s> \xa1FstatusBok
92 97 s> \r\n
93 received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
98 received frame(size=11; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation)
94 99 s> 588\r\n
95 s> \x80\x05\x00\x01\x00\x02\x001
100 s> \x80\x05\x00\x01\x00\x02\x041
96 101 s> \xa5Hcommands\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\x81DpushQframingmediatypes\x81X&application/mercurial-exp-framing-0006Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x82LgeneraldeltaHrevlogv1Hredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x81\xa5DnameHtarget-aHprotocolDhttpKsnirequired\xf4Ktlsversions\x82C1.2C1.3Duris\x81Shttp://example.com/
97 102 s> \r\n
98 received frame(size=1408; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
103 received frame(size=1408; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation)
99 104 s> 8\r\n
100 105 s> \x00\x00\x00\x01\x00\x02\x002
101 106 s> \r\n
102 107 s> 0\r\n
103 108 s> \r\n
104 109 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
105 110 response: gen[
106 111 {
107 112 b'commands': {
108 113 b'branchmap': {
109 114 b'args': {},
110 115 b'permissions': [
111 116 b'pull'
112 117 ]
113 118 },
114 119 b'capabilities': {
115 120 b'args': {},
116 121 b'permissions': [
117 122 b'pull'
118 123 ]
119 124 },
120 125 b'changesetdata': {
121 126 b'args': {
122 127 b'fields': {
123 128 b'default': set([]),
124 129 b'required': False,
125 130 b'type': b'set',
126 131 b'validvalues': set([
127 132 b'bookmarks',
128 133 b'parents',
129 134 b'phase',
130 135 b'revision'
131 136 ])
132 137 },
133 138 b'noderange': {
134 139 b'default': None,
135 140 b'required': False,
136 141 b'type': b'list'
137 142 },
138 143 b'nodes': {
139 144 b'default': None,
140 145 b'required': False,
141 146 b'type': b'list'
142 147 },
143 148 b'nodesdepth': {
144 149 b'default': None,
145 150 b'required': False,
146 151 b'type': b'int'
147 152 }
148 153 },
149 154 b'permissions': [
150 155 b'pull'
151 156 ]
152 157 },
153 158 b'filedata': {
154 159 b'args': {
155 160 b'fields': {
156 161 b'default': set([]),
157 162 b'required': False,
158 163 b'type': b'set',
159 164 b'validvalues': set([
160 165 b'parents',
161 166 b'revision'
162 167 ])
163 168 },
164 169 b'haveparents': {
165 170 b'default': False,
166 171 b'required': False,
167 172 b'type': b'bool'
168 173 },
169 174 b'nodes': {
170 175 b'required': True,
171 176 b'type': b'list'
172 177 },
173 178 b'path': {
174 179 b'required': True,
175 180 b'type': b'bytes'
176 181 }
177 182 },
178 183 b'permissions': [
179 184 b'pull'
180 185 ]
181 186 },
182 187 b'heads': {
183 188 b'args': {
184 189 b'publiconly': {
185 190 b'default': False,
186 191 b'required': False,
187 192 b'type': b'bool'
188 193 }
189 194 },
190 195 b'permissions': [
191 196 b'pull'
192 197 ]
193 198 },
194 199 b'known': {
195 200 b'args': {
196 201 b'nodes': {
197 202 b'default': [],
198 203 b'required': False,
199 204 b'type': b'list'
200 205 }
201 206 },
202 207 b'permissions': [
203 208 b'pull'
204 209 ]
205 210 },
206 211 b'listkeys': {
207 212 b'args': {
208 213 b'namespace': {
209 214 b'required': True,
210 215 b'type': b'bytes'
211 216 }
212 217 },
213 218 b'permissions': [
214 219 b'pull'
215 220 ]
216 221 },
217 222 b'lookup': {
218 223 b'args': {
219 224 b'key': {
220 225 b'required': True,
221 226 b'type': b'bytes'
222 227 }
223 228 },
224 229 b'permissions': [
225 230 b'pull'
226 231 ]
227 232 },
228 233 b'manifestdata': {
229 234 b'args': {
230 235 b'fields': {
231 236 b'default': set([]),
232 237 b'required': False,
233 238 b'type': b'set',
234 239 b'validvalues': set([
235 240 b'parents',
236 241 b'revision'
237 242 ])
238 243 },
239 244 b'haveparents': {
240 245 b'default': False,
241 246 b'required': False,
242 247 b'type': b'bool'
243 248 },
244 249 b'nodes': {
245 250 b'required': True,
246 251 b'type': b'list'
247 252 },
248 253 b'tree': {
249 254 b'required': True,
250 255 b'type': b'bytes'
251 256 }
252 257 },
253 258 b'permissions': [
254 259 b'pull'
255 260 ]
256 261 },
257 262 b'pushkey': {
258 263 b'args': {
259 264 b'key': {
260 265 b'required': True,
261 266 b'type': b'bytes'
262 267 },
263 268 b'namespace': {
264 269 b'required': True,
265 270 b'type': b'bytes'
266 271 },
267 272 b'new': {
268 273 b'required': True,
269 274 b'type': b'bytes'
270 275 },
271 276 b'old': {
272 277 b'required': True,
273 278 b'type': b'bytes'
274 279 }
275 280 },
276 281 b'permissions': [
277 282 b'push'
278 283 ]
279 284 }
280 285 },
281 286 b'framingmediatypes': [
282 287 b'application/mercurial-exp-framing-0006'
283 288 ],
284 289 b'pathfilterprefixes': set([
285 290 b'path:',
286 291 b'rootfilesin:'
287 292 ]),
288 293 b'rawrepoformats': [
289 294 b'generaldelta',
290 295 b'revlogv1'
291 296 ],
292 297 b'redirect': {
293 298 b'hashes': [
294 299 b'sha256',
295 300 b'sha1'
296 301 ],
297 302 b'targets': [
298 303 {
299 304 b'name': b'target-a',
300 305 b'protocol': b'http',
301 306 b'snirequired': False,
302 307 b'tlsversions': [
303 308 b'1.2',
304 309 b'1.3'
305 310 ],
306 311 b'uris': [
307 312 b'http://example.com/'
308 313 ]
309 314 }
310 315 ]
311 316 }
312 317 }
313 318 ]
314 319 (sent 2 HTTP requests and * bytes; received * bytes in responses) (glob)
315 320
316 321 Unknown protocol is filtered from compatible targets
317 322
318 323 $ cat > redirects.py << EOF
319 324 > [
320 325 > {
321 326 > b'name': b'target-a',
322 327 > b'protocol': b'http',
323 328 > b'uris': [b'http://example.com/'],
324 329 > },
325 330 > {
326 331 > b'name': b'target-b',
327 332 > b'protocol': b'unknown',
328 333 > b'uris': [b'unknown://example.com/'],
329 334 > },
330 335 > ]
331 336 > EOF
332 337
333 338 $ sendhttpv2peerhandshake << EOF
334 339 > command capabilities
335 340 > EOF
336 341 creating http peer for wire protocol version 2
337 342 s> GET /?cmd=capabilities HTTP/1.1\r\n
338 343 s> Accept-Encoding: identity\r\n
339 344 s> vary: X-HgProto-1,X-HgUpgrade-1\r\n
340 345 s> x-hgproto-1: cbor\r\n
341 346 s> x-hgupgrade-1: exp-http-v2-0002\r\n
342 347 s> accept: application/mercurial-0.1\r\n
343 348 s> host: $LOCALIP:$HGPORT\r\n (glob)
344 349 s> user-agent: Mercurial debugwireproto\r\n
345 350 s> \r\n
346 351 s> makefile('rb', None)
347 352 s> HTTP/1.1 200 OK\r\n
348 353 s> Server: testing stub value\r\n
349 354 s> Date: $HTTP_DATE$\r\n
350 355 s> Content-Type: application/mercurial-cbor\r\n
351 356 s> Content-Length: 1957\r\n
352 357 s> \r\n
353 358 s> \xa3GapibaseDapi/Dapis\xa1Pexp-http-v2-0002\xa5Hcommands\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\x81DpushQframingmediatypes\x81X&application/mercurial-exp-framing-0006Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x82LgeneraldeltaHrevlogv1Hredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x82\xa3DnameHtarget-aHprotocolDhttpDuris\x81Shttp://example.com/\xa3DnameHtarget-bHprotocolGunknownDuris\x81Vunknown://example.com/Nv1capabilitiesY\x01\xd3batch 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
354 359 (remote redirect target target-a is compatible)
355 360 (remote redirect target target-b uses unsupported protocol: unknown)
356 361 sending capabilities command
357 362 s> POST /api/exp-http-v2-0002/ro/capabilities HTTP/1.1\r\n
358 363 s> Accept-Encoding: identity\r\n
359 364 s> accept: application/mercurial-exp-framing-0006\r\n
360 365 s> content-type: application/mercurial-exp-framing-0006\r\n
361 366 s> content-length: 111\r\n
362 367 s> host: $LOCALIP:$HGPORT\r\n (glob)
363 368 s> user-agent: Mercurial debugwireproto\r\n
364 369 s> \r\n
365 370 s> \x1c\x00\x00\x01\x00\x01\x01\x82\xa1Pcontentencodings\x81HidentityC\x00\x00\x01\x00\x01\x00\x11\xa2DnameLcapabilitiesHredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x81Htarget-a
366 371 s> makefile('rb', None)
367 372 s> HTTP/1.1 200 OK\r\n
368 373 s> Server: testing stub value\r\n
369 374 s> Date: $HTTP_DATE$\r\n
370 375 s> Content-Type: application/mercurial-exp-framing-0006\r\n
371 376 s> Transfer-Encoding: chunked\r\n
372 377 s> \r\n
378 s> 11\r\n
379 s> \t\x00\x00\x01\x00\x02\x01\x92
380 s> Hidentity
381 s> \r\n
382 received frame(size=9; request=1; stream=2; streamflags=stream-begin; type=stream-settings; flags=eos)
373 383 s> 13\r\n
374 s> \x0b\x00\x00\x01\x00\x02\x011
384 s> \x0b\x00\x00\x01\x00\x02\x041
375 385 s> \xa1FstatusBok
376 386 s> \r\n
377 received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
387 received frame(size=11; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation)
378 388 s> 5a3\r\n
379 s> \x9b\x05\x00\x01\x00\x02\x001
389 s> \x9b\x05\x00\x01\x00\x02\x041
380 390 s> \xa5Hcommands\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\x81DpushQframingmediatypes\x81X&application/mercurial-exp-framing-0006Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x82LgeneraldeltaHrevlogv1Hredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x82\xa3DnameHtarget-aHprotocolDhttpDuris\x81Shttp://example.com/\xa3DnameHtarget-bHprotocolGunknownDuris\x81Vunknown://example.com/
381 391 s> \r\n
382 received frame(size=1435; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
392 received frame(size=1435; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation)
383 393 s> 8\r\n
384 394 s> \x00\x00\x00\x01\x00\x02\x002
385 395 s> \r\n
386 396 s> 0\r\n
387 397 s> \r\n
388 398 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
389 399 response: gen[
390 400 {
391 401 b'commands': {
392 402 b'branchmap': {
393 403 b'args': {},
394 404 b'permissions': [
395 405 b'pull'
396 406 ]
397 407 },
398 408 b'capabilities': {
399 409 b'args': {},
400 410 b'permissions': [
401 411 b'pull'
402 412 ]
403 413 },
404 414 b'changesetdata': {
405 415 b'args': {
406 416 b'fields': {
407 417 b'default': set([]),
408 418 b'required': False,
409 419 b'type': b'set',
410 420 b'validvalues': set([
411 421 b'bookmarks',
412 422 b'parents',
413 423 b'phase',
414 424 b'revision'
415 425 ])
416 426 },
417 427 b'noderange': {
418 428 b'default': None,
419 429 b'required': False,
420 430 b'type': b'list'
421 431 },
422 432 b'nodes': {
423 433 b'default': None,
424 434 b'required': False,
425 435 b'type': b'list'
426 436 },
427 437 b'nodesdepth': {
428 438 b'default': None,
429 439 b'required': False,
430 440 b'type': b'int'
431 441 }
432 442 },
433 443 b'permissions': [
434 444 b'pull'
435 445 ]
436 446 },
437 447 b'filedata': {
438 448 b'args': {
439 449 b'fields': {
440 450 b'default': set([]),
441 451 b'required': False,
442 452 b'type': b'set',
443 453 b'validvalues': set([
444 454 b'parents',
445 455 b'revision'
446 456 ])
447 457 },
448 458 b'haveparents': {
449 459 b'default': False,
450 460 b'required': False,
451 461 b'type': b'bool'
452 462 },
453 463 b'nodes': {
454 464 b'required': True,
455 465 b'type': b'list'
456 466 },
457 467 b'path': {
458 468 b'required': True,
459 469 b'type': b'bytes'
460 470 }
461 471 },
462 472 b'permissions': [
463 473 b'pull'
464 474 ]
465 475 },
466 476 b'heads': {
467 477 b'args': {
468 478 b'publiconly': {
469 479 b'default': False,
470 480 b'required': False,
471 481 b'type': b'bool'
472 482 }
473 483 },
474 484 b'permissions': [
475 485 b'pull'
476 486 ]
477 487 },
478 488 b'known': {
479 489 b'args': {
480 490 b'nodes': {
481 491 b'default': [],
482 492 b'required': False,
483 493 b'type': b'list'
484 494 }
485 495 },
486 496 b'permissions': [
487 497 b'pull'
488 498 ]
489 499 },
490 500 b'listkeys': {
491 501 b'args': {
492 502 b'namespace': {
493 503 b'required': True,
494 504 b'type': b'bytes'
495 505 }
496 506 },
497 507 b'permissions': [
498 508 b'pull'
499 509 ]
500 510 },
501 511 b'lookup': {
502 512 b'args': {
503 513 b'key': {
504 514 b'required': True,
505 515 b'type': b'bytes'
506 516 }
507 517 },
508 518 b'permissions': [
509 519 b'pull'
510 520 ]
511 521 },
512 522 b'manifestdata': {
513 523 b'args': {
514 524 b'fields': {
515 525 b'default': set([]),
516 526 b'required': False,
517 527 b'type': b'set',
518 528 b'validvalues': set([
519 529 b'parents',
520 530 b'revision'
521 531 ])
522 532 },
523 533 b'haveparents': {
524 534 b'default': False,
525 535 b'required': False,
526 536 b'type': b'bool'
527 537 },
528 538 b'nodes': {
529 539 b'required': True,
530 540 b'type': b'list'
531 541 },
532 542 b'tree': {
533 543 b'required': True,
534 544 b'type': b'bytes'
535 545 }
536 546 },
537 547 b'permissions': [
538 548 b'pull'
539 549 ]
540 550 },
541 551 b'pushkey': {
542 552 b'args': {
543 553 b'key': {
544 554 b'required': True,
545 555 b'type': b'bytes'
546 556 },
547 557 b'namespace': {
548 558 b'required': True,
549 559 b'type': b'bytes'
550 560 },
551 561 b'new': {
552 562 b'required': True,
553 563 b'type': b'bytes'
554 564 },
555 565 b'old': {
556 566 b'required': True,
557 567 b'type': b'bytes'
558 568 }
559 569 },
560 570 b'permissions': [
561 571 b'push'
562 572 ]
563 573 }
564 574 },
565 575 b'framingmediatypes': [
566 576 b'application/mercurial-exp-framing-0006'
567 577 ],
568 578 b'pathfilterprefixes': set([
569 579 b'path:',
570 580 b'rootfilesin:'
571 581 ]),
572 582 b'rawrepoformats': [
573 583 b'generaldelta',
574 584 b'revlogv1'
575 585 ],
576 586 b'redirect': {
577 587 b'hashes': [
578 588 b'sha256',
579 589 b'sha1'
580 590 ],
581 591 b'targets': [
582 592 {
583 593 b'name': b'target-a',
584 594 b'protocol': b'http',
585 595 b'uris': [
586 596 b'http://example.com/'
587 597 ]
588 598 },
589 599 {
590 600 b'name': b'target-b',
591 601 b'protocol': b'unknown',
592 602 b'uris': [
593 603 b'unknown://example.com/'
594 604 ]
595 605 }
596 606 ]
597 607 }
598 608 }
599 609 ]
600 610 (sent 2 HTTP requests and * bytes; received * bytes in responses) (glob)
601 611
602 612 Missing SNI support filters targets that require SNI
603 613
604 614 $ cat > nosni.py << EOF
605 615 > from mercurial import sslutil
606 616 > sslutil.hassni = False
607 617 > EOF
608 618 $ cat >> $HGRCPATH << EOF
609 619 > [extensions]
610 620 > nosni=`pwd`/nosni.py
611 621 > EOF
612 622
613 623 $ cat > redirects.py << EOF
614 624 > [
615 625 > {
616 626 > b'name': b'target-bad-tls',
617 627 > b'protocol': b'https',
618 628 > b'uris': [b'https://example.com/'],
619 629 > b'snirequired': True,
620 630 > },
621 631 > ]
622 632 > EOF
623 633
624 634 $ sendhttpv2peerhandshake << EOF
625 635 > command capabilities
626 636 > EOF
627 637 creating http peer for wire protocol version 2
628 638 s> GET /?cmd=capabilities HTTP/1.1\r\n
629 639 s> Accept-Encoding: identity\r\n
630 640 s> vary: X-HgProto-1,X-HgUpgrade-1\r\n
631 641 s> x-hgproto-1: cbor\r\n
632 642 s> x-hgupgrade-1: exp-http-v2-0002\r\n
633 643 s> accept: application/mercurial-0.1\r\n
634 644 s> host: $LOCALIP:$HGPORT\r\n (glob)
635 645 s> user-agent: Mercurial debugwireproto\r\n
636 646 s> \r\n
637 647 s> makefile('rb', None)
638 648 s> HTTP/1.1 200 OK\r\n
639 649 s> Server: testing stub value\r\n
640 650 s> Date: $HTTP_DATE$\r\n
641 651 s> Content-Type: application/mercurial-cbor\r\n
642 652 s> Content-Length: 1917\r\n
643 653 s> \r\n
644 654 s> \xa3GapibaseDapi/Dapis\xa1Pexp-http-v2-0002\xa5Hcommands\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\x81DpushQframingmediatypes\x81X&application/mercurial-exp-framing-0006Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x82LgeneraldeltaHrevlogv1Hredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x81\xa4DnameNtarget-bad-tlsHprotocolEhttpsKsnirequired\xf5Duris\x81Thttps://example.com/Nv1capabilitiesY\x01\xd3batch 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
645 655 (redirect target target-bad-tls requires SNI, which is unsupported)
646 656 sending capabilities command
647 657 s> POST /api/exp-http-v2-0002/ro/capabilities HTTP/1.1\r\n
648 658 s> Accept-Encoding: identity\r\n
649 659 s> accept: application/mercurial-exp-framing-0006\r\n
650 660 s> content-type: application/mercurial-exp-framing-0006\r\n
651 661 s> content-length: 102\r\n
652 662 s> host: $LOCALIP:$HGPORT\r\n (glob)
653 663 s> user-agent: Mercurial debugwireproto\r\n
654 664 s> \r\n
655 665 s> \x1c\x00\x00\x01\x00\x01\x01\x82\xa1Pcontentencodings\x81Hidentity:\x00\x00\x01\x00\x01\x00\x11\xa2DnameLcapabilitiesHredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x80
656 666 s> makefile('rb', None)
657 667 s> HTTP/1.1 200 OK\r\n
658 668 s> Server: testing stub value\r\n
659 669 s> Date: $HTTP_DATE$\r\n
660 670 s> Content-Type: application/mercurial-exp-framing-0006\r\n
661 671 s> Transfer-Encoding: chunked\r\n
662 672 s> \r\n
673 s> 11\r\n
674 s> \t\x00\x00\x01\x00\x02\x01\x92
675 s> Hidentity
676 s> \r\n
677 received frame(size=9; request=1; stream=2; streamflags=stream-begin; type=stream-settings; flags=eos)
663 678 s> 13\r\n
664 s> \x0b\x00\x00\x01\x00\x02\x011
679 s> \x0b\x00\x00\x01\x00\x02\x041
665 680 s> \xa1FstatusBok
666 681 s> \r\n
667 received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
682 received frame(size=11; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation)
668 683 s> 57b\r\n
669 s> s\x05\x00\x01\x00\x02\x001
684 s> s\x05\x00\x01\x00\x02\x041
670 685 s> \xa5Hcommands\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\x81DpushQframingmediatypes\x81X&application/mercurial-exp-framing-0006Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x82LgeneraldeltaHrevlogv1Hredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x81\xa4DnameNtarget-bad-tlsHprotocolEhttpsKsnirequired\xf5Duris\x81Thttps://example.com/
671 686 s> \r\n
672 received frame(size=1395; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
687 received frame(size=1395; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation)
673 688 s> 8\r\n
674 689 s> \x00\x00\x00\x01\x00\x02\x002
675 690 s> \r\n
676 691 s> 0\r\n
677 692 s> \r\n
678 693 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
679 694 response: gen[
680 695 {
681 696 b'commands': {
682 697 b'branchmap': {
683 698 b'args': {},
684 699 b'permissions': [
685 700 b'pull'
686 701 ]
687 702 },
688 703 b'capabilities': {
689 704 b'args': {},
690 705 b'permissions': [
691 706 b'pull'
692 707 ]
693 708 },
694 709 b'changesetdata': {
695 710 b'args': {
696 711 b'fields': {
697 712 b'default': set([]),
698 713 b'required': False,
699 714 b'type': b'set',
700 715 b'validvalues': set([
701 716 b'bookmarks',
702 717 b'parents',
703 718 b'phase',
704 719 b'revision'
705 720 ])
706 721 },
707 722 b'noderange': {
708 723 b'default': None,
709 724 b'required': False,
710 725 b'type': b'list'
711 726 },
712 727 b'nodes': {
713 728 b'default': None,
714 729 b'required': False,
715 730 b'type': b'list'
716 731 },
717 732 b'nodesdepth': {
718 733 b'default': None,
719 734 b'required': False,
720 735 b'type': b'int'
721 736 }
722 737 },
723 738 b'permissions': [
724 739 b'pull'
725 740 ]
726 741 },
727 742 b'filedata': {
728 743 b'args': {
729 744 b'fields': {
730 745 b'default': set([]),
731 746 b'required': False,
732 747 b'type': b'set',
733 748 b'validvalues': set([
734 749 b'parents',
735 750 b'revision'
736 751 ])
737 752 },
738 753 b'haveparents': {
739 754 b'default': False,
740 755 b'required': False,
741 756 b'type': b'bool'
742 757 },
743 758 b'nodes': {
744 759 b'required': True,
745 760 b'type': b'list'
746 761 },
747 762 b'path': {
748 763 b'required': True,
749 764 b'type': b'bytes'
750 765 }
751 766 },
752 767 b'permissions': [
753 768 b'pull'
754 769 ]
755 770 },
756 771 b'heads': {
757 772 b'args': {
758 773 b'publiconly': {
759 774 b'default': False,
760 775 b'required': False,
761 776 b'type': b'bool'
762 777 }
763 778 },
764 779 b'permissions': [
765 780 b'pull'
766 781 ]
767 782 },
768 783 b'known': {
769 784 b'args': {
770 785 b'nodes': {
771 786 b'default': [],
772 787 b'required': False,
773 788 b'type': b'list'
774 789 }
775 790 },
776 791 b'permissions': [
777 792 b'pull'
778 793 ]
779 794 },
780 795 b'listkeys': {
781 796 b'args': {
782 797 b'namespace': {
783 798 b'required': True,
784 799 b'type': b'bytes'
785 800 }
786 801 },
787 802 b'permissions': [
788 803 b'pull'
789 804 ]
790 805 },
791 806 b'lookup': {
792 807 b'args': {
793 808 b'key': {
794 809 b'required': True,
795 810 b'type': b'bytes'
796 811 }
797 812 },
798 813 b'permissions': [
799 814 b'pull'
800 815 ]
801 816 },
802 817 b'manifestdata': {
803 818 b'args': {
804 819 b'fields': {
805 820 b'default': set([]),
806 821 b'required': False,
807 822 b'type': b'set',
808 823 b'validvalues': set([
809 824 b'parents',
810 825 b'revision'
811 826 ])
812 827 },
813 828 b'haveparents': {
814 829 b'default': False,
815 830 b'required': False,
816 831 b'type': b'bool'
817 832 },
818 833 b'nodes': {
819 834 b'required': True,
820 835 b'type': b'list'
821 836 },
822 837 b'tree': {
823 838 b'required': True,
824 839 b'type': b'bytes'
825 840 }
826 841 },
827 842 b'permissions': [
828 843 b'pull'
829 844 ]
830 845 },
831 846 b'pushkey': {
832 847 b'args': {
833 848 b'key': {
834 849 b'required': True,
835 850 b'type': b'bytes'
836 851 },
837 852 b'namespace': {
838 853 b'required': True,
839 854 b'type': b'bytes'
840 855 },
841 856 b'new': {
842 857 b'required': True,
843 858 b'type': b'bytes'
844 859 },
845 860 b'old': {
846 861 b'required': True,
847 862 b'type': b'bytes'
848 863 }
849 864 },
850 865 b'permissions': [
851 866 b'push'
852 867 ]
853 868 }
854 869 },
855 870 b'framingmediatypes': [
856 871 b'application/mercurial-exp-framing-0006'
857 872 ],
858 873 b'pathfilterprefixes': set([
859 874 b'path:',
860 875 b'rootfilesin:'
861 876 ]),
862 877 b'rawrepoformats': [
863 878 b'generaldelta',
864 879 b'revlogv1'
865 880 ],
866 881 b'redirect': {
867 882 b'hashes': [
868 883 b'sha256',
869 884 b'sha1'
870 885 ],
871 886 b'targets': [
872 887 {
873 888 b'name': b'target-bad-tls',
874 889 b'protocol': b'https',
875 890 b'snirequired': True,
876 891 b'uris': [
877 892 b'https://example.com/'
878 893 ]
879 894 }
880 895 ]
881 896 }
882 897 }
883 898 ]
884 899 (sent 2 HTTP requests and * bytes; received * bytes in responses) (glob)
885 900
886 901 $ cat >> $HGRCPATH << EOF
887 902 > [extensions]
888 903 > nosni=!
889 904 > EOF
890 905
891 906 Unknown tls value is filtered from compatible targets
892 907
893 908 $ cat > redirects.py << EOF
894 909 > [
895 910 > {
896 911 > b'name': b'target-bad-tls',
897 912 > b'protocol': b'https',
898 913 > b'uris': [b'https://example.com/'],
899 914 > b'tlsversions': [b'42', b'39'],
900 915 > },
901 916 > ]
902 917 > EOF
903 918
904 919 $ sendhttpv2peerhandshake << EOF
905 920 > command capabilities
906 921 > EOF
907 922 creating http peer for wire protocol version 2
908 923 s> GET /?cmd=capabilities HTTP/1.1\r\n
909 924 s> Accept-Encoding: identity\r\n
910 925 s> vary: X-HgProto-1,X-HgUpgrade-1\r\n
911 926 s> x-hgproto-1: cbor\r\n
912 927 s> x-hgupgrade-1: exp-http-v2-0002\r\n
913 928 s> accept: application/mercurial-0.1\r\n
914 929 s> host: $LOCALIP:$HGPORT\r\n (glob)
915 930 s> user-agent: Mercurial debugwireproto\r\n
916 931 s> \r\n
917 932 s> makefile('rb', None)
918 933 s> HTTP/1.1 200 OK\r\n
919 934 s> Server: testing stub value\r\n
920 935 s> Date: $HTTP_DATE$\r\n
921 936 s> Content-Type: application/mercurial-cbor\r\n
922 937 s> Content-Length: 1923\r\n
923 938 s> \r\n
924 939 s> \xa3GapibaseDapi/Dapis\xa1Pexp-http-v2-0002\xa5Hcommands\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\x81DpushQframingmediatypes\x81X&application/mercurial-exp-framing-0006Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x82LgeneraldeltaHrevlogv1Hredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x81\xa4DnameNtarget-bad-tlsHprotocolEhttpsKtlsversions\x82B42B39Duris\x81Thttps://example.com/Nv1capabilitiesY\x01\xd3batch 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
925 940 (remote redirect target target-bad-tls requires unsupported TLS versions: 39, 42)
926 941 sending capabilities command
927 942 s> POST /api/exp-http-v2-0002/ro/capabilities HTTP/1.1\r\n
928 943 s> Accept-Encoding: identity\r\n
929 944 s> accept: application/mercurial-exp-framing-0006\r\n
930 945 s> content-type: application/mercurial-exp-framing-0006\r\n
931 946 s> content-length: 102\r\n
932 947 s> host: $LOCALIP:$HGPORT\r\n (glob)
933 948 s> user-agent: Mercurial debugwireproto\r\n
934 949 s> \r\n
935 950 s> \x1c\x00\x00\x01\x00\x01\x01\x82\xa1Pcontentencodings\x81Hidentity:\x00\x00\x01\x00\x01\x00\x11\xa2DnameLcapabilitiesHredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x80
936 951 s> makefile('rb', None)
937 952 s> HTTP/1.1 200 OK\r\n
938 953 s> Server: testing stub value\r\n
939 954 s> Date: $HTTP_DATE$\r\n
940 955 s> Content-Type: application/mercurial-exp-framing-0006\r\n
941 956 s> Transfer-Encoding: chunked\r\n
942 957 s> \r\n
958 s> 11\r\n
959 s> \t\x00\x00\x01\x00\x02\x01\x92
960 s> Hidentity
961 s> \r\n
962 received frame(size=9; request=1; stream=2; streamflags=stream-begin; type=stream-settings; flags=eos)
943 963 s> 13\r\n
944 s> \x0b\x00\x00\x01\x00\x02\x011
964 s> \x0b\x00\x00\x01\x00\x02\x041
945 965 s> \xa1FstatusBok
946 966 s> \r\n
947 received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
967 received frame(size=11; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation)
948 968 s> 581\r\n
949 s> y\x05\x00\x01\x00\x02\x001
969 s> y\x05\x00\x01\x00\x02\x041
950 970 s> \xa5Hcommands\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\x81DpushQframingmediatypes\x81X&application/mercurial-exp-framing-0006Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x82LgeneraldeltaHrevlogv1Hredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x81\xa4DnameNtarget-bad-tlsHprotocolEhttpsKtlsversions\x82B42B39Duris\x81Thttps://example.com/
951 971 s> \r\n
952 received frame(size=1401; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
972 received frame(size=1401; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation)
953 973 s> 8\r\n
954 974 s> \x00\x00\x00\x01\x00\x02\x002
955 975 s> \r\n
956 976 s> 0\r\n
957 977 s> \r\n
958 978 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
959 979 response: gen[
960 980 {
961 981 b'commands': {
962 982 b'branchmap': {
963 983 b'args': {},
964 984 b'permissions': [
965 985 b'pull'
966 986 ]
967 987 },
968 988 b'capabilities': {
969 989 b'args': {},
970 990 b'permissions': [
971 991 b'pull'
972 992 ]
973 993 },
974 994 b'changesetdata': {
975 995 b'args': {
976 996 b'fields': {
977 997 b'default': set([]),
978 998 b'required': False,
979 999 b'type': b'set',
980 1000 b'validvalues': set([
981 1001 b'bookmarks',
982 1002 b'parents',
983 1003 b'phase',
984 1004 b'revision'
985 1005 ])
986 1006 },
987 1007 b'noderange': {
988 1008 b'default': None,
989 1009 b'required': False,
990 1010 b'type': b'list'
991 1011 },
992 1012 b'nodes': {
993 1013 b'default': None,
994 1014 b'required': False,
995 1015 b'type': b'list'
996 1016 },
997 1017 b'nodesdepth': {
998 1018 b'default': None,
999 1019 b'required': False,
1000 1020 b'type': b'int'
1001 1021 }
1002 1022 },
1003 1023 b'permissions': [
1004 1024 b'pull'
1005 1025 ]
1006 1026 },
1007 1027 b'filedata': {
1008 1028 b'args': {
1009 1029 b'fields': {
1010 1030 b'default': set([]),
1011 1031 b'required': False,
1012 1032 b'type': b'set',
1013 1033 b'validvalues': set([
1014 1034 b'parents',
1015 1035 b'revision'
1016 1036 ])
1017 1037 },
1018 1038 b'haveparents': {
1019 1039 b'default': False,
1020 1040 b'required': False,
1021 1041 b'type': b'bool'
1022 1042 },
1023 1043 b'nodes': {
1024 1044 b'required': True,
1025 1045 b'type': b'list'
1026 1046 },
1027 1047 b'path': {
1028 1048 b'required': True,
1029 1049 b'type': b'bytes'
1030 1050 }
1031 1051 },
1032 1052 b'permissions': [
1033 1053 b'pull'
1034 1054 ]
1035 1055 },
1036 1056 b'heads': {
1037 1057 b'args': {
1038 1058 b'publiconly': {
1039 1059 b'default': False,
1040 1060 b'required': False,
1041 1061 b'type': b'bool'
1042 1062 }
1043 1063 },
1044 1064 b'permissions': [
1045 1065 b'pull'
1046 1066 ]
1047 1067 },
1048 1068 b'known': {
1049 1069 b'args': {
1050 1070 b'nodes': {
1051 1071 b'default': [],
1052 1072 b'required': False,
1053 1073 b'type': b'list'
1054 1074 }
1055 1075 },
1056 1076 b'permissions': [
1057 1077 b'pull'
1058 1078 ]
1059 1079 },
1060 1080 b'listkeys': {
1061 1081 b'args': {
1062 1082 b'namespace': {
1063 1083 b'required': True,
1064 1084 b'type': b'bytes'
1065 1085 }
1066 1086 },
1067 1087 b'permissions': [
1068 1088 b'pull'
1069 1089 ]
1070 1090 },
1071 1091 b'lookup': {
1072 1092 b'args': {
1073 1093 b'key': {
1074 1094 b'required': True,
1075 1095 b'type': b'bytes'
1076 1096 }
1077 1097 },
1078 1098 b'permissions': [
1079 1099 b'pull'
1080 1100 ]
1081 1101 },
1082 1102 b'manifestdata': {
1083 1103 b'args': {
1084 1104 b'fields': {
1085 1105 b'default': set([]),
1086 1106 b'required': False,
1087 1107 b'type': b'set',
1088 1108 b'validvalues': set([
1089 1109 b'parents',
1090 1110 b'revision'
1091 1111 ])
1092 1112 },
1093 1113 b'haveparents': {
1094 1114 b'default': False,
1095 1115 b'required': False,
1096 1116 b'type': b'bool'
1097 1117 },
1098 1118 b'nodes': {
1099 1119 b'required': True,
1100 1120 b'type': b'list'
1101 1121 },
1102 1122 b'tree': {
1103 1123 b'required': True,
1104 1124 b'type': b'bytes'
1105 1125 }
1106 1126 },
1107 1127 b'permissions': [
1108 1128 b'pull'
1109 1129 ]
1110 1130 },
1111 1131 b'pushkey': {
1112 1132 b'args': {
1113 1133 b'key': {
1114 1134 b'required': True,
1115 1135 b'type': b'bytes'
1116 1136 },
1117 1137 b'namespace': {
1118 1138 b'required': True,
1119 1139 b'type': b'bytes'
1120 1140 },
1121 1141 b'new': {
1122 1142 b'required': True,
1123 1143 b'type': b'bytes'
1124 1144 },
1125 1145 b'old': {
1126 1146 b'required': True,
1127 1147 b'type': b'bytes'
1128 1148 }
1129 1149 },
1130 1150 b'permissions': [
1131 1151 b'push'
1132 1152 ]
1133 1153 }
1134 1154 },
1135 1155 b'framingmediatypes': [
1136 1156 b'application/mercurial-exp-framing-0006'
1137 1157 ],
1138 1158 b'pathfilterprefixes': set([
1139 1159 b'path:',
1140 1160 b'rootfilesin:'
1141 1161 ]),
1142 1162 b'rawrepoformats': [
1143 1163 b'generaldelta',
1144 1164 b'revlogv1'
1145 1165 ],
1146 1166 b'redirect': {
1147 1167 b'hashes': [
1148 1168 b'sha256',
1149 1169 b'sha1'
1150 1170 ],
1151 1171 b'targets': [
1152 1172 {
1153 1173 b'name': b'target-bad-tls',
1154 1174 b'protocol': b'https',
1155 1175 b'tlsversions': [
1156 1176 b'42',
1157 1177 b'39'
1158 1178 ],
1159 1179 b'uris': [
1160 1180 b'https://example.com/'
1161 1181 ]
1162 1182 }
1163 1183 ]
1164 1184 }
1165 1185 }
1166 1186 ]
1167 1187 (sent 2 HTTP requests and * bytes; received * bytes in responses) (glob)
1168 1188
1169 1189 Set up the server to issue content redirects to its built-in API server.
1170 1190
1171 1191 $ cat > redirects.py << EOF
1172 1192 > [
1173 1193 > {
1174 1194 > b'name': b'local',
1175 1195 > b'protocol': b'http',
1176 1196 > b'uris': [b'http://example.com/'],
1177 1197 > },
1178 1198 > ]
1179 1199 > EOF
1180 1200
1181 1201 Request to eventual cache URL should return 404 (validating the cache server works)
1182 1202
1183 1203 $ sendhttpraw << EOF
1184 1204 > httprequest GET api/simplecache/missingkey
1185 1205 > user-agent: test
1186 1206 > EOF
1187 1207 using raw connection to peer
1188 1208 s> GET /api/simplecache/missingkey HTTP/1.1\r\n
1189 1209 s> Accept-Encoding: identity\r\n
1190 1210 s> user-agent: test\r\n
1191 1211 s> host: $LOCALIP:$HGPORT\r\n (glob)
1192 1212 s> \r\n
1193 1213 s> makefile('rb', None)
1194 1214 s> HTTP/1.1 404 Not Found\r\n
1195 1215 s> Server: testing stub value\r\n
1196 1216 s> Date: $HTTP_DATE$\r\n
1197 1217 s> Content-Type: text/plain\r\n
1198 1218 s> Content-Length: 22\r\n
1199 1219 s> \r\n
1200 1220 s> key not found in cache
1201 1221
1202 1222 Send a cacheable request
1203 1223
1204 1224 $ sendhttpv2peer << EOF
1205 1225 > command manifestdata
1206 1226 > nodes eval:[b'\x99\x2f\x47\x79\x02\x9a\x3d\xf8\xd0\x66\x6d\x00\xbb\x92\x4f\x69\x63\x4e\x26\x41']
1207 1227 > tree eval:b''
1208 1228 > fields eval:[b'parents']
1209 1229 > EOF
1210 1230 creating http peer for wire protocol version 2
1211 1231 sending manifestdata command
1212 1232 response: gen[
1213 1233 {
1214 1234 b'totalitems': 1
1215 1235 },
1216 1236 {
1217 1237 b'node': b'\x99/Gy\x02\x9a=\xf8\xd0fm\x00\xbb\x92OicN&A',
1218 1238 b'parents': [
1219 1239 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
1220 1240 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
1221 1241 ]
1222 1242 }
1223 1243 ]
1224 1244
1225 1245 Cached entry should be available on server
1226 1246
1227 1247 $ sendhttpraw << EOF
1228 1248 > httprequest GET api/simplecache/64b3162af49ea3c88e8ce2785e03ed7b88a2d6ca
1229 1249 > user-agent: test
1230 1250 > EOF
1231 1251 using raw connection to peer
1232 1252 s> GET /api/simplecache/64b3162af49ea3c88e8ce2785e03ed7b88a2d6ca HTTP/1.1\r\n
1233 1253 s> Accept-Encoding: identity\r\n
1234 1254 s> user-agent: test\r\n
1235 1255 s> host: $LOCALIP:$HGPORT\r\n (glob)
1236 1256 s> \r\n
1237 1257 s> makefile('rb', None)
1238 1258 s> HTTP/1.1 200 OK\r\n
1239 1259 s> Server: testing stub value\r\n
1240 1260 s> Date: $HTTP_DATE$\r\n
1241 1261 s> Content-Type: application/mercurial-cbor\r\n
1242 1262 s> Content-Length: 91\r\n
1243 1263 s> \r\n
1244 1264 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
1245 1265 cbor> [
1246 1266 {
1247 1267 b'totalitems': 1
1248 1268 },
1249 1269 {
1250 1270 b'node': b'\x99/Gy\x02\x9a=\xf8\xd0fm\x00\xbb\x92OicN&A',
1251 1271 b'parents': [
1252 1272 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
1253 1273 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
1254 1274 ]
1255 1275 }
1256 1276 ]
1257 1277
1258 1278 2nd request should result in content redirect response
1259 1279
1260 1280 $ sendhttpv2peer << EOF
1261 1281 > command manifestdata
1262 1282 > nodes eval:[b'\x99\x2f\x47\x79\x02\x9a\x3d\xf8\xd0\x66\x6d\x00\xbb\x92\x4f\x69\x63\x4e\x26\x41']
1263 1283 > tree eval:b''
1264 1284 > fields eval:[b'parents']
1265 1285 > EOF
1266 1286 creating http peer for wire protocol version 2
1267 1287 sending manifestdata command
1268 1288 response: gen[
1269 1289 {
1270 1290 b'totalitems': 1
1271 1291 },
1272 1292 {
1273 1293 b'node': b'\x99/Gy\x02\x9a=\xf8\xd0fm\x00\xbb\x92OicN&A',
1274 1294 b'parents': [
1275 1295 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
1276 1296 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
1277 1297 ]
1278 1298 }
1279 1299 ]
1280 1300
1281 1301 $ cat error.log
1282 1302 $ killdaemons.py
1283 1303
1284 1304 $ cat .hg/blackbox.log
1285 1305 *> cacher constructed for manifestdata (glob)
1286 1306 *> cache miss for 64b3162af49ea3c88e8ce2785e03ed7b88a2d6ca (glob)
1287 1307 *> storing cache entry for 64b3162af49ea3c88e8ce2785e03ed7b88a2d6ca (glob)
1288 1308 *> cacher constructed for manifestdata (glob)
1289 1309 *> cache hit for 64b3162af49ea3c88e8ce2785e03ed7b88a2d6ca (glob)
1290 1310 *> sending content redirect for 64b3162af49ea3c88e8ce2785e03ed7b88a2d6ca to http://*:$HGPORT/api/simplecache/64b3162af49ea3c88e8ce2785e03ed7b88a2d6ca (glob)
@@ -1,623 +1,644 b''
1 1 Tests for wire protocol version 2 exchange.
2 2 Tests in this file should be folded into existing tests once protocol
3 3 v2 has enough features that it can be enabled via #testcase in existing
4 4 tests.
5 5
6 6 $ . $TESTDIR/wireprotohelpers.sh
7 7 $ enablehttpv2client
8 8
9 9 $ hg init server-simple
10 10 $ enablehttpv2 server-simple
11 11 $ cd server-simple
12 12 $ cat >> .hg/hgrc << EOF
13 13 > [phases]
14 14 > publish = false
15 15 > EOF
16 16 $ echo a0 > a
17 17 $ echo b0 > b
18 18 $ hg -q commit -A -m 'commit 0'
19 19
20 20 $ echo a1 > a
21 21 $ hg commit -m 'commit 1'
22 22 $ hg phase --public -r .
23 23 $ echo a2 > a
24 24 $ hg commit -m 'commit 2'
25 25
26 26 $ hg -q up -r 0
27 27 $ echo b1 > b
28 28 $ hg -q commit -m 'head 2 commit 1'
29 29 $ echo b2 > b
30 30 $ hg -q commit -m 'head 2 commit 2'
31 31
32 32 $ hg serve -p $HGPORT -d --pid-file hg.pid -E error.log
33 33 $ cat hg.pid > $DAEMON_PIDS
34 34
35 35 $ cd ..
36 36
37 37 Test basic clone
38 38
39 39 $ hg --debug clone -U http://localhost:$HGPORT client-simple
40 40 using http://localhost:$HGPORT/
41 41 sending capabilities command
42 42 query 1; heads
43 43 sending 2 commands
44 44 sending command heads: {}
45 45 sending command known: {
46 46 'nodes': []
47 47 }
48 received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
49 received frame(size=43; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
48 received frame(size=9; request=1; stream=2; streamflags=stream-begin; type=stream-settings; flags=eos)
49 received frame(size=11; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation)
50 received frame(size=43; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation)
50 51 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
51 received frame(size=11; request=3; stream=2; streamflags=; type=command-response; flags=continuation)
52 received frame(size=1; request=3; stream=2; streamflags=; type=command-response; flags=continuation)
52 received frame(size=11; request=3; stream=2; streamflags=encoded; type=command-response; flags=continuation)
53 received frame(size=1; request=3; stream=2; streamflags=encoded; type=command-response; flags=continuation)
53 54 received frame(size=0; request=3; stream=2; streamflags=; type=command-response; flags=eos)
54 55 sending 1 commands
55 56 sending command changesetdata: {
56 57 'fields': set([
57 58 'bookmarks',
58 59 'parents',
59 60 'phase',
60 61 'revision'
61 62 ]),
62 63 'noderange': [
63 64 [],
64 65 [
65 66 '\xca\xa2\xa4eE\x1d\xd1\xfa\xcd\xa0\xf5\xb1#\x12\xc3UXA\x88\xa1',
66 67 '\xcd%4vk\xec\xe18\xc7\xc1\xaf\xdch%0/\x0fb\xd8\x1f'
67 68 ]
68 69 ]
69 70 }
70 received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
71 received frame(size=941; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
71 received frame(size=9; request=1; stream=2; streamflags=stream-begin; type=stream-settings; flags=eos)
72 received frame(size=11; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation)
73 received frame(size=941; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation)
72 74 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
73 75 add changeset 3390ef850073
74 76 add changeset 4432d83626e8
75 77 add changeset cd2534766bec
76 78 add changeset e96ae20f4188
77 79 add changeset caa2a465451d
78 80 checking for updated bookmarks
79 81 sending 1 commands
80 82 sending command manifestdata: {
81 83 'fields': set([
82 84 'parents',
83 85 'revision'
84 86 ]),
85 87 'haveparents': True,
86 88 'nodes': [
87 89 '\x99/Gy\x02\x9a=\xf8\xd0fm\x00\xbb\x92OicN&A',
88 90 '\xa9\x88\xfbCX>\x87\x1d\x1e\xd5u\x0e\xe0t\xc6\xd8@\xbb\xbf\xc8',
89 91 '\xec\x80NH\x8c \x88\xc25\t\x9a\x10 u\x13\xbe\xcd\xc3\xdd\xa5',
90 92 '\x04\\\x7f9\'\xda\x13\xe7Z\xf8\xf0\xe4\xf0HI\xe4a\xa9x\x0f',
91 93 '7\x9c\xb0\xc2\xe6d\\y\xdd\xc5\x9a\x1dG\'\xa9\xfb\x83\n\xeb&'
92 94 ],
93 95 'tree': ''
94 96 }
95 received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
96 received frame(size=992; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
97 received frame(size=9; request=1; stream=2; streamflags=stream-begin; type=stream-settings; flags=eos)
98 received frame(size=11; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation)
99 received frame(size=992; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation)
97 100 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
98 101 sending 2 commands
99 102 sending command filedata: {
100 103 'fields': set([
101 104 'parents',
102 105 'revision'
103 106 ]),
104 107 'haveparents': True,
105 108 'nodes': [
106 109 '+N\xb0s\x19\xbf\xa0w\xa4\n/\x04\x916Y\xae\xf0\xdaB\xda',
107 110 '\x9a8\x12)\x97\xb3\xac\x97\xbe*\x9a\xa2\xe5V\x83\x83A\xfd\xf2\xcc',
108 111 '\xc2\xa2\x05\xc8\xb2\xad\xe2J\xf2`b\xe5<\xd5\xbc8\x01\xd6`\xda'
109 112 ],
110 113 'path': 'a'
111 114 }
112 115 sending command filedata: {
113 116 'fields': set([
114 117 'parents',
115 118 'revision'
116 119 ]),
117 120 'haveparents': True,
118 121 'nodes': [
119 122 '\x81\x9e%\x8d1\xa5\xe1`f)\xf3e\xbb\x90*\x1b!\xeeB\x16',
120 123 '\xb1zk\xd3g=\x9a\xb8\xce\xd5\x81\xa2\t\xf6/=\xa5\xccEx',
121 124 '\xc5\xb1\xf9\xd3n\x1c\xc18\xbf\xb6\xef\xb3\xde\xb7]\x8c\xcad\x94\xc3'
122 125 ],
123 126 'path': 'b'
124 127 }
125 received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
126 received frame(size=431; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
128 received frame(size=9; request=1; stream=2; streamflags=stream-begin; type=stream-settings; flags=eos)
129 received frame(size=11; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation)
130 received frame(size=431; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation)
127 131 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
128 received frame(size=11; request=3; stream=2; streamflags=; type=command-response; flags=continuation)
129 received frame(size=431; request=3; stream=2; streamflags=; type=command-response; flags=continuation)
132 received frame(size=11; request=3; stream=2; streamflags=encoded; type=command-response; flags=continuation)
133 received frame(size=431; request=3; stream=2; streamflags=encoded; type=command-response; flags=continuation)
130 134 received frame(size=0; request=3; stream=2; streamflags=; type=command-response; flags=eos)
131 135 updating the branch cache
132 136 new changesets 3390ef850073:caa2a465451d (3 drafts)
133 137 (sent 5 HTTP requests and * bytes; received * bytes in responses) (glob)
134 138
135 139 All changesets should have been transferred
136 140
137 141 $ hg -R client-simple debugindex -c
138 142 rev linkrev nodeid p1 p2
139 143 0 0 3390ef850073 000000000000 000000000000
140 144 1 1 4432d83626e8 3390ef850073 000000000000
141 145 2 2 cd2534766bec 4432d83626e8 000000000000
142 146 3 3 e96ae20f4188 3390ef850073 000000000000
143 147 4 4 caa2a465451d e96ae20f4188 000000000000
144 148
145 149 $ hg -R client-simple log -G -T '{rev} {node} {phase}\n'
146 150 o 4 caa2a465451dd1facda0f5b12312c355584188a1 draft
147 151 |
148 152 o 3 e96ae20f4188487b9ae4ef3941c27c81143146e5 draft
149 153 |
150 154 | o 2 cd2534766bece138c7c1afdc6825302f0f62d81f draft
151 155 | |
152 156 | o 1 4432d83626e8a98655f062ec1f2a43b07f7fbbb0 public
153 157 |/
154 158 o 0 3390ef850073fbc2f0dfff2244342c8e9229013a public
155 159
156 160
157 161 All manifests should have been transferred
158 162
159 163 $ hg -R client-simple debugindex -m
160 164 rev linkrev nodeid p1 p2
161 165 0 0 992f4779029a 000000000000 000000000000
162 166 1 1 a988fb43583e 992f4779029a 000000000000
163 167 2 2 ec804e488c20 a988fb43583e 000000000000
164 168 3 3 045c7f3927da 992f4779029a 000000000000
165 169 4 4 379cb0c2e664 045c7f3927da 000000000000
166 170
167 171 Cloning only a specific revision works
168 172
169 173 $ hg --debug clone -U -r 4432d83626e8 http://localhost:$HGPORT client-singlehead
170 174 using http://localhost:$HGPORT/
171 175 sending capabilities command
172 176 sending 1 commands
173 177 sending command lookup: {
174 178 'key': '4432d83626e8'
175 179 }
176 received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
177 received frame(size=21; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
180 received frame(size=9; request=1; stream=2; streamflags=stream-begin; type=stream-settings; flags=eos)
181 received frame(size=11; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation)
182 received frame(size=21; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation)
178 183 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
179 184 query 1; heads
180 185 sending 2 commands
181 186 sending command heads: {}
182 187 sending command known: {
183 188 'nodes': []
184 189 }
185 received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
186 received frame(size=43; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
190 received frame(size=9; request=1; stream=2; streamflags=stream-begin; type=stream-settings; flags=eos)
191 received frame(size=11; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation)
192 received frame(size=43; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation)
187 193 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
188 received frame(size=11; request=3; stream=2; streamflags=; type=command-response; flags=continuation)
189 received frame(size=1; request=3; stream=2; streamflags=; type=command-response; flags=continuation)
194 received frame(size=11; request=3; stream=2; streamflags=encoded; type=command-response; flags=continuation)
195 received frame(size=1; request=3; stream=2; streamflags=encoded; type=command-response; flags=continuation)
190 196 received frame(size=0; request=3; stream=2; streamflags=; type=command-response; flags=eos)
191 197 sending 1 commands
192 198 sending command changesetdata: {
193 199 'fields': set([
194 200 'bookmarks',
195 201 'parents',
196 202 'phase',
197 203 'revision'
198 204 ]),
199 205 'noderange': [
200 206 [],
201 207 [
202 208 'D2\xd86&\xe8\xa9\x86U\xf0b\xec\x1f*C\xb0\x7f\x7f\xbb\xb0'
203 209 ]
204 210 ]
205 211 }
206 received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
207 received frame(size=381; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
212 received frame(size=9; request=1; stream=2; streamflags=stream-begin; type=stream-settings; flags=eos)
213 received frame(size=11; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation)
214 received frame(size=381; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation)
208 215 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
209 216 add changeset 3390ef850073
210 217 add changeset 4432d83626e8
211 218 checking for updated bookmarks
212 219 sending 1 commands
213 220 sending command manifestdata: {
214 221 'fields': set([
215 222 'parents',
216 223 'revision'
217 224 ]),
218 225 'haveparents': True,
219 226 'nodes': [
220 227 '\x99/Gy\x02\x9a=\xf8\xd0fm\x00\xbb\x92OicN&A',
221 228 '\xa9\x88\xfbCX>\x87\x1d\x1e\xd5u\x0e\xe0t\xc6\xd8@\xbb\xbf\xc8'
222 229 ],
223 230 'tree': ''
224 231 }
225 received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
226 received frame(size=404; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
232 received frame(size=9; request=1; stream=2; streamflags=stream-begin; type=stream-settings; flags=eos)
233 received frame(size=11; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation)
234 received frame(size=404; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation)
227 235 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
228 236 sending 2 commands
229 237 sending command filedata: {
230 238 'fields': set([
231 239 'parents',
232 240 'revision'
233 241 ]),
234 242 'haveparents': True,
235 243 'nodes': [
236 244 '+N\xb0s\x19\xbf\xa0w\xa4\n/\x04\x916Y\xae\xf0\xdaB\xda',
237 245 '\x9a8\x12)\x97\xb3\xac\x97\xbe*\x9a\xa2\xe5V\x83\x83A\xfd\xf2\xcc'
238 246 ],
239 247 'path': 'a'
240 248 }
241 249 sending command filedata: {
242 250 'fields': set([
243 251 'parents',
244 252 'revision'
245 253 ]),
246 254 'haveparents': True,
247 255 'nodes': [
248 256 '\x81\x9e%\x8d1\xa5\xe1`f)\xf3e\xbb\x90*\x1b!\xeeB\x16'
249 257 ],
250 258 'path': 'b'
251 259 }
252 received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
253 received frame(size=277; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
260 received frame(size=9; request=1; stream=2; streamflags=stream-begin; type=stream-settings; flags=eos)
261 received frame(size=11; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation)
262 received frame(size=277; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation)
254 263 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
255 received frame(size=11; request=3; stream=2; streamflags=; type=command-response; flags=continuation)
256 received frame(size=123; request=3; stream=2; streamflags=; type=command-response; flags=continuation)
264 received frame(size=11; request=3; stream=2; streamflags=encoded; type=command-response; flags=continuation)
265 received frame(size=123; request=3; stream=2; streamflags=encoded; type=command-response; flags=continuation)
257 266 received frame(size=0; request=3; stream=2; streamflags=; type=command-response; flags=eos)
258 267 updating the branch cache
259 268 new changesets 3390ef850073:4432d83626e8
260 269 (sent 6 HTTP requests and * bytes; received * bytes in responses) (glob)
261 270
262 271 $ cd client-singlehead
263 272
264 273 $ hg log -G -T '{rev} {node} {phase}\n'
265 274 o 1 4432d83626e8a98655f062ec1f2a43b07f7fbbb0 public
266 275 |
267 276 o 0 3390ef850073fbc2f0dfff2244342c8e9229013a public
268 277
269 278
270 279 $ hg debugindex -m
271 280 rev linkrev nodeid p1 p2
272 281 0 0 992f4779029a 000000000000 000000000000
273 282 1 1 a988fb43583e 992f4779029a 000000000000
274 283
275 284 Incremental pull works
276 285
277 286 $ hg --debug pull
278 287 pulling from http://localhost:$HGPORT/
279 288 using http://localhost:$HGPORT/
280 289 sending capabilities command
281 290 query 1; heads
282 291 sending 2 commands
283 292 sending command heads: {}
284 293 sending command known: {
285 294 'nodes': [
286 295 'D2\xd86&\xe8\xa9\x86U\xf0b\xec\x1f*C\xb0\x7f\x7f\xbb\xb0'
287 296 ]
288 297 }
289 received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
290 received frame(size=43; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
298 received frame(size=9; request=1; stream=2; streamflags=stream-begin; type=stream-settings; flags=eos)
299 received frame(size=11; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation)
300 received frame(size=43; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation)
291 301 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
292 received frame(size=11; request=3; stream=2; streamflags=; type=command-response; flags=continuation)
293 received frame(size=2; request=3; stream=2; streamflags=; type=command-response; flags=continuation)
302 received frame(size=11; request=3; stream=2; streamflags=encoded; type=command-response; flags=continuation)
303 received frame(size=2; request=3; stream=2; streamflags=encoded; type=command-response; flags=continuation)
294 304 received frame(size=0; request=3; stream=2; streamflags=; type=command-response; flags=eos)
295 305 searching for changes
296 306 all local heads known remotely
297 307 sending 1 commands
298 308 sending command changesetdata: {
299 309 'fields': set([
300 310 'bookmarks',
301 311 'parents',
302 312 'phase',
303 313 'revision'
304 314 ]),
305 315 'noderange': [
306 316 [
307 317 'D2\xd86&\xe8\xa9\x86U\xf0b\xec\x1f*C\xb0\x7f\x7f\xbb\xb0'
308 318 ],
309 319 [
310 320 '\xca\xa2\xa4eE\x1d\xd1\xfa\xcd\xa0\xf5\xb1#\x12\xc3UXA\x88\xa1',
311 321 '\xcd%4vk\xec\xe18\xc7\xc1\xaf\xdch%0/\x0fb\xd8\x1f'
312 322 ]
313 323 ]
314 324 }
315 received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
316 received frame(size=613; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
325 received frame(size=9; request=1; stream=2; streamflags=stream-begin; type=stream-settings; flags=eos)
326 received frame(size=11; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation)
327 received frame(size=613; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation)
317 328 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
318 329 add changeset cd2534766bec
319 330 add changeset e96ae20f4188
320 331 add changeset caa2a465451d
321 332 checking for updated bookmarks
322 333 sending 1 commands
323 334 sending command manifestdata: {
324 335 'fields': set([
325 336 'parents',
326 337 'revision'
327 338 ]),
328 339 'haveparents': True,
329 340 'nodes': [
330 341 '\xec\x80NH\x8c \x88\xc25\t\x9a\x10 u\x13\xbe\xcd\xc3\xdd\xa5',
331 342 '\x04\\\x7f9\'\xda\x13\xe7Z\xf8\xf0\xe4\xf0HI\xe4a\xa9x\x0f',
332 343 '7\x9c\xb0\xc2\xe6d\\y\xdd\xc5\x9a\x1dG\'\xa9\xfb\x83\n\xeb&'
333 344 ],
334 345 'tree': ''
335 346 }
336 received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
337 received frame(size=601; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
347 received frame(size=9; request=1; stream=2; streamflags=stream-begin; type=stream-settings; flags=eos)
348 received frame(size=11; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation)
349 received frame(size=601; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation)
338 350 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
339 351 sending 2 commands
340 352 sending command filedata: {
341 353 'fields': set([
342 354 'parents',
343 355 'revision'
344 356 ]),
345 357 'haveparents': True,
346 358 'nodes': [
347 359 '+N\xb0s\x19\xbf\xa0w\xa4\n/\x04\x916Y\xae\xf0\xdaB\xda',
348 360 '\xc2\xa2\x05\xc8\xb2\xad\xe2J\xf2`b\xe5<\xd5\xbc8\x01\xd6`\xda'
349 361 ],
350 362 'path': 'a'
351 363 }
352 364 sending command filedata: {
353 365 'fields': set([
354 366 'parents',
355 367 'revision'
356 368 ]),
357 369 'haveparents': True,
358 370 'nodes': [
359 371 '\x81\x9e%\x8d1\xa5\xe1`f)\xf3e\xbb\x90*\x1b!\xeeB\x16',
360 372 '\xb1zk\xd3g=\x9a\xb8\xce\xd5\x81\xa2\t\xf6/=\xa5\xccEx',
361 373 '\xc5\xb1\xf9\xd3n\x1c\xc18\xbf\xb6\xef\xb3\xde\xb7]\x8c\xcad\x94\xc3'
362 374 ],
363 375 'path': 'b'
364 376 }
365 received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
366 received frame(size=277; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
377 received frame(size=9; request=1; stream=2; streamflags=stream-begin; type=stream-settings; flags=eos)
378 received frame(size=11; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation)
379 received frame(size=277; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation)
367 380 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
368 received frame(size=11; request=3; stream=2; streamflags=; type=command-response; flags=continuation)
369 received frame(size=431; request=3; stream=2; streamflags=; type=command-response; flags=continuation)
381 received frame(size=11; request=3; stream=2; streamflags=encoded; type=command-response; flags=continuation)
382 received frame(size=431; request=3; stream=2; streamflags=encoded; type=command-response; flags=continuation)
370 383 received frame(size=0; request=3; stream=2; streamflags=; type=command-response; flags=eos)
371 384 updating the branch cache
372 385 new changesets cd2534766bec:caa2a465451d (3 drafts)
373 386 (run 'hg update' to get a working copy)
374 387 (sent 5 HTTP requests and * bytes; received * bytes in responses) (glob)
375 388
376 389 $ hg log -G -T '{rev} {node} {phase}\n'
377 390 o 4 caa2a465451dd1facda0f5b12312c355584188a1 draft
378 391 |
379 392 o 3 e96ae20f4188487b9ae4ef3941c27c81143146e5 draft
380 393 |
381 394 | o 2 cd2534766bece138c7c1afdc6825302f0f62d81f draft
382 395 | |
383 396 | o 1 4432d83626e8a98655f062ec1f2a43b07f7fbbb0 public
384 397 |/
385 398 o 0 3390ef850073fbc2f0dfff2244342c8e9229013a public
386 399
387 400
388 401 $ hg debugindex -m
389 402 rev linkrev nodeid p1 p2
390 403 0 0 992f4779029a 000000000000 000000000000
391 404 1 1 a988fb43583e 992f4779029a 000000000000
392 405 2 2 ec804e488c20 a988fb43583e 000000000000
393 406 3 3 045c7f3927da 992f4779029a 000000000000
394 407 4 4 379cb0c2e664 045c7f3927da 000000000000
395 408
396 409 Phase-only update works
397 410
398 411 $ hg -R ../server-simple phase --public -r caa2a465451dd
399 412 $ hg --debug pull
400 413 pulling from http://localhost:$HGPORT/
401 414 using http://localhost:$HGPORT/
402 415 sending capabilities command
403 416 query 1; heads
404 417 sending 2 commands
405 418 sending command heads: {}
406 419 sending command known: {
407 420 'nodes': [
408 421 '\xcd%4vk\xec\xe18\xc7\xc1\xaf\xdch%0/\x0fb\xd8\x1f',
409 422 '\xca\xa2\xa4eE\x1d\xd1\xfa\xcd\xa0\xf5\xb1#\x12\xc3UXA\x88\xa1'
410 423 ]
411 424 }
412 received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
413 received frame(size=43; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
425 received frame(size=9; request=1; stream=2; streamflags=stream-begin; type=stream-settings; flags=eos)
426 received frame(size=11; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation)
427 received frame(size=43; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation)
414 428 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
415 received frame(size=11; request=3; stream=2; streamflags=; type=command-response; flags=continuation)
416 received frame(size=3; request=3; stream=2; streamflags=; type=command-response; flags=continuation)
429 received frame(size=11; request=3; stream=2; streamflags=encoded; type=command-response; flags=continuation)
430 received frame(size=3; request=3; stream=2; streamflags=encoded; type=command-response; flags=continuation)
417 431 received frame(size=0; request=3; stream=2; streamflags=; type=command-response; flags=eos)
418 432 searching for changes
419 433 all remote heads known locally
420 434 sending 1 commands
421 435 sending command changesetdata: {
422 436 'fields': set([
423 437 'bookmarks',
424 438 'parents',
425 439 'phase',
426 440 'revision'
427 441 ]),
428 442 'noderange': [
429 443 [
430 444 '\xca\xa2\xa4eE\x1d\xd1\xfa\xcd\xa0\xf5\xb1#\x12\xc3UXA\x88\xa1',
431 445 '\xcd%4vk\xec\xe18\xc7\xc1\xaf\xdch%0/\x0fb\xd8\x1f'
432 446 ],
433 447 [
434 448 '\xca\xa2\xa4eE\x1d\xd1\xfa\xcd\xa0\xf5\xb1#\x12\xc3UXA\x88\xa1',
435 449 '\xcd%4vk\xec\xe18\xc7\xc1\xaf\xdch%0/\x0fb\xd8\x1f'
436 450 ]
437 451 ]
438 452 }
439 received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
440 received frame(size=92; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
453 received frame(size=9; request=1; stream=2; streamflags=stream-begin; type=stream-settings; flags=eos)
454 received frame(size=11; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation)
455 received frame(size=92; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation)
441 456 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
442 457 checking for updated bookmarks
443 458 2 local changesets published
444 459 (run 'hg update' to get a working copy)
445 460 (sent 3 HTTP requests and * bytes; received * bytes in responses) (glob)
446 461
447 462 $ hg log -G -T '{rev} {node} {phase}\n'
448 463 o 4 caa2a465451dd1facda0f5b12312c355584188a1 public
449 464 |
450 465 o 3 e96ae20f4188487b9ae4ef3941c27c81143146e5 public
451 466 |
452 467 | o 2 cd2534766bece138c7c1afdc6825302f0f62d81f draft
453 468 | |
454 469 | o 1 4432d83626e8a98655f062ec1f2a43b07f7fbbb0 public
455 470 |/
456 471 o 0 3390ef850073fbc2f0dfff2244342c8e9229013a public
457 472
458 473
459 474 $ cd ..
460 475
461 476 Bookmarks are transferred on clone
462 477
463 478 $ hg -R server-simple bookmark -r 3390ef850073fbc2f0dfff2244342c8e9229013a book-1
464 479 $ hg -R server-simple bookmark -r cd2534766bece138c7c1afdc6825302f0f62d81f book-2
465 480
466 481 $ hg --debug clone -U http://localhost:$HGPORT/ client-bookmarks
467 482 using http://localhost:$HGPORT/
468 483 sending capabilities command
469 484 query 1; heads
470 485 sending 2 commands
471 486 sending command heads: {}
472 487 sending command known: {
473 488 'nodes': []
474 489 }
475 received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
476 received frame(size=43; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
490 received frame(size=9; request=1; stream=2; streamflags=stream-begin; type=stream-settings; flags=eos)
491 received frame(size=11; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation)
492 received frame(size=43; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation)
477 493 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
478 received frame(size=11; request=3; stream=2; streamflags=; type=command-response; flags=continuation)
479 received frame(size=1; request=3; stream=2; streamflags=; type=command-response; flags=continuation)
494 received frame(size=11; request=3; stream=2; streamflags=encoded; type=command-response; flags=continuation)
495 received frame(size=1; request=3; stream=2; streamflags=encoded; type=command-response; flags=continuation)
480 496 received frame(size=0; request=3; stream=2; streamflags=; type=command-response; flags=eos)
481 497 sending 1 commands
482 498 sending command changesetdata: {
483 499 'fields': set([
484 500 'bookmarks',
485 501 'parents',
486 502 'phase',
487 503 'revision'
488 504 ]),
489 505 'noderange': [
490 506 [],
491 507 [
492 508 '\xca\xa2\xa4eE\x1d\xd1\xfa\xcd\xa0\xf5\xb1#\x12\xc3UXA\x88\xa1',
493 509 '\xcd%4vk\xec\xe18\xc7\xc1\xaf\xdch%0/\x0fb\xd8\x1f'
494 510 ]
495 511 ]
496 512 }
497 received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
498 received frame(size=979; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
513 received frame(size=9; request=1; stream=2; streamflags=stream-begin; type=stream-settings; flags=eos)
514 received frame(size=11; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation)
515 received frame(size=979; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation)
499 516 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
500 517 add changeset 3390ef850073
501 518 add changeset 4432d83626e8
502 519 add changeset cd2534766bec
503 520 add changeset e96ae20f4188
504 521 add changeset caa2a465451d
505 522 checking for updated bookmarks
506 523 adding remote bookmark book-1
507 524 adding remote bookmark book-2
508 525 sending 1 commands
509 526 sending command manifestdata: {
510 527 'fields': set([
511 528 'parents',
512 529 'revision'
513 530 ]),
514 531 'haveparents': True,
515 532 'nodes': [
516 533 '\x99/Gy\x02\x9a=\xf8\xd0fm\x00\xbb\x92OicN&A',
517 534 '\xa9\x88\xfbCX>\x87\x1d\x1e\xd5u\x0e\xe0t\xc6\xd8@\xbb\xbf\xc8',
518 535 '\xec\x80NH\x8c \x88\xc25\t\x9a\x10 u\x13\xbe\xcd\xc3\xdd\xa5',
519 536 '\x04\\\x7f9\'\xda\x13\xe7Z\xf8\xf0\xe4\xf0HI\xe4a\xa9x\x0f',
520 537 '7\x9c\xb0\xc2\xe6d\\y\xdd\xc5\x9a\x1dG\'\xa9\xfb\x83\n\xeb&'
521 538 ],
522 539 'tree': ''
523 540 }
524 received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
525 received frame(size=992; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
541 received frame(size=9; request=1; stream=2; streamflags=stream-begin; type=stream-settings; flags=eos)
542 received frame(size=11; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation)
543 received frame(size=992; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation)
526 544 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
527 545 sending 2 commands
528 546 sending command filedata: {
529 547 'fields': set([
530 548 'parents',
531 549 'revision'
532 550 ]),
533 551 'haveparents': True,
534 552 'nodes': [
535 553 '+N\xb0s\x19\xbf\xa0w\xa4\n/\x04\x916Y\xae\xf0\xdaB\xda',
536 554 '\x9a8\x12)\x97\xb3\xac\x97\xbe*\x9a\xa2\xe5V\x83\x83A\xfd\xf2\xcc',
537 555 '\xc2\xa2\x05\xc8\xb2\xad\xe2J\xf2`b\xe5<\xd5\xbc8\x01\xd6`\xda'
538 556 ],
539 557 'path': 'a'
540 558 }
541 559 sending command filedata: {
542 560 'fields': set([
543 561 'parents',
544 562 'revision'
545 563 ]),
546 564 'haveparents': True,
547 565 'nodes': [
548 566 '\x81\x9e%\x8d1\xa5\xe1`f)\xf3e\xbb\x90*\x1b!\xeeB\x16',
549 567 '\xb1zk\xd3g=\x9a\xb8\xce\xd5\x81\xa2\t\xf6/=\xa5\xccEx',
550 568 '\xc5\xb1\xf9\xd3n\x1c\xc18\xbf\xb6\xef\xb3\xde\xb7]\x8c\xcad\x94\xc3'
551 569 ],
552 570 'path': 'b'
553 571 }
554 received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
555 received frame(size=431; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
572 received frame(size=9; request=1; stream=2; streamflags=stream-begin; type=stream-settings; flags=eos)
573 received frame(size=11; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation)
574 received frame(size=431; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation)
556 575 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
557 received frame(size=11; request=3; stream=2; streamflags=; type=command-response; flags=continuation)
558 received frame(size=431; request=3; stream=2; streamflags=; type=command-response; flags=continuation)
576 received frame(size=11; request=3; stream=2; streamflags=encoded; type=command-response; flags=continuation)
577 received frame(size=431; request=3; stream=2; streamflags=encoded; type=command-response; flags=continuation)
559 578 received frame(size=0; request=3; stream=2; streamflags=; type=command-response; flags=eos)
560 579 updating the branch cache
561 580 new changesets 3390ef850073:caa2a465451d (1 drafts)
562 581 (sent 5 HTTP requests and * bytes; received * bytes in responses) (glob)
563 582
564 583 $ hg -R client-bookmarks bookmarks
565 584 book-1 0:3390ef850073
566 585 book-2 2:cd2534766bec
567 586
568 587 Server-side bookmark moves are reflected during `hg pull`
569 588
570 589 $ hg -R server-simple bookmark -r cd2534766bece138c7c1afdc6825302f0f62d81f book-1
571 590 moving bookmark 'book-1' forward from 3390ef850073
572 591
573 592 $ hg -R client-bookmarks --debug pull
574 593 pulling from http://localhost:$HGPORT/
575 594 using http://localhost:$HGPORT/
576 595 sending capabilities command
577 596 query 1; heads
578 597 sending 2 commands
579 598 sending command heads: {}
580 599 sending command known: {
581 600 'nodes': [
582 601 '\xcd%4vk\xec\xe18\xc7\xc1\xaf\xdch%0/\x0fb\xd8\x1f',
583 602 '\xca\xa2\xa4eE\x1d\xd1\xfa\xcd\xa0\xf5\xb1#\x12\xc3UXA\x88\xa1'
584 603 ]
585 604 }
586 received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
587 received frame(size=43; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
605 received frame(size=9; request=1; stream=2; streamflags=stream-begin; type=stream-settings; flags=eos)
606 received frame(size=11; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation)
607 received frame(size=43; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation)
588 608 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
589 received frame(size=11; request=3; stream=2; streamflags=; type=command-response; flags=continuation)
590 received frame(size=3; request=3; stream=2; streamflags=; type=command-response; flags=continuation)
609 received frame(size=11; request=3; stream=2; streamflags=encoded; type=command-response; flags=continuation)
610 received frame(size=3; request=3; stream=2; streamflags=encoded; type=command-response; flags=continuation)
591 611 received frame(size=0; request=3; stream=2; streamflags=; type=command-response; flags=eos)
592 612 searching for changes
593 613 all remote heads known locally
594 614 sending 1 commands
595 615 sending command changesetdata: {
596 616 'fields': set([
597 617 'bookmarks',
598 618 'parents',
599 619 'phase',
600 620 'revision'
601 621 ]),
602 622 'noderange': [
603 623 [
604 624 '\xca\xa2\xa4eE\x1d\xd1\xfa\xcd\xa0\xf5\xb1#\x12\xc3UXA\x88\xa1',
605 625 '\xcd%4vk\xec\xe18\xc7\xc1\xaf\xdch%0/\x0fb\xd8\x1f'
606 626 ],
607 627 [
608 628 '\xca\xa2\xa4eE\x1d\xd1\xfa\xcd\xa0\xf5\xb1#\x12\xc3UXA\x88\xa1',
609 629 '\xcd%4vk\xec\xe18\xc7\xc1\xaf\xdch%0/\x0fb\xd8\x1f'
610 630 ]
611 631 ]
612 632 }
613 received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
614 received frame(size=144; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
633 received frame(size=9; request=1; stream=2; streamflags=stream-begin; type=stream-settings; flags=eos)
634 received frame(size=11; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation)
635 received frame(size=144; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation)
615 636 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
616 637 checking for updated bookmarks
617 638 updating bookmark book-1
618 639 (run 'hg update' to get a working copy)
619 640 (sent 3 HTTP requests and * bytes; received * bytes in responses) (glob)
620 641
621 642 $ hg -R client-bookmarks bookmarks
622 643 book-1 2:cd2534766bec
623 644 book-2 2:cd2534766bec
@@ -1,628 +1,633 b''
1 1 from __future__ import absolute_import, print_function
2 2
3 3 import unittest
4 4
5 5 from mercurial.thirdparty import (
6 6 cbor,
7 7 )
8 8 from mercurial import (
9 9 ui as uimod,
10 10 util,
11 11 wireprotoframing as framing,
12 12 )
13 13 from mercurial.utils import (
14 14 cborutil,
15 15 )
16 16
17 17 ffs = framing.makeframefromhumanstring
18 18
19 19 OK = cbor.dumps({b'status': b'ok'})
20 20
21 21 def makereactor(deferoutput=False):
22 22 ui = uimod.ui()
23 23 return framing.serverreactor(ui, deferoutput=deferoutput)
24 24
25 25 def sendframes(reactor, gen):
26 26 """Send a generator of frame bytearray to a reactor.
27 27
28 28 Emits a generator of results from ``onframerecv()`` calls.
29 29 """
30 30 for frame in gen:
31 31 header = framing.parseheader(frame)
32 32 payload = frame[framing.FRAME_HEADER_SIZE:]
33 33 assert len(payload) == header.length
34 34
35 35 yield reactor.onframerecv(framing.frame(header.requestid,
36 36 header.streamid,
37 37 header.streamflags,
38 38 header.typeid,
39 39 header.flags,
40 40 payload))
41 41
42 42 def sendcommandframes(reactor, stream, rid, cmd, args, datafh=None):
43 43 """Generate frames to run a command and send them to a reactor."""
44 44 return sendframes(reactor,
45 45 framing.createcommandframes(stream, rid, cmd, args,
46 46 datafh))
47 47
48 48
49 49 class ServerReactorTests(unittest.TestCase):
50 50 def _sendsingleframe(self, reactor, f):
51 51 results = list(sendframes(reactor, [f]))
52 52 self.assertEqual(len(results), 1)
53 53
54 54 return results[0]
55 55
56 56 def assertaction(self, res, expected):
57 57 self.assertIsInstance(res, tuple)
58 58 self.assertEqual(len(res), 2)
59 59 self.assertIsInstance(res[1], dict)
60 60 self.assertEqual(res[0], expected)
61 61
62 62 def assertframesequal(self, frames, framestrings):
63 63 expected = [ffs(s) for s in framestrings]
64 64 self.assertEqual(list(frames), expected)
65 65
66 66 def test1framecommand(self):
67 67 """Receiving a command in a single frame yields request to run it."""
68 68 reactor = makereactor()
69 69 stream = framing.stream(1)
70 70 results = list(sendcommandframes(reactor, stream, 1, b'mycommand', {}))
71 71 self.assertEqual(len(results), 1)
72 72 self.assertaction(results[0], b'runcommand')
73 73 self.assertEqual(results[0][1], {
74 74 b'requestid': 1,
75 75 b'command': b'mycommand',
76 76 b'args': {},
77 77 b'redirect': None,
78 78 b'data': None,
79 79 })
80 80
81 81 result = reactor.oninputeof()
82 82 self.assertaction(result, b'noop')
83 83
84 84 def test1argument(self):
85 85 reactor = makereactor()
86 86 stream = framing.stream(1)
87 87 results = list(sendcommandframes(reactor, stream, 41, b'mycommand',
88 88 {b'foo': b'bar'}))
89 89 self.assertEqual(len(results), 1)
90 90 self.assertaction(results[0], b'runcommand')
91 91 self.assertEqual(results[0][1], {
92 92 b'requestid': 41,
93 93 b'command': b'mycommand',
94 94 b'args': {b'foo': b'bar'},
95 95 b'redirect': None,
96 96 b'data': None,
97 97 })
98 98
99 99 def testmultiarguments(self):
100 100 reactor = makereactor()
101 101 stream = framing.stream(1)
102 102 results = list(sendcommandframes(reactor, stream, 1, b'mycommand',
103 103 {b'foo': b'bar', b'biz': b'baz'}))
104 104 self.assertEqual(len(results), 1)
105 105 self.assertaction(results[0], b'runcommand')
106 106 self.assertEqual(results[0][1], {
107 107 b'requestid': 1,
108 108 b'command': b'mycommand',
109 109 b'args': {b'foo': b'bar', b'biz': b'baz'},
110 110 b'redirect': None,
111 111 b'data': None,
112 112 })
113 113
114 114 def testsimplecommanddata(self):
115 115 reactor = makereactor()
116 116 stream = framing.stream(1)
117 117 results = list(sendcommandframes(reactor, stream, 1, b'mycommand', {},
118 118 util.bytesio(b'data!')))
119 119 self.assertEqual(len(results), 2)
120 120 self.assertaction(results[0], b'wantframe')
121 121 self.assertaction(results[1], b'runcommand')
122 122 self.assertEqual(results[1][1], {
123 123 b'requestid': 1,
124 124 b'command': b'mycommand',
125 125 b'args': {},
126 126 b'redirect': None,
127 127 b'data': b'data!',
128 128 })
129 129
130 130 def testmultipledataframes(self):
131 131 frames = [
132 132 ffs(b'1 1 stream-begin command-request new|have-data '
133 133 b"cbor:{b'name': b'mycommand'}"),
134 134 ffs(b'1 1 0 command-data continuation data1'),
135 135 ffs(b'1 1 0 command-data continuation data2'),
136 136 ffs(b'1 1 0 command-data eos data3'),
137 137 ]
138 138
139 139 reactor = makereactor()
140 140 results = list(sendframes(reactor, frames))
141 141 self.assertEqual(len(results), 4)
142 142 for i in range(3):
143 143 self.assertaction(results[i], b'wantframe')
144 144 self.assertaction(results[3], b'runcommand')
145 145 self.assertEqual(results[3][1], {
146 146 b'requestid': 1,
147 147 b'command': b'mycommand',
148 148 b'args': {},
149 149 b'redirect': None,
150 150 b'data': b'data1data2data3',
151 151 })
152 152
153 153 def testargumentanddata(self):
154 154 frames = [
155 155 ffs(b'1 1 stream-begin command-request new|have-data '
156 156 b"cbor:{b'name': b'command', b'args': {b'key': b'val',"
157 157 b"b'foo': b'bar'}}"),
158 158 ffs(b'1 1 0 command-data continuation value1'),
159 159 ffs(b'1 1 0 command-data eos value2'),
160 160 ]
161 161
162 162 reactor = makereactor()
163 163 results = list(sendframes(reactor, frames))
164 164
165 165 self.assertaction(results[-1], b'runcommand')
166 166 self.assertEqual(results[-1][1], {
167 167 b'requestid': 1,
168 168 b'command': b'command',
169 169 b'args': {
170 170 b'key': b'val',
171 171 b'foo': b'bar',
172 172 },
173 173 b'redirect': None,
174 174 b'data': b'value1value2',
175 175 })
176 176
177 177 def testnewandcontinuation(self):
178 178 result = self._sendsingleframe(makereactor(),
179 179 ffs(b'1 1 stream-begin command-request new|continuation '))
180 180 self.assertaction(result, b'error')
181 181 self.assertEqual(result[1], {
182 182 b'message': b'received command request frame with both new and '
183 183 b'continuation flags set',
184 184 })
185 185
186 186 def testneithernewnorcontinuation(self):
187 187 result = self._sendsingleframe(makereactor(),
188 188 ffs(b'1 1 stream-begin command-request 0 '))
189 189 self.assertaction(result, b'error')
190 190 self.assertEqual(result[1], {
191 191 b'message': b'received command request frame with neither new nor '
192 192 b'continuation flags set',
193 193 })
194 194
195 195 def testunexpectedcommanddata(self):
196 196 """Command data frame when not running a command is an error."""
197 197 result = self._sendsingleframe(makereactor(),
198 198 ffs(b'1 1 stream-begin command-data 0 ignored'))
199 199 self.assertaction(result, b'error')
200 200 self.assertEqual(result[1], {
201 201 b'message': b'expected sender protocol settings or command request '
202 202 b'frame; got 2',
203 203 })
204 204
205 205 def testunexpectedcommanddatareceiving(self):
206 206 """Same as above except the command is receiving."""
207 207 results = list(sendframes(makereactor(), [
208 208 ffs(b'1 1 stream-begin command-request new|more '
209 209 b"cbor:{b'name': b'ignored'}"),
210 210 ffs(b'1 1 0 command-data eos ignored'),
211 211 ]))
212 212
213 213 self.assertaction(results[0], b'wantframe')
214 214 self.assertaction(results[1], b'error')
215 215 self.assertEqual(results[1][1], {
216 216 b'message': b'received command data frame for request that is not '
217 217 b'expecting data: 1',
218 218 })
219 219
220 220 def testconflictingrequestidallowed(self):
221 221 """Multiple fully serviced commands with same request ID is allowed."""
222 222 reactor = makereactor()
223 223 results = []
224 224 outstream = reactor.makeoutputstream()
225 225 results.append(self._sendsingleframe(
226 226 reactor, ffs(b'1 1 stream-begin command-request new '
227 227 b"cbor:{b'name': b'command'}")))
228 228 result = reactor.oncommandresponsereadyobjects(
229 229 outstream, 1, [b'response1'])
230 230 self.assertaction(result, b'sendframes')
231 231 list(result[1][b'framegen'])
232 232 results.append(self._sendsingleframe(
233 233 reactor, ffs(b'1 1 stream-begin command-request new '
234 234 b"cbor:{b'name': b'command'}")))
235 235 result = reactor.oncommandresponsereadyobjects(
236 236 outstream, 1, [b'response2'])
237 237 self.assertaction(result, b'sendframes')
238 238 list(result[1][b'framegen'])
239 239 results.append(self._sendsingleframe(
240 240 reactor, ffs(b'1 1 stream-begin command-request new '
241 241 b"cbor:{b'name': b'command'}")))
242 242 result = reactor.oncommandresponsereadyobjects(
243 243 outstream, 1, [b'response3'])
244 244 self.assertaction(result, b'sendframes')
245 245 list(result[1][b'framegen'])
246 246
247 247 for i in range(3):
248 248 self.assertaction(results[i], b'runcommand')
249 249 self.assertEqual(results[i][1], {
250 250 b'requestid': 1,
251 251 b'command': b'command',
252 252 b'args': {},
253 253 b'redirect': None,
254 254 b'data': None,
255 255 })
256 256
257 257 def testconflictingrequestid(self):
258 258 """Request ID for new command matching in-flight command is illegal."""
259 259 results = list(sendframes(makereactor(), [
260 260 ffs(b'1 1 stream-begin command-request new|more '
261 261 b"cbor:{b'name': b'command'}"),
262 262 ffs(b'1 1 0 command-request new '
263 263 b"cbor:{b'name': b'command1'}"),
264 264 ]))
265 265
266 266 self.assertaction(results[0], b'wantframe')
267 267 self.assertaction(results[1], b'error')
268 268 self.assertEqual(results[1][1], {
269 269 b'message': b'request with ID 1 already received',
270 270 })
271 271
272 272 def testinterleavedcommands(self):
273 273 cbor1 = cbor.dumps({
274 274 b'name': b'command1',
275 275 b'args': {
276 276 b'foo': b'bar',
277 277 b'key1': b'val',
278 278 }
279 279 }, canonical=True)
280 280 cbor3 = cbor.dumps({
281 281 b'name': b'command3',
282 282 b'args': {
283 283 b'biz': b'baz',
284 284 b'key': b'val',
285 285 },
286 286 }, canonical=True)
287 287
288 288 results = list(sendframes(makereactor(), [
289 289 ffs(b'1 1 stream-begin command-request new|more %s' % cbor1[0:6]),
290 290 ffs(b'3 1 0 command-request new|more %s' % cbor3[0:10]),
291 291 ffs(b'1 1 0 command-request continuation|more %s' % cbor1[6:9]),
292 292 ffs(b'3 1 0 command-request continuation|more %s' % cbor3[10:13]),
293 293 ffs(b'3 1 0 command-request continuation %s' % cbor3[13:]),
294 294 ffs(b'1 1 0 command-request continuation %s' % cbor1[9:]),
295 295 ]))
296 296
297 297 self.assertEqual([t[0] for t in results], [
298 298 b'wantframe',
299 299 b'wantframe',
300 300 b'wantframe',
301 301 b'wantframe',
302 302 b'runcommand',
303 303 b'runcommand',
304 304 ])
305 305
306 306 self.assertEqual(results[4][1], {
307 307 b'requestid': 3,
308 308 b'command': b'command3',
309 309 b'args': {b'biz': b'baz', b'key': b'val'},
310 310 b'redirect': None,
311 311 b'data': None,
312 312 })
313 313 self.assertEqual(results[5][1], {
314 314 b'requestid': 1,
315 315 b'command': b'command1',
316 316 b'args': {b'foo': b'bar', b'key1': b'val'},
317 317 b'redirect': None,
318 318 b'data': None,
319 319 })
320 320
321 321 def testmissingcommanddataframe(self):
322 322 # The reactor doesn't currently handle partially received commands.
323 323 # So this test is failing to do anything with request 1.
324 324 frames = [
325 325 ffs(b'1 1 stream-begin command-request new|have-data '
326 326 b"cbor:{b'name': b'command1'}"),
327 327 ffs(b'3 1 0 command-request new '
328 328 b"cbor:{b'name': b'command2'}"),
329 329 ]
330 330 results = list(sendframes(makereactor(), frames))
331 331 self.assertEqual(len(results), 2)
332 332 self.assertaction(results[0], b'wantframe')
333 333 self.assertaction(results[1], b'runcommand')
334 334
335 335 def testmissingcommanddataframeflags(self):
336 336 frames = [
337 337 ffs(b'1 1 stream-begin command-request new|have-data '
338 338 b"cbor:{b'name': b'command1'}"),
339 339 ffs(b'1 1 0 command-data 0 data'),
340 340 ]
341 341 results = list(sendframes(makereactor(), frames))
342 342 self.assertEqual(len(results), 2)
343 343 self.assertaction(results[0], b'wantframe')
344 344 self.assertaction(results[1], b'error')
345 345 self.assertEqual(results[1][1], {
346 346 b'message': b'command data frame without flags',
347 347 })
348 348
349 349 def testframefornonreceivingrequest(self):
350 350 """Receiving a frame for a command that is not receiving is illegal."""
351 351 results = list(sendframes(makereactor(), [
352 352 ffs(b'1 1 stream-begin command-request new '
353 353 b"cbor:{b'name': b'command1'}"),
354 354 ffs(b'3 1 0 command-request new|have-data '
355 355 b"cbor:{b'name': b'command3'}"),
356 356 ffs(b'5 1 0 command-data eos ignored'),
357 357 ]))
358 358 self.assertaction(results[2], b'error')
359 359 self.assertEqual(results[2][1], {
360 360 b'message': b'received frame for request that is not receiving: 5',
361 361 })
362 362
363 363 def testsimpleresponse(self):
364 364 """Bytes response to command sends result frames."""
365 365 reactor = makereactor()
366 366 instream = framing.stream(1)
367 367 list(sendcommandframes(reactor, instream, 1, b'mycommand', {}))
368 368
369 369 outstream = reactor.makeoutputstream()
370 370 result = reactor.oncommandresponsereadyobjects(
371 371 outstream, 1, [b'response'])
372 372 self.assertaction(result, b'sendframes')
373 373 self.assertframesequal(result[1][b'framegen'], [
374 b'1 2 stream-begin command-response continuation %s' % OK,
375 b'1 2 0 command-response continuation cbor:b"response"',
374 b'1 2 stream-begin stream-settings eos cbor:b"identity"',
375 b'1 2 encoded command-response continuation %s' % OK,
376 b'1 2 encoded command-response continuation cbor:b"response"',
376 377 b'1 2 0 command-response eos ',
377 378 ])
378 379
379 380 def testmultiframeresponse(self):
380 381 """Bytes response spanning multiple frames is handled."""
381 382 first = b'x' * framing.DEFAULT_MAX_FRAME_SIZE
382 383 second = b'y' * 100
383 384
384 385 reactor = makereactor()
385 386 instream = framing.stream(1)
386 387 list(sendcommandframes(reactor, instream, 1, b'mycommand', {}))
387 388
388 389 outstream = reactor.makeoutputstream()
389 390 result = reactor.oncommandresponsereadyobjects(
390 391 outstream, 1, [first + second])
391 392 self.assertaction(result, b'sendframes')
392 393 self.assertframesequal(result[1][b'framegen'], [
393 b'1 2 stream-begin command-response continuation %s' % OK,
394 b'1 2 0 command-response continuation Y\x80d',
395 b'1 2 0 command-response continuation %s' % first,
396 b'1 2 0 command-response continuation %s' % second,
394 b'1 2 stream-begin stream-settings eos cbor:b"identity"',
395 b'1 2 encoded command-response continuation %s' % OK,
396 b'1 2 encoded command-response continuation Y\x80d',
397 b'1 2 encoded command-response continuation %s' % first,
398 b'1 2 encoded command-response continuation %s' % second,
397 399 b'1 2 0 command-response eos '
398 400 ])
399 401
400 402 def testservererror(self):
401 403 reactor = makereactor()
402 404 instream = framing.stream(1)
403 405 list(sendcommandframes(reactor, instream, 1, b'mycommand', {}))
404 406
405 407 outstream = reactor.makeoutputstream()
406 408 result = reactor.onservererror(outstream, 1, b'some message')
407 409 self.assertaction(result, b'sendframes')
408 410 self.assertframesequal(result[1][b'framegen'], [
409 411 b"1 2 stream-begin error-response 0 "
410 412 b"cbor:{b'type': b'server', "
411 413 b"b'message': [{b'msg': b'some message'}]}",
412 414 ])
413 415
414 416 def test1commanddeferresponse(self):
415 417 """Responses when in deferred output mode are delayed until EOF."""
416 418 reactor = makereactor(deferoutput=True)
417 419 instream = framing.stream(1)
418 420 results = list(sendcommandframes(reactor, instream, 1, b'mycommand',
419 421 {}))
420 422 self.assertEqual(len(results), 1)
421 423 self.assertaction(results[0], b'runcommand')
422 424
423 425 outstream = reactor.makeoutputstream()
424 426 result = reactor.oncommandresponsereadyobjects(
425 427 outstream, 1, [b'response'])
426 428 self.assertaction(result, b'noop')
427 429 result = reactor.oninputeof()
428 430 self.assertaction(result, b'sendframes')
429 431 self.assertframesequal(result[1][b'framegen'], [
430 b'1 2 stream-begin command-response continuation %s' % OK,
431 b'1 2 0 command-response continuation cbor:b"response"',
432 b'1 2 stream-begin stream-settings eos cbor:b"identity"',
433 b'1 2 encoded command-response continuation %s' % OK,
434 b'1 2 encoded command-response continuation cbor:b"response"',
432 435 b'1 2 0 command-response eos ',
433 436 ])
434 437
435 438 def testmultiplecommanddeferresponse(self):
436 439 reactor = makereactor(deferoutput=True)
437 440 instream = framing.stream(1)
438 441 list(sendcommandframes(reactor, instream, 1, b'command1', {}))
439 442 list(sendcommandframes(reactor, instream, 3, b'command2', {}))
440 443
441 444 outstream = reactor.makeoutputstream()
442 445 result = reactor.oncommandresponsereadyobjects(
443 446 outstream, 1, [b'response1'])
444 447 self.assertaction(result, b'noop')
445 448 result = reactor.oncommandresponsereadyobjects(
446 449 outstream, 3, [b'response2'])
447 450 self.assertaction(result, b'noop')
448 451 result = reactor.oninputeof()
449 452 self.assertaction(result, b'sendframes')
450 453 self.assertframesequal(result[1][b'framegen'], [
451 b'1 2 stream-begin command-response continuation %s' % OK,
452 b'1 2 0 command-response continuation cbor:b"response1"',
454 b'1 2 stream-begin stream-settings eos cbor:b"identity"',
455 b'1 2 encoded command-response continuation %s' % OK,
456 b'1 2 encoded command-response continuation cbor:b"response1"',
453 457 b'1 2 0 command-response eos ',
454 b'3 2 0 command-response continuation %s' % OK,
455 b'3 2 0 command-response continuation cbor:b"response2"',
458 b'3 2 encoded command-response continuation %s' % OK,
459 b'3 2 encoded command-response continuation cbor:b"response2"',
456 460 b'3 2 0 command-response eos ',
457 461 ])
458 462
459 463 def testrequestidtracking(self):
460 464 reactor = makereactor(deferoutput=True)
461 465 instream = framing.stream(1)
462 466 list(sendcommandframes(reactor, instream, 1, b'command1', {}))
463 467 list(sendcommandframes(reactor, instream, 3, b'command2', {}))
464 468 list(sendcommandframes(reactor, instream, 5, b'command3', {}))
465 469
466 470 # Register results for commands out of order.
467 471 outstream = reactor.makeoutputstream()
468 472 reactor.oncommandresponsereadyobjects(outstream, 3, [b'response3'])
469 473 reactor.oncommandresponsereadyobjects(outstream, 1, [b'response1'])
470 474 reactor.oncommandresponsereadyobjects(outstream, 5, [b'response5'])
471 475
472 476 result = reactor.oninputeof()
473 477 self.assertaction(result, b'sendframes')
474 478 self.assertframesequal(result[1][b'framegen'], [
475 b'3 2 stream-begin command-response continuation %s' % OK,
476 b'3 2 0 command-response continuation cbor:b"response3"',
479 b'3 2 stream-begin stream-settings eos cbor:b"identity"',
480 b'3 2 encoded command-response continuation %s' % OK,
481 b'3 2 encoded command-response continuation cbor:b"response3"',
477 482 b'3 2 0 command-response eos ',
478 b'1 2 0 command-response continuation %s' % OK,
479 b'1 2 0 command-response continuation cbor:b"response1"',
483 b'1 2 encoded command-response continuation %s' % OK,
484 b'1 2 encoded command-response continuation cbor:b"response1"',
480 485 b'1 2 0 command-response eos ',
481 b'5 2 0 command-response continuation %s' % OK,
482 b'5 2 0 command-response continuation cbor:b"response5"',
486 b'5 2 encoded command-response continuation %s' % OK,
487 b'5 2 encoded command-response continuation cbor:b"response5"',
483 488 b'5 2 0 command-response eos ',
484 489 ])
485 490
486 491 def testduplicaterequestonactivecommand(self):
487 492 """Receiving a request ID that matches a request that isn't finished."""
488 493 reactor = makereactor()
489 494 stream = framing.stream(1)
490 495 list(sendcommandframes(reactor, stream, 1, b'command1', {}))
491 496 results = list(sendcommandframes(reactor, stream, 1, b'command1', {}))
492 497
493 498 self.assertaction(results[0], b'error')
494 499 self.assertEqual(results[0][1], {
495 500 b'message': b'request with ID 1 is already active',
496 501 })
497 502
498 503 def testduplicaterequestonactivecommandnosend(self):
499 504 """Same as above but we've registered a response but haven't sent it."""
500 505 reactor = makereactor()
501 506 instream = framing.stream(1)
502 507 list(sendcommandframes(reactor, instream, 1, b'command1', {}))
503 508 outstream = reactor.makeoutputstream()
504 509 reactor.oncommandresponsereadyobjects(outstream, 1, [b'response'])
505 510
506 511 # We've registered the response but haven't sent it. From the
507 512 # perspective of the reactor, the command is still active.
508 513
509 514 results = list(sendcommandframes(reactor, instream, 1, b'command1', {}))
510 515 self.assertaction(results[0], b'error')
511 516 self.assertEqual(results[0][1], {
512 517 b'message': b'request with ID 1 is already active',
513 518 })
514 519
515 520 def testduplicaterequestaftersend(self):
516 521 """We can use a duplicate request ID after we've sent the response."""
517 522 reactor = makereactor()
518 523 instream = framing.stream(1)
519 524 list(sendcommandframes(reactor, instream, 1, b'command1', {}))
520 525 outstream = reactor.makeoutputstream()
521 526 res = reactor.oncommandresponsereadyobjects(outstream, 1, [b'response'])
522 527 list(res[1][b'framegen'])
523 528
524 529 results = list(sendcommandframes(reactor, instream, 1, b'command1', {}))
525 530 self.assertaction(results[0], b'runcommand')
526 531
527 532 def testprotocolsettingsnoflags(self):
528 533 result = self._sendsingleframe(
529 534 makereactor(),
530 535 ffs(b'0 1 stream-begin sender-protocol-settings 0 '))
531 536 self.assertaction(result, b'error')
532 537 self.assertEqual(result[1], {
533 538 b'message': b'sender protocol settings frame must have '
534 539 b'continuation or end of stream flag set',
535 540 })
536 541
537 542 def testprotocolsettingsconflictflags(self):
538 543 result = self._sendsingleframe(
539 544 makereactor(),
540 545 ffs(b'0 1 stream-begin sender-protocol-settings continuation|eos '))
541 546 self.assertaction(result, b'error')
542 547 self.assertEqual(result[1], {
543 548 b'message': b'sender protocol settings frame cannot have both '
544 549 b'continuation and end of stream flags set',
545 550 })
546 551
547 552 def testprotocolsettingsemptypayload(self):
548 553 result = self._sendsingleframe(
549 554 makereactor(),
550 555 ffs(b'0 1 stream-begin sender-protocol-settings eos '))
551 556 self.assertaction(result, b'error')
552 557 self.assertEqual(result[1], {
553 558 b'message': b'sender protocol settings frame did not contain CBOR '
554 559 b'data',
555 560 })
556 561
557 562 def testprotocolsettingsmultipleobjects(self):
558 563 result = self._sendsingleframe(
559 564 makereactor(),
560 565 ffs(b'0 1 stream-begin sender-protocol-settings eos '
561 566 b'\x46foobar\x43foo'))
562 567 self.assertaction(result, b'error')
563 568 self.assertEqual(result[1], {
564 569 b'message': b'sender protocol settings frame contained multiple '
565 570 b'CBOR values',
566 571 })
567 572
568 573 def testprotocolsettingscontentencodings(self):
569 574 reactor = makereactor()
570 575
571 576 result = self._sendsingleframe(
572 577 reactor,
573 578 ffs(b'0 1 stream-begin sender-protocol-settings eos '
574 579 b'cbor:{b"contentencodings": [b"a", b"b"]}'))
575 580 self.assertaction(result, b'wantframe')
576 581
577 582 self.assertEqual(reactor._state, b'idle')
578 583 self.assertEqual(reactor._sendersettings[b'contentencodings'],
579 584 [b'a', b'b'])
580 585
581 586 def testprotocolsettingsmultipleframes(self):
582 587 reactor = makereactor()
583 588
584 589 data = b''.join(cborutil.streamencode({
585 590 b'contentencodings': [b'value1', b'value2'],
586 591 }))
587 592
588 593 results = list(sendframes(reactor, [
589 594 ffs(b'0 1 stream-begin sender-protocol-settings continuation %s' %
590 595 data[0:5]),
591 596 ffs(b'0 1 0 sender-protocol-settings eos %s' % data[5:]),
592 597 ]))
593 598
594 599 self.assertEqual(len(results), 2)
595 600
596 601 self.assertaction(results[0], b'wantframe')
597 602 self.assertaction(results[1], b'wantframe')
598 603
599 604 self.assertEqual(reactor._state, b'idle')
600 605 self.assertEqual(reactor._sendersettings[b'contentencodings'],
601 606 [b'value1', b'value2'])
602 607
603 608 def testprotocolsettingsbadcbor(self):
604 609 result = self._sendsingleframe(
605 610 makereactor(),
606 611 ffs(b'0 1 stream-begin sender-protocol-settings eos badvalue'))
607 612 self.assertaction(result, b'error')
608 613
609 614 def testprotocolsettingsnoninitial(self):
610 615 # Cannot have protocol settings frames as non-initial frames.
611 616 reactor = makereactor()
612 617
613 618 stream = framing.stream(1)
614 619 results = list(sendcommandframes(reactor, stream, 1, b'mycommand', {}))
615 620 self.assertEqual(len(results), 1)
616 621 self.assertaction(results[0], b'runcommand')
617 622
618 623 result = self._sendsingleframe(
619 624 reactor,
620 625 ffs(b'0 1 0 sender-protocol-settings eos '))
621 626 self.assertaction(result, b'error')
622 627 self.assertEqual(result[1], {
623 628 b'message': b'expected command request frame; got 8',
624 629 })
625 630
626 631 if __name__ == '__main__':
627 632 import silenttestrunner
628 633 silenttestrunner.main(__name__)
General Comments 0
You need to be logged in to leave comments. Login now