httppeer: extract code for creating a request into own function
authorGregory Szorc <gregory.szorc@gmail.com>
Tue, 10 Apr 2018 10:51:12 -0700
changeset 37549 66d1001e1500
parent 37548 8e7a4435ab6d
child 37550 b5862ee01abe
httppeer: extract code for creating a request into own function 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. Differential Revision: https://phab.mercurial-scm.org/D3234
mercurial/httppeer.py
--- a/mercurial/httppeer.py	Tue Apr 10 10:27:49 2018 -0700
+++ b/mercurial/httppeer.py	Tue Apr 10 10:51:12 2018 -0700
@@ -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 +341,11 @@
 
     def _callstream(self, cmd, _compressible=False, **args):
         args = pycompat.byteskwargs(args)
-        if cmd == 'pushkey':
-            args['data'] = ''
-        data = args.pop('data', None)
-        headers = args.pop('headers', {})
-
-        self.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 = self._caps is not None and 'httppostargs' in self._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 = self.capable('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" % (self._url, 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 self._caps is not None:
-            mt = self.capable('httpmediatype')
-            if mt:
-                protoparams.add('0.1')
-                mediatypes = set(mt.split(','))
-
-        if '0.2tx' in mediatypes:
-            protoparams.add('0.2')
+        req, cu, qs = makev1commandrequest(self.ui, self._requestbuilder,
+                                           self._caps, self.capable,
+                                           self._url, cmd, args)
 
-        if '0.2tx' in mediatypes and self.capable('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 = self._requestbuilder(pycompat.strurl(cu), data, headers)
-
-        if data is not None:
-            self.ui.debug("sending %d bytes\n" % size)
-            req.add_unredirected_header(r'Content-Length', r'%d' % size)
         try:
             resp = sendrequest(self.ui, self._urlopener, req)
         except urlerr.httperror as inst: