Mercurial > public > mercurial-scm > hg
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 |