comparison mercurial/wireprotoserver.py @ 37393:afcfdf53e4b5

wireproto: provide accessors for client capabilities For HTTP, this refactors the existing logic, including the parsing of the compression engine capability. For SSH, this adds a ssh-only capability "protocaps" and a command for informing the server on what the client supports. Since SSH is stateful, keep track of the capabilities in the server instance. Differential Revision: https://phab.mercurial-scm.org/D1944
author Joerg Sonnenberger <joerg@bec.de>
date Sat, 24 Mar 2018 17:57:22 +0100
parents 78103e4138b1
children 2d965bfeb8f6
comparison
equal deleted inserted replaced
37392:a4f02a17420d 37393:afcfdf53e4b5
65 class httpv1protocolhandler(object): 65 class httpv1protocolhandler(object):
66 def __init__(self, req, ui, checkperm): 66 def __init__(self, req, ui, checkperm):
67 self._req = req 67 self._req = req
68 self._ui = ui 68 self._ui = ui
69 self._checkperm = checkperm 69 self._checkperm = checkperm
70 self._protocaps = None
70 71
71 @property 72 @property
72 def name(self): 73 def name(self):
73 return 'http-v1' 74 return 'http-v1'
74 75
97 98
98 argvalue = decodevaluefromheaders(self._req, b'X-HgArg') 99 argvalue = decodevaluefromheaders(self._req, b'X-HgArg')
99 args.update(urlreq.parseqs(argvalue, keep_blank_values=True)) 100 args.update(urlreq.parseqs(argvalue, keep_blank_values=True))
100 return args 101 return args
101 102
103 def getprotocaps(self):
104 if self._protocaps is None:
105 value = decodevaluefromheaders(self._req, r'X-HgProto')
106 self._protocaps = set(value.split(' '))
107 return self._protocaps
108
102 def forwardpayload(self, fp): 109 def forwardpayload(self, fp):
103 # Existing clients *always* send Content-Length. 110 # Existing clients *always* send Content-Length.
104 length = int(self._req.headers[b'Content-Length']) 111 length = int(self._req.headers[b'Content-Length'])
105 112
106 # If httppostargs is used, we need to read Content-Length 113 # If httppostargs is used, we need to read Content-Length
597 else: 604 else:
598 data[k] = self._args[k] 605 data[k] = self._args[k]
599 606
600 return [data[k] for k in args.split()] 607 return [data[k] for k in args.split()]
601 608
609 def getprotocaps(self):
610 # Protocol capabilities are currently not implemented for HTTP V2.
611 return set()
612
602 def forwardpayload(self, fp): 613 def forwardpayload(self, fp):
603 raise NotImplementedError 614 raise NotImplementedError
604 615
605 @contextlib.contextmanager 616 @contextlib.contextmanager
606 def mayberedirectstdio(self): 617 def mayberedirectstdio(self):
613 return caps 624 return caps
614 625
615 def checkperm(self, perm): 626 def checkperm(self, perm):
616 raise NotImplementedError 627 raise NotImplementedError
617 628
618 def _httpresponsetype(ui, req, prefer_uncompressed): 629 def _httpresponsetype(ui, proto, prefer_uncompressed):
619 """Determine the appropriate response type and compression settings. 630 """Determine the appropriate response type and compression settings.
620 631
621 Returns a tuple of (mediatype, compengine, engineopts). 632 Returns a tuple of (mediatype, compengine, engineopts).
622 """ 633 """
623 # Determine the response media type and compression engine based 634 # Determine the response media type and compression engine based
624 # on the request parameters. 635 # on the request parameters.
625 protocaps = decodevaluefromheaders(req, 'X-HgProto').split(' ') 636
626 637 if '0.2' in proto.getprotocaps():
627 if '0.2' in protocaps:
628 # All clients are expected to support uncompressed data. 638 # All clients are expected to support uncompressed data.
629 if prefer_uncompressed: 639 if prefer_uncompressed:
630 return HGTYPE2, util._noopengine(), {} 640 return HGTYPE2, util._noopengine(), {}
631 641
632 # Default as defined by wire protocol spec.
633 compformats = ['zlib', 'none']
634 for cap in protocaps:
635 if cap.startswith('comp='):
636 compformats = cap[5:].split(',')
637 break
638
639 # Now find an agreed upon compression format. 642 # Now find an agreed upon compression format.
643 compformats = wireproto.clientcompressionsupport(proto)
640 for engine in wireproto.supportedcompengines(ui, util.SERVERROLE): 644 for engine in wireproto.supportedcompengines(ui, util.SERVERROLE):
641 if engine.wireprotosupport().name in compformats: 645 if engine.wireprotosupport().name in compformats:
642 opts = {} 646 opts = {}
643 level = ui.configint('server', '%slevel' % engine.name()) 647 level = ui.configint('server', '%slevel' % engine.name())
644 if level is not None: 648 if level is not None:
703 gen = rsp.gen 707 gen = rsp.gen
704 708
705 # This code for compression should not be streamres specific. It 709 # This code for compression should not be streamres specific. It
706 # is here because we only compress streamres at the moment. 710 # is here because we only compress streamres at the moment.
707 mediatype, engine, engineopts = _httpresponsetype( 711 mediatype, engine, engineopts = _httpresponsetype(
708 repo.ui, req, rsp.prefer_uncompressed) 712 repo.ui, proto, rsp.prefer_uncompressed)
709 gen = engine.compressstream(gen, engineopts) 713 gen = engine.compressstream(gen, engineopts)
710 714
711 if mediatype == HGTYPE2: 715 if mediatype == HGTYPE2:
712 gen = genversion2(gen, engine, engineopts) 716 gen = genversion2(gen, engine, engineopts)
713 717
747 """Handler for requests services via version 1 of SSH protocol.""" 751 """Handler for requests services via version 1 of SSH protocol."""
748 def __init__(self, ui, fin, fout): 752 def __init__(self, ui, fin, fout):
749 self._ui = ui 753 self._ui = ui
750 self._fin = fin 754 self._fin = fin
751 self._fout = fout 755 self._fout = fout
756 self._protocaps = set()
752 757
753 @property 758 @property
754 def name(self): 759 def name(self):
755 return wireprototypes.SSHV1 760 return wireprototypes.SSHV1
756 761
773 else: 778 else:
774 val = self._fin.read(int(l)) 779 val = self._fin.read(int(l))
775 data[arg] = val 780 data[arg] = val
776 return [data[k] for k in keys] 781 return [data[k] for k in keys]
777 782
783 def getprotocaps(self):
784 return self._protocaps
785
778 def forwardpayload(self, fpout): 786 def forwardpayload(self, fpout):
779 # We initially send an empty response. This tells the client it is 787 # We initially send an empty response. This tells the client it is
780 # OK to start sending data. If a client sees any other response, it 788 # OK to start sending data. If a client sees any other response, it
781 # interprets it as an error. 789 # interprets it as an error.
782 _sshv1respondbytes(self._fout, b'') 790 _sshv1respondbytes(self._fout, b'')
798 def client(self): 806 def client(self):
799 client = encoding.environ.get('SSH_CLIENT', '').split(' ', 1)[0] 807 client = encoding.environ.get('SSH_CLIENT', '').split(' ', 1)[0]
800 return 'remote:ssh:' + client 808 return 'remote:ssh:' + client
801 809
802 def addcapabilities(self, repo, caps): 810 def addcapabilities(self, repo, caps):
811 if self.name == wireprototypes.SSHV1:
812 caps.append(b'protocaps')
803 caps.append(b'batch') 813 caps.append(b'batch')
804 return caps 814 return caps
805 815
806 def checkperm(self, perm): 816 def checkperm(self, perm):
807 pass 817 pass