##// 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 2632 ``--peer`` can be used to bypass the handshake protocol and construct a
2633 2633 peer instance using the specified class type. Valid values are ``raw``,
2634 ``ssh1``, and ``ssh2``. ``raw`` instances only allow sending raw data
2635 payloads and don't support higher-level command actions.
2634 ``http2``, ``ssh1``, and ``ssh2``. ``raw`` instances only allow sending
2635 raw data payloads and don't support higher-level command actions.
2636 2636
2637 2637 ``--noreadstderr`` can be used to disable automatic reading from stderr
2638 2638 of the peer (for SSH connections only). Disabling automatic reading of
@@ -2678,8 +2678,10 b' def debugwireproto(ui, repo, path=None, '
2678 2678 command listkeys
2679 2679 namespace bookmarks
2680 2680
2681 Values are interpreted as Python b'' literals. This allows encoding
2682 special byte sequences via backslash escaping.
2681 If the value begins with ``eval:``, it will be interpreted as a Python
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 2686 The following arguments have special meaning:
2685 2687
@@ -2803,7 +2805,7 b' def debugwireproto(ui, repo, path=None, '
2803 2805 if opts['localssh'] and not repo:
2804 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 2809 raise error.Abort(_('invalid value for --peer'),
2808 2810 hint=_('valid values are "raw", "ssh1", and "ssh2"'))
2809 2811
@@ -2877,18 +2879,20 b' def debugwireproto(ui, repo, path=None, '
2877 2879 raise error.Abort(_('only http:// paths are currently supported'))
2878 2880
2879 2881 url, authinfo = u.authinfo()
2880 openerargs = {}
2882 openerargs = {
2883 r'useragent': b'Mercurial debugwireproto',
2884 }
2881 2885
2882 2886 # Turn pipes/sockets into observers so we can log I/O.
2883 2887 if ui.verbose:
2884 openerargs = {
2888 openerargs.update({
2885 2889 r'loggingfh': ui,
2886 2890 r'loggingname': b's',
2887 2891 r'loggingopts': {
2888 2892 r'logdata': True,
2889 2893 r'logdataapis': False,
2890 2894 },
2891 }
2895 })
2892 2896
2893 2897 if ui.debugflag:
2894 2898 openerargs[r'loggingopts'][r'logdataapis'] = True
@@ -2901,7 +2905,10 b' def debugwireproto(ui, repo, path=None, '
2901 2905
2902 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 2912 ui.write(_('using raw connection to peer\n'))
2906 2913 peer = None
2907 2914 elif opts['peer']:
@@ -2951,7 +2958,12 b' def debugwireproto(ui, repo, path=None, '
2951 2958 else:
2952 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 2968 if batchedcommands is not None:
2957 2969 batchedcommands.append((command, args))
@@ -16,6 +16,9 b' import struct'
16 16 import tempfile
17 17
18 18 from .i18n import _
19 from .thirdparty import (
20 cbor,
21 )
19 22 from . import (
20 23 bundle2,
21 24 error,
@@ -25,6 +28,8 b' from . import ('
25 28 url as urlmod,
26 29 util,
27 30 wireproto,
31 wireprotoframing,
32 wireprotoserver,
28 33 )
29 34
30 35 httplib = util.httplib
@@ -467,6 +472,95 b' class httppeer(wireproto.wirepeer):'
467 472 def _abort(self, exception):
468 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 564 def makepeer(ui, path):
471 565 u = util.url(path)
472 566 if u.query or u.fragment:
@@ -180,6 +180,36 b' Request to read-only command works out o'
180 180 s> 0\r\n
181 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 213 Request to read-write command fails because server is read-only by default
184 214
185 215 GET to read-write request yields 405
@@ -179,7 +179,7 b' Test listkeys for listing namespaces'
179 179 s> Accept-Encoding: identity\r\n
180 180 s> accept: application/mercurial-0.1\r\n
181 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 183 s> \r\n
184 184 s> makefile('rb', None)
185 185 s> HTTP/1.1 200 Script output follows\r\n
@@ -197,7 +197,7 b' Test listkeys for listing namespaces'
197 197 s> x-hgproto-1: 0.1 0.2 comp=$USUAL_COMPRESSIONS$\r\n
198 198 s> accept: application/mercurial-0.1\r\n
199 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 201 s> \r\n
202 202 s> makefile('rb', None)
203 203 s> HTTP/1.1 200 Script output follows\r\n
@@ -5,6 +5,10 b' sendhttpraw() {'
5 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 12 cat > dummycommands.py << EOF
9 13 from mercurial import (
10 14 wireprototypes,
General Comments 0
You need to be logged in to leave comments. Login now