Show More
@@ -2631,8 +2631,8 b' def debugwireproto(ui, repo, path=None, ' | |||||
2631 |
|
2631 | |||
2632 | ``--peer`` can be used to bypass the handshake protocol and construct a |
|
2632 | ``--peer`` can be used to bypass the handshake protocol and construct a | |
2633 | peer instance using the specified class type. Valid values are ``raw``, |
|
2633 | peer instance using the specified class type. Valid values are ``raw``, | |
2634 |
``ssh1``, and ``ssh2``. ``raw`` instances only allow sending |
|
2634 | ``http2``, ``ssh1``, and ``ssh2``. ``raw`` instances only allow sending | |
2635 | payloads and don't support higher-level command actions. |
|
2635 | raw data payloads and don't support higher-level command actions. | |
2636 |
|
2636 | |||
2637 | ``--noreadstderr`` can be used to disable automatic reading from stderr |
|
2637 | ``--noreadstderr`` can be used to disable automatic reading from stderr | |
2638 | of the peer (for SSH connections only). Disabling automatic reading of |
|
2638 | of the peer (for SSH connections only). Disabling automatic reading of | |
@@ -2678,8 +2678,10 b' def debugwireproto(ui, repo, path=None, ' | |||||
2678 | command listkeys |
|
2678 | command listkeys | |
2679 | namespace bookmarks |
|
2679 | namespace bookmarks | |
2680 |
|
2680 | |||
2681 | Values are interpreted as Python b'' literals. This allows encoding |
|
2681 | If the value begins with ``eval:``, it will be interpreted as a Python | |
2682 | special byte sequences via backslash escaping. |
|
2682 | literal expression. Otherwise values are interpreted as Python b'' literals. | |
|
2683 | This allows sending complex types and encoding special byte sequences via | |||
|
2684 | backslash escaping. | |||
2683 |
|
2685 | |||
2684 | The following arguments have special meaning: |
|
2686 | The following arguments have special meaning: | |
2685 |
|
2687 | |||
@@ -2803,7 +2805,7 b' def debugwireproto(ui, repo, path=None, ' | |||||
2803 | if opts['localssh'] and not repo: |
|
2805 | if opts['localssh'] and not repo: | |
2804 | raise error.Abort(_('--localssh requires a repository')) |
|
2806 | raise error.Abort(_('--localssh requires a repository')) | |
2805 |
|
2807 | |||
2806 | if opts['peer'] and opts['peer'] not in ('raw', 'ssh1', 'ssh2'): |
|
2808 | if opts['peer'] and opts['peer'] not in ('raw', 'http2', 'ssh1', 'ssh2'): | |
2807 | raise error.Abort(_('invalid value for --peer'), |
|
2809 | raise error.Abort(_('invalid value for --peer'), | |
2808 | hint=_('valid values are "raw", "ssh1", and "ssh2"')) |
|
2810 | hint=_('valid values are "raw", "ssh1", and "ssh2"')) | |
2809 |
|
2811 | |||
@@ -2877,18 +2879,20 b' def debugwireproto(ui, repo, path=None, ' | |||||
2877 | raise error.Abort(_('only http:// paths are currently supported')) |
|
2879 | raise error.Abort(_('only http:// paths are currently supported')) | |
2878 |
|
2880 | |||
2879 | url, authinfo = u.authinfo() |
|
2881 | url, authinfo = u.authinfo() | |
2880 |
openerargs = { |
|
2882 | openerargs = { | |
|
2883 | r'useragent': b'Mercurial debugwireproto', | |||
|
2884 | } | |||
2881 |
|
2885 | |||
2882 | # Turn pipes/sockets into observers so we can log I/O. |
|
2886 | # Turn pipes/sockets into observers so we can log I/O. | |
2883 | if ui.verbose: |
|
2887 | if ui.verbose: | |
2884 |
openerargs |
|
2888 | openerargs.update({ | |
2885 | r'loggingfh': ui, |
|
2889 | r'loggingfh': ui, | |
2886 | r'loggingname': b's', |
|
2890 | r'loggingname': b's', | |
2887 | r'loggingopts': { |
|
2891 | r'loggingopts': { | |
2888 | r'logdata': True, |
|
2892 | r'logdata': True, | |
2889 | r'logdataapis': False, |
|
2893 | r'logdataapis': False, | |
2890 | }, |
|
2894 | }, | |
2891 | } |
|
2895 | }) | |
2892 |
|
2896 | |||
2893 | if ui.debugflag: |
|
2897 | if ui.debugflag: | |
2894 | openerargs[r'loggingopts'][r'logdataapis'] = True |
|
2898 | openerargs[r'loggingopts'][r'logdataapis'] = True | |
@@ -2901,7 +2905,10 b' def debugwireproto(ui, repo, path=None, ' | |||||
2901 |
|
2905 | |||
2902 | opener = urlmod.opener(ui, authinfo, **openerargs) |
|
2906 | opener = urlmod.opener(ui, authinfo, **openerargs) | |
2903 |
|
2907 | |||
2904 |
if opts['peer'] == ' |
|
2908 | if opts['peer'] == 'http2': | |
|
2909 | ui.write(_('creating http peer for wire protocol version 2\n')) | |||
|
2910 | peer = httppeer.httpv2peer(ui, path, opener) | |||
|
2911 | elif opts['peer'] == 'raw': | |||
2905 | ui.write(_('using raw connection to peer\n')) |
|
2912 | ui.write(_('using raw connection to peer\n')) | |
2906 | peer = None |
|
2913 | peer = None | |
2907 | elif opts['peer']: |
|
2914 | elif opts['peer']: | |
@@ -2951,7 +2958,12 b' def debugwireproto(ui, repo, path=None, ' | |||||
2951 | else: |
|
2958 | else: | |
2952 | key, value = fields |
|
2959 | key, value = fields | |
2953 |
|
2960 | |||
2954 | args[key] = stringutil.unescapestr(value) |
|
2961 | if value.startswith('eval:'): | |
|
2962 | value = stringutil.evalpythonliteral(value[5:]) | |||
|
2963 | else: | |||
|
2964 | value = stringutil.unescapestr(value) | |||
|
2965 | ||||
|
2966 | args[key] = value | |||
2955 |
|
2967 | |||
2956 | if batchedcommands is not None: |
|
2968 | if batchedcommands is not None: | |
2957 | batchedcommands.append((command, args)) |
|
2969 | batchedcommands.append((command, args)) |
@@ -16,6 +16,9 b' import struct' | |||||
16 | import tempfile |
|
16 | import tempfile | |
17 |
|
17 | |||
18 | from .i18n import _ |
|
18 | from .i18n import _ | |
|
19 | from .thirdparty import ( | |||
|
20 | cbor, | |||
|
21 | ) | |||
19 | from . import ( |
|
22 | from . import ( | |
20 | bundle2, |
|
23 | bundle2, | |
21 | error, |
|
24 | error, | |
@@ -25,6 +28,8 b' from . import (' | |||||
25 | url as urlmod, |
|
28 | url as urlmod, | |
26 | util, |
|
29 | util, | |
27 | wireproto, |
|
30 | wireproto, | |
|
31 | wireprotoframing, | |||
|
32 | wireprotoserver, | |||
28 | ) |
|
33 | ) | |
29 |
|
34 | |||
30 | httplib = util.httplib |
|
35 | httplib = util.httplib | |
@@ -467,6 +472,95 b' class httppeer(wireproto.wirepeer):' | |||||
467 | def _abort(self, exception): |
|
472 | def _abort(self, exception): | |
468 | raise exception |
|
473 | raise exception | |
469 |
|
474 | |||
|
475 | # TODO implement interface for version 2 peers | |||
|
476 | class httpv2peer(object): | |||
|
477 | def __init__(self, ui, repourl, opener): | |||
|
478 | self.ui = ui | |||
|
479 | ||||
|
480 | if repourl.endswith('/'): | |||
|
481 | repourl = repourl[:-1] | |||
|
482 | ||||
|
483 | self.url = repourl | |||
|
484 | self._opener = opener | |||
|
485 | # This is an its own attribute to facilitate extensions overriding | |||
|
486 | # the default type. | |||
|
487 | self._requestbuilder = urlreq.request | |||
|
488 | ||||
|
489 | def close(self): | |||
|
490 | pass | |||
|
491 | ||||
|
492 | # TODO require to be part of a batched primitive, use futures. | |||
|
493 | def _call(self, name, **args): | |||
|
494 | """Call a wire protocol command with arguments.""" | |||
|
495 | ||||
|
496 | # TODO permissions should come from capabilities results. | |||
|
497 | permission = wireproto.commandsv2[name].permission | |||
|
498 | if permission not in ('push', 'pull'): | |||
|
499 | raise error.ProgrammingError('unknown permission type: %s' % | |||
|
500 | permission) | |||
|
501 | ||||
|
502 | permission = { | |||
|
503 | 'push': 'rw', | |||
|
504 | 'pull': 'ro', | |||
|
505 | }[permission] | |||
|
506 | ||||
|
507 | url = '%s/api/%s/%s/%s' % (self.url, wireprotoserver.HTTPV2, permission, | |||
|
508 | name) | |||
|
509 | ||||
|
510 | # TODO modify user-agent to reflect v2. | |||
|
511 | headers = { | |||
|
512 | r'Accept': wireprotoserver.FRAMINGTYPE, | |||
|
513 | r'Content-Type': wireprotoserver.FRAMINGTYPE, | |||
|
514 | } | |||
|
515 | ||||
|
516 | # TODO this should be part of a generic peer for the frame-based | |||
|
517 | # protocol. | |||
|
518 | stream = wireprotoframing.stream(1) | |||
|
519 | frames = wireprotoframing.createcommandframes(stream, 1, | |||
|
520 | name, args) | |||
|
521 | ||||
|
522 | body = b''.join(map(bytes, frames)) | |||
|
523 | req = self._requestbuilder(pycompat.strurl(url), body, headers) | |||
|
524 | req.add_unredirected_header(r'Content-Length', r'%d' % len(body)) | |||
|
525 | ||||
|
526 | # TODO unify this code with httppeer. | |||
|
527 | try: | |||
|
528 | res = self._opener.open(req) | |||
|
529 | except urlerr.httperror as e: | |||
|
530 | if e.code == 401: | |||
|
531 | raise error.Abort(_('authorization failed')) | |||
|
532 | ||||
|
533 | raise | |||
|
534 | except httplib.HTTPException as e: | |||
|
535 | self.ui.traceback() | |||
|
536 | raise IOError(None, e) | |||
|
537 | ||||
|
538 | # TODO validate response type, wrap response to handle I/O errors. | |||
|
539 | # TODO more robust frame receiver. | |||
|
540 | results = [] | |||
|
541 | ||||
|
542 | while True: | |||
|
543 | frame = wireprotoframing.readframe(res) | |||
|
544 | if frame is None: | |||
|
545 | break | |||
|
546 | ||||
|
547 | self.ui.note(_('received %r\n') % frame) | |||
|
548 | ||||
|
549 | if frame.typeid == wireprotoframing.FRAME_TYPE_BYTES_RESPONSE: | |||
|
550 | if frame.flags & wireprotoframing.FLAG_BYTES_RESPONSE_CBOR: | |||
|
551 | payload = util.bytesio(frame.payload) | |||
|
552 | ||||
|
553 | decoder = cbor.CBORDecoder(payload) | |||
|
554 | while payload.tell() + 1 < len(frame.payload): | |||
|
555 | results.append(decoder.decode()) | |||
|
556 | else: | |||
|
557 | results.append(frame.payload) | |||
|
558 | else: | |||
|
559 | error.ProgrammingError('unhandled frame type: %d' % | |||
|
560 | frame.typeid) | |||
|
561 | ||||
|
562 | return results | |||
|
563 | ||||
470 | def makepeer(ui, path): |
|
564 | def makepeer(ui, path): | |
471 | u = util.url(path) |
|
565 | u = util.url(path) | |
472 | if u.query or u.fragment: |
|
566 | if u.query or u.fragment: |
@@ -180,6 +180,36 b' Request to read-only command works out o' | |||||
180 | s> 0\r\n |
|
180 | s> 0\r\n | |
181 | s> \r\n |
|
181 | s> \r\n | |
182 |
|
182 | |||
|
183 | $ sendhttpv2peer << EOF | |||
|
184 | > command customreadonly | |||
|
185 | > EOF | |||
|
186 | creating http peer for wire protocol version 2 | |||
|
187 | sending customreadonly command | |||
|
188 | s> POST /api/exp-http-v2-0001/ro/customreadonly HTTP/1.1\r\n | |||
|
189 | s> Accept-Encoding: identity\r\n | |||
|
190 | s> accept: application/mercurial-exp-framing-0003\r\n | |||
|
191 | s> content-type: application/mercurial-exp-framing-0003\r\n | |||
|
192 | s> content-length: 29\r\n | |||
|
193 | s> host: $LOCALIP:$HGPORT\r\n (glob) | |||
|
194 | s> user-agent: Mercurial debugwireproto\r\n | |||
|
195 | s> \r\n | |||
|
196 | s> \x15\x00\x00\x01\x00\x01\x01\x11\xa1DnameNcustomreadonly | |||
|
197 | s> makefile('rb', None) | |||
|
198 | s> HTTP/1.1 200 OK\r\n | |||
|
199 | s> Server: testing stub value\r\n | |||
|
200 | s> Date: $HTTP_DATE$\r\n | |||
|
201 | s> Content-Type: application/mercurial-exp-framing-0003\r\n | |||
|
202 | s> Transfer-Encoding: chunked\r\n | |||
|
203 | s> \r\n | |||
|
204 | s> 25\r\n | |||
|
205 | s> \x1d\x00\x00\x01\x00\x02\x01B | |||
|
206 | s> customreadonly bytes response | |||
|
207 | s> \r\n | |||
|
208 | received frame(size=29; request=1; stream=2; streamflags=stream-begin; type=bytes-response; flags=eos) | |||
|
209 | s> 0\r\n | |||
|
210 | s> \r\n | |||
|
211 | response: [b'customreadonly bytes response'] | |||
|
212 | ||||
183 | Request to read-write command fails because server is read-only by default |
|
213 | Request to read-write command fails because server is read-only by default | |
184 |
|
214 | |||
185 | GET to read-write request yields 405 |
|
215 | GET to read-write request yields 405 |
@@ -179,7 +179,7 b' Test listkeys for listing namespaces' | |||||
179 | s> Accept-Encoding: identity\r\n |
|
179 | s> Accept-Encoding: identity\r\n | |
180 | s> accept: application/mercurial-0.1\r\n |
|
180 | s> accept: application/mercurial-0.1\r\n | |
181 | s> host: $LOCALIP:$HGPORT\r\n (glob) |
|
181 | s> host: $LOCALIP:$HGPORT\r\n (glob) | |
182 |
s> user-agent: |
|
182 | s> user-agent: Mercurial debugwireproto\r\n | |
183 | s> \r\n |
|
183 | s> \r\n | |
184 | s> makefile('rb', None) |
|
184 | s> makefile('rb', None) | |
185 | s> HTTP/1.1 200 Script output follows\r\n |
|
185 | s> HTTP/1.1 200 Script output follows\r\n | |
@@ -197,7 +197,7 b' Test listkeys for listing namespaces' | |||||
197 | s> x-hgproto-1: 0.1 0.2 comp=$USUAL_COMPRESSIONS$\r\n |
|
197 | s> x-hgproto-1: 0.1 0.2 comp=$USUAL_COMPRESSIONS$\r\n | |
198 | s> accept: application/mercurial-0.1\r\n |
|
198 | s> accept: application/mercurial-0.1\r\n | |
199 | s> host: $LOCALIP:$HGPORT\r\n (glob) |
|
199 | s> host: $LOCALIP:$HGPORT\r\n (glob) | |
200 |
s> user-agent: |
|
200 | s> user-agent: Mercurial debugwireproto\r\n | |
201 | s> \r\n |
|
201 | s> \r\n | |
202 | s> makefile('rb', None) |
|
202 | s> makefile('rb', None) | |
203 | s> HTTP/1.1 200 Script output follows\r\n |
|
203 | s> HTTP/1.1 200 Script output follows\r\n |
@@ -5,6 +5,10 b' sendhttpraw() {' | |||||
5 | hg --verbose debugwireproto --peer raw http://$LOCALIP:$HGPORT/ |
|
5 | hg --verbose debugwireproto --peer raw http://$LOCALIP:$HGPORT/ | |
6 | } |
|
6 | } | |
7 |
|
7 | |||
|
8 | sendhttpv2peer() { | |||
|
9 | hg --verbose debugwireproto --peer http2 http://$LOCALIP:$HGPORT/ | |||
|
10 | } | |||
|
11 | ||||
8 | cat > dummycommands.py << EOF |
|
12 | cat > dummycommands.py << EOF | |
9 | from mercurial import ( |
|
13 | from mercurial import ( | |
10 | wireprototypes, |
|
14 | wireprototypes, |
General Comments 0
You need to be logged in to leave comments.
Login now