--- 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()