diff -r fa9faf58959d -r 61e405fb6372 mercurial/httppeer.py --- a/mercurial/httppeer.py Mon Mar 26 15:34:52 2018 -0700 +++ b/mercurial/httppeer.py Wed Mar 28 15:09:34 2018 -0700 @@ -16,6 +16,9 @@ import tempfile from .i18n import _ +from .thirdparty import ( + cbor, +) from . import ( bundle2, error, @@ -25,6 +28,8 @@ url as urlmod, util, wireproto, + wireprotoframing, + wireprotoserver, ) httplib = util.httplib @@ -467,6 +472,95 @@ def _abort(self, exception): raise exception +# TODO implement interface for version 2 peers +class httpv2peer(object): + def __init__(self, ui, repourl, opener): + self.ui = ui + + if repourl.endswith('/'): + repourl = repourl[:-1] + + self.url = repourl + self._opener = opener + # This is an its own attribute to facilitate extensions overriding + # the default type. + self._requestbuilder = urlreq.request + + def close(self): + pass + + # TODO require to be part of a batched primitive, use futures. + def _call(self, name, **args): + """Call a wire protocol command with arguments.""" + + # TODO permissions should come from capabilities results. + permission = wireproto.commandsv2[name].permission + if permission not in ('push', 'pull'): + raise error.ProgrammingError('unknown permission type: %s' % + permission) + + permission = { + 'push': 'rw', + 'pull': 'ro', + }[permission] + + url = '%s/api/%s/%s/%s' % (self.url, wireprotoserver.HTTPV2, permission, + name) + + # TODO modify user-agent to reflect v2. + headers = { + r'Accept': wireprotoserver.FRAMINGTYPE, + r'Content-Type': wireprotoserver.FRAMINGTYPE, + } + + # TODO this should be part of a generic peer for the frame-based + # protocol. + stream = wireprotoframing.stream(1) + frames = wireprotoframing.createcommandframes(stream, 1, + name, args) + + body = b''.join(map(bytes, frames)) + req = self._requestbuilder(pycompat.strurl(url), body, headers) + req.add_unredirected_header(r'Content-Length', r'%d' % len(body)) + + # TODO unify this code with httppeer. + try: + res = self._opener.open(req) + except urlerr.httperror as e: + if e.code == 401: + raise error.Abort(_('authorization failed')) + + raise + except httplib.HTTPException as e: + self.ui.traceback() + raise IOError(None, e) + + # TODO validate response type, wrap response to handle I/O errors. + # TODO more robust frame receiver. + results = [] + + while True: + frame = wireprotoframing.readframe(res) + if frame is None: + break + + self.ui.note(_('received %r\n') % frame) + + if frame.typeid == wireprotoframing.FRAME_TYPE_BYTES_RESPONSE: + if frame.flags & wireprotoframing.FLAG_BYTES_RESPONSE_CBOR: + payload = util.bytesio(frame.payload) + + decoder = cbor.CBORDecoder(payload) + while payload.tell() + 1 < len(frame.payload): + results.append(decoder.decode()) + else: + results.append(frame.payload) + else: + error.ProgrammingError('unhandled frame type: %d' % + frame.typeid) + + return results + def makepeer(ui, path): u = util.url(path) if u.query or u.fragment: