comparison mercurial/httppeer.py @ 37483:61e405fb6372

wireproto: crude support for version 2 HTTP peer As part of implementing the server-side bits of the wire protocol command handlers for version 2, we want a way to easily test those commands. Currently, we use the "httprequest" action of `hg debugwireproto`. But this requires explicitly specifying the HTTP request headers, low-level frame details, and the data structure to encode with CBOR. That's a lot of boilerplate and a lot of it can change as the wire protocol evolves. `hg debugwireproto` has a mechanism to issue commands via the peer interface. That is *much* easier to use and we prefer to test with that going forward. This commit implements enough parts of the peer API to send basic requests via the HTTP version 2 transport. The peer code is super hacky. Again, the goal is to facilitate server testing, not robustly implement a client. The client code will receive love at a later time. Differential Revision: https://phab.mercurial-scm.org/D3177
author Gregory Szorc <gregory.szorc@gmail.com>
date Wed, 28 Mar 2018 15:09:34 -0700
parents 3e1688711efd
children aacfca6f9767
comparison
equal deleted inserted replaced
37482:fa9faf58959d 37483:61e405fb6372
14 import socket 14 import socket
15 import struct 15 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,
22 httpconnection, 25 httpconnection,
23 pycompat, 26 pycompat,
24 statichttprepo, 27 statichttprepo,
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
31 urlerr = util.urlerr 36 urlerr = util.urlerr
32 urlreq = util.urlreq 37 urlreq = util.urlreq
465 return self._callstream(cmd, _compressible=True, **args) 470 return self._callstream(cmd, _compressible=True, **args)
466 471
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:
473 raise error.Abort(_('unsupported URL component: "%s"') % 567 raise error.Abort(_('unsupported URL component: "%s"') %
474 (u.query or u.fragment)) 568 (u.query or u.fragment))