##// END OF EJS Templates
wireproto: crude support for version 2 HTTP peer...
Gregory Szorc -
r37501:61e405fb default
parent child Browse files
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 raw data
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'] == 'raw':
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: mercurial/proto-1.0 (Mercurial *)\r\n (glob)
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: mercurial/proto-1.0 (Mercurial *)\r\n (glob)
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