D3234: httppeer: extract code for creating a request into own function
This revision was automatically updated to reflect the committed changes. Closed by commit rHG66d1001e1500: httppeer: extract code for creating a request into own function (authored by indygreg, committed by ). REPOSITORY rHG Mercurial CHANGES SINCE LAST UPDATE https://phab.mercurial-scm.org/D3234?vs=7952&id=7995 REVISION DETAIL https://phab.mercurial-scm.org/D3234 AFFECTED FILES mercurial/httppeer.py CHANGE DETAILS diff --git a/mercurial/httppeer.py b/mercurial/httppeer.py --- a/mercurial/httppeer.py +++ b/mercurial/httppeer.py @@ -138,6 +138,119 @@ f.seek(0) self._index = 0 +def makev1commandrequest(ui, requestbuilder, caps, capablefn, + repobaseurl, cmd, args): +"""Make an HTTP request to run a command for a version 1 client. + +``caps`` is a set of known server capabilities. The value may be +None if capabilities are not yet known. + +``capablefn`` is a function to evaluate a capability. + +``cmd``, ``args``, and ``data`` define the command, its arguments, and +raw data to pass to it. +""" +if cmd == 'pushkey': +args['data'] = '' +data = args.pop('data', None) +headers = args.pop('headers', {}) + +ui.debug("sending %s command\n" % cmd) +q = [('cmd', cmd)] +headersize = 0 +varyheaders = [] +# Important: don't use self.capable() here or else you end up +# with infinite recursion when trying to look up capabilities +# for the first time. +postargsok = caps is not None and 'httppostargs' in caps + +# Send arguments via POST. +if postargsok and args: +strargs = urlreq.urlencode(sorted(args.items())) +if not data: +data = strargs +else: +if isinstance(data, bytes): +i = io.BytesIO(data) +i.length = len(data) +data = i +argsio = io.BytesIO(strargs) +argsio.length = len(strargs) +data = _multifile(argsio, data) +headers[r'X-HgArgs-Post'] = len(strargs) +elif args: +# Calling self.capable() can infinite loop if we are calling +# "capabilities". But that command should never accept wire +# protocol arguments. So this should never happen. +assert cmd != 'capabilities' +httpheader = capablefn('httpheader') +if httpheader: +headersize = int(httpheader.split(',', 1)[0]) + +# Send arguments via HTTP headers. +if headersize > 0: +# The headers can typically carry more data than the URL. +encargs = urlreq.urlencode(sorted(args.items())) +for header, value in encodevalueinheaders(encargs, 'X-HgArg', + headersize): +headers[header] = value +varyheaders.append(header) +# Send arguments via query string (Mercurial <1.9). +else: +q += sorted(args.items()) + +qs = '?%s' % urlreq.urlencode(q) +cu = "%s%s" % (repobaseurl, qs) +size = 0 +if util.safehasattr(data, 'length'): +size = data.length +elif data is not None: +size = len(data) +if data is not None and r'Content-Type' not in headers: +headers[r'Content-Type'] = r'application/mercurial-0.1' + +# Tell the server we accept application/mercurial-0.2 and multiple +# compression formats if the server is capable of emitting those +# payloads. +protoparams = {'partial-pull'} + +mediatypes = set() +if caps is not None: +mt = capablefn('httpmediatype') +if mt: +protoparams.add('0.1') +mediatypes = set(mt.split(',')) + +if '0.2tx' in mediatypes: +protoparams.add('0.2') + +if '0.2tx' in mediatypes and capablefn('compression'): +# We /could/ compare supported compression formats and prune +# non-mutually supported or error if nothing is mutually supported. +# For now, send the full list to the server and have it error. +comps = [e.wireprotosupport().name for e in + util.compengines.supportedwireengines(util.CLIENTROLE)] +protoparams.add('comp=%s' % ','.join(comps)) + +if protoparams: +protoheaders = encodevalueinheaders(' '.join(sorted(protoparams)), +'X-HgProto', +headersize or 1024) +for header, value in protoheaders: +headers[header] = value +varyheaders.append(header) + +if varyheaders: +headers[r'Vary'] = r','.join(varyheaders) + +req = requestbuilder(pycompat.strurl(cu), data, headers) + +if data is not None: +ui.debug("sending %d bytes\n" % size) +req.add_unredirected_header(r'Content-Length', r'%d' % size) + +return req, cu, qs + def sendrequest(ui, opener, req): """Send a prepared HTTP request. @@ -228,104 +
D3234: httppeer: extract code for creating a request into own function
indygreg created this revision. Herald added a subscriber: mercurial-devel. Herald added a reviewer: hg-reviewers. REVISION SUMMARY Some of this feels awkward, such as having to pass in a function to evaluate a capability. And this code is generally pretty difficult to read. I didn't want to perform too much refactoring as part of the code move since it would make review more difficult. REPOSITORY rHG Mercurial REVISION DETAIL https://phab.mercurial-scm.org/D3234 AFFECTED FILES mercurial/httppeer.py CHANGE DETAILS diff --git a/mercurial/httppeer.py b/mercurial/httppeer.py --- a/mercurial/httppeer.py +++ b/mercurial/httppeer.py @@ -138,6 +138,119 @@ f.seek(0) self._index = 0 +def makev1commandrequest(ui, requestbuilder, caps, capablefn, + repobaseurl, cmd, args): +"""Make an HTTP request to run a command for a version 1 client. + +``caps`` is a set of known server capabilities. The value may be +None if capabilities are not yet known. + +``capablefn`` is a function to evaluate a capability. + +``cmd``, ``args``, and ``data`` define the command, its arguments, and +raw data to pass to it. +""" +if cmd == 'pushkey': +args['data'] = '' +data = args.pop('data', None) +headers = args.pop('headers', {}) + +ui.debug("sending %s command\n" % cmd) +q = [('cmd', cmd)] +headersize = 0 +varyheaders = [] +# Important: don't use self.capable() here or else you end up +# with infinite recursion when trying to look up capabilities +# for the first time. +postargsok = caps is not None and 'httppostargs' in caps + +# Send arguments via POST. +if postargsok and args: +strargs = urlreq.urlencode(sorted(args.items())) +if not data: +data = strargs +else: +if isinstance(data, bytes): +i = io.BytesIO(data) +i.length = len(data) +data = i +argsio = io.BytesIO(strargs) +argsio.length = len(strargs) +data = _multifile(argsio, data) +headers[r'X-HgArgs-Post'] = len(strargs) +elif args: +# Calling self.capable() can infinite loop if we are calling +# "capabilities". But that command should never accept wire +# protocol arguments. So this should never happen. +assert cmd != 'capabilities' +httpheader = capablefn('httpheader') +if httpheader: +headersize = int(httpheader.split(',', 1)[0]) + +# Send arguments via HTTP headers. +if headersize > 0: +# The headers can typically carry more data than the URL. +encargs = urlreq.urlencode(sorted(args.items())) +for header, value in encodevalueinheaders(encargs, 'X-HgArg', + headersize): +headers[header] = value +varyheaders.append(header) +# Send arguments via query string (Mercurial <1.9). +else: +q += sorted(args.items()) + +qs = '?%s' % urlreq.urlencode(q) +cu = "%s%s" % (repobaseurl, qs) +size = 0 +if util.safehasattr(data, 'length'): +size = data.length +elif data is not None: +size = len(data) +if data is not None and r'Content-Type' not in headers: +headers[r'Content-Type'] = r'application/mercurial-0.1' + +# Tell the server we accept application/mercurial-0.2 and multiple +# compression formats if the server is capable of emitting those +# payloads. +protoparams = {'partial-pull'} + +mediatypes = set() +if caps is not None: +mt = capablefn('httpmediatype') +if mt: +protoparams.add('0.1') +mediatypes = set(mt.split(',')) + +if '0.2tx' in mediatypes: +protoparams.add('0.2') + +if '0.2tx' in mediatypes and capablefn('compression'): +# We /could/ compare supported compression formats and prune +# non-mutually supported or error if nothing is mutually supported. +# For now, send the full list to the server and have it error. +comps = [e.wireprotosupport().name for e in + util.compengines.supportedwireengines(util.CLIENTROLE)] +protoparams.add('comp=%s' % ','.join(comps)) + +if protoparams: +protoheaders = encodevalueinheaders(' '.join(sorted(protoparams)), +'X-HgProto', +headersize or 1024) +for header, value in protoheaders: +headers[header] = value +varyheaders.append(header) + +if varyheaders: +headers[r'Vary'] = r','.join(varyheaders) + +req = requestbuilder(pycompat.strurl(cu), data, headers) + +if data is not None: +ui.debug("sending %d bytes\n" % size) +req.add_unredirected_header(r'Content-Length', r'%d' % size) + +retu