mercurial/hgweb/protocol.py
changeset 30764 e75463e3179f
parent 30759 3f5f0c98cd18
child 33228 35c233975b78
--- a/mercurial/hgweb/protocol.py	Sat Dec 24 15:22:18 2016 -0700
+++ b/mercurial/hgweb/protocol.py	Sat Dec 24 15:29:32 2016 -0700
@@ -8,6 +8,7 @@
 from __future__ import absolute_import
 
 import cgi
+import struct
 
 from .common import (
     HTTP_OK,
@@ -23,6 +24,7 @@
 urlreq = util.urlreq
 
 HGTYPE = 'application/mercurial-0.1'
+HGTYPE2 = 'application/mercurial-0.2'
 HGERRTYPE = 'application/hg-error'
 
 def decodevaluefromheaders(req, headerprefix):
@@ -83,24 +85,80 @@
         self.ui.ferr, self.ui.fout = self.oldio
         return val
 
-    def compresschunks(self, chunks):
-        # Don't allow untrusted settings because disabling compression or
-        # setting a very high compression level could lead to flooding
-        # the server's network or CPU.
-        opts = {'level': self.ui.configint('server', 'zliblevel', -1)}
-        return util.compengines['zlib'].compressstream(chunks, opts)
-
     def _client(self):
         return 'remote:%s:%s:%s' % (
             self.req.env.get('wsgi.url_scheme') or 'http',
             urlreq.quote(self.req.env.get('REMOTE_HOST', '')),
             urlreq.quote(self.req.env.get('REMOTE_USER', '')))
 
+    def responsetype(self, v1compressible=False):
+        """Determine the appropriate response type and compression settings.
+
+        The ``v1compressible`` argument states whether the response with
+        application/mercurial-0.1 media types should be zlib compressed.
+
+        Returns a tuple of (mediatype, compengine, engineopts).
+        """
+        # For now, if it isn't compressible in the old world, it's never
+        # compressible. We can change this to send uncompressed 0.2 payloads
+        # later.
+        if not v1compressible:
+            return HGTYPE, None, None
+
+        # Determine the response media type and compression engine based
+        # on the request parameters.
+        protocaps = decodevaluefromheaders(self.req, 'X-HgProto').split(' ')
+
+        if '0.2' in protocaps:
+            # Default as defined by wire protocol spec.
+            compformats = ['zlib', 'none']
+            for cap in protocaps:
+                if cap.startswith('comp='):
+                    compformats = cap[5:].split(',')
+                    break
+
+            # Now find an agreed upon compression format.
+            for engine in wireproto.supportedcompengines(self.ui, self,
+                                                         util.SERVERROLE):
+                if engine.wireprotosupport().name in compformats:
+                    opts = {}
+                    level = self.ui.configint('server',
+                                              '%slevel' % engine.name())
+                    if level is not None:
+                        opts['level'] = level
+
+                    return HGTYPE2, engine, opts
+
+            # No mutually supported compression format. Fall back to the
+            # legacy protocol.
+
+        # Don't allow untrusted settings because disabling compression or
+        # setting a very high compression level could lead to flooding
+        # the server's network or CPU.
+        opts = {'level': self.ui.configint('server', 'zliblevel', -1)}
+        return HGTYPE, util.compengines['zlib'], opts
+
 def iscmd(cmd):
     return cmd in wireproto.commands
 
 def call(repo, req, cmd):
     p = webproto(req, repo.ui)
+
+    def genversion2(gen, compress, engine, engineopts):
+        # application/mercurial-0.2 always sends a payload header
+        # identifying the compression engine.
+        name = engine.wireprotosupport().name
+        assert 0 < len(name) < 256
+        yield struct.pack('B', len(name))
+        yield name
+
+        if compress:
+            for chunk in engine.compressstream(gen, opts=engineopts):
+                yield chunk
+        else:
+            for chunk in gen:
+                yield chunk
+
     rsp = wireproto.dispatch(repo, p, cmd)
     if isinstance(rsp, str):
         req.respond(HTTP_OK, HGTYPE, body=rsp)
@@ -111,10 +169,16 @@
         else:
             gen = rsp.gen
 
-        if rsp.v1compressible:
-            gen = p.compresschunks(gen)
+        # This code for compression should not be streamres specific. It
+        # is here because we only compress streamres at the moment.
+        mediatype, engine, engineopts = p.responsetype(rsp.v1compressible)
 
-        req.respond(HTTP_OK, HGTYPE)
+        if mediatype == HGTYPE and rsp.v1compressible:
+            gen = engine.compressstream(gen, engineopts)
+        elif mediatype == HGTYPE2:
+            gen = genversion2(gen, rsp.v1compressible, engine, engineopts)
+
+        req.respond(HTTP_OK, mediatype)
         return gen
     elif isinstance(rsp, wireproto.pushres):
         val = p.restore()