Mercurial > public > mercurial-scm > hg
comparison mercurial/wireprotoframing.py @ 40025:b099e6032f38
wireprotov2: server support for sending content redirects
A "content redirect" can be sent in place of inline response content.
In terms of code, we model a content redirect as a special type of
response object holding the attributes describing that redirect.
Sending a content redirect thus becomes as simple as the object
emission layer sending an instance of that type. A cacher using
externally-addressable content storage could replace the outgoing
object stream with an object advertising its location.
The bulk of the code in this commit is teaching the output layer
which handles the object stream to recognize alternate location
objects. The rules are that if an alternate location object is
present, it must be the first and only object in the object stream.
Otherwise the server emits an error.
Differential Revision: https://phab.mercurial-scm.org/D4777
author | Gregory Szorc <gregory.szorc@gmail.com> |
---|---|
date | Wed, 26 Sep 2018 18:07:55 -0700 |
parents | 86b22a4cfab1 |
children | e2fe1074024c |
comparison
equal
deleted
inserted
replaced
40024:86b22a4cfab1 | 40025:b099e6032f38 |
---|---|
19 attr, | 19 attr, |
20 ) | 20 ) |
21 from . import ( | 21 from . import ( |
22 encoding, | 22 encoding, |
23 error, | 23 error, |
24 pycompat, | |
24 util, | 25 util, |
25 wireprototypes, | 26 wireprototypes, |
26 ) | 27 ) |
27 from .utils import ( | 28 from .utils import ( |
28 cborutil, | 29 cborutil, |
426 """Create an empty payload frame representing command end-of-stream.""" | 427 """Create an empty payload frame representing command end-of-stream.""" |
427 return stream.makeframe(requestid=requestid, | 428 return stream.makeframe(requestid=requestid, |
428 typeid=FRAME_TYPE_COMMAND_RESPONSE, | 429 typeid=FRAME_TYPE_COMMAND_RESPONSE, |
429 flags=FLAG_COMMAND_RESPONSE_EOS, | 430 flags=FLAG_COMMAND_RESPONSE_EOS, |
430 payload=b'') | 431 payload=b'') |
432 | |
433 def createalternatelocationresponseframe(stream, requestid, location): | |
434 data = { | |
435 b'status': b'redirect', | |
436 b'location': { | |
437 b'url': location.url, | |
438 b'mediatype': location.mediatype, | |
439 } | |
440 } | |
441 | |
442 for a in (r'size', r'fullhashes', r'fullhashseed', r'serverdercerts', | |
443 r'servercadercerts'): | |
444 value = getattr(location, a) | |
445 if value is not None: | |
446 data[b'location'][pycompat.bytestr(a)] = value | |
447 | |
448 return stream.makeframe(requestid=requestid, | |
449 typeid=FRAME_TYPE_COMMAND_RESPONSE, | |
450 flags=FLAG_COMMAND_RESPONSE_CONTINUATION, | |
451 payload=b''.join(cborutil.streamencode(data))) | |
431 | 452 |
432 def createcommanderrorresponse(stream, requestid, message, args=None): | 453 def createcommanderrorresponse(stream, requestid, message, args=None): |
433 # TODO should this be using a list of {'msg': ..., 'args': {}} so atom | 454 # TODO should this be using a list of {'msg': ..., 'args': {}} so atom |
434 # formatting works consistently? | 455 # formatting works consistently? |
435 m = { | 456 m = { |
811 # In all cases, when the function finishes, the request is fully | 832 # In all cases, when the function finishes, the request is fully |
812 # handled and no new frames for it should be seen. | 833 # handled and no new frames for it should be seen. |
813 | 834 |
814 def sendframes(): | 835 def sendframes(): |
815 emitted = False | 836 emitted = False |
837 alternatelocationsent = False | |
816 emitter = bufferingcommandresponseemitter(stream, requestid) | 838 emitter = bufferingcommandresponseemitter(stream, requestid) |
817 while True: | 839 while True: |
818 try: | 840 try: |
819 o = next(objs) | 841 o = next(objs) |
820 except StopIteration: | 842 except StopIteration: |
839 yield frame | 861 yield frame |
840 | 862 |
841 break | 863 break |
842 | 864 |
843 try: | 865 try: |
866 # Alternate location responses can only be the first and | |
867 # only object in the output stream. | |
868 if isinstance(o, wireprototypes.alternatelocationresponse): | |
869 if emitted: | |
870 raise error.ProgrammingError( | |
871 'alternatelocationresponse seen after initial ' | |
872 'output object') | |
873 | |
874 yield createalternatelocationresponseframe( | |
875 stream, requestid, o) | |
876 | |
877 alternatelocationsent = True | |
878 emitted = True | |
879 continue | |
880 | |
881 if alternatelocationsent: | |
882 raise error.ProgrammingError( | |
883 'object follows alternatelocationresponse') | |
884 | |
844 if not emitted: | 885 if not emitted: |
845 yield createcommandresponseokframe(stream, requestid) | 886 yield createcommandresponseokframe(stream, requestid) |
846 emitted = True | 887 emitted = True |
847 | 888 |
848 # Objects emitted by command functions can be serializable | 889 # Objects emitted by command functions can be serializable |
975 | 1016 |
976 return 'runcommand', { | 1017 return 'runcommand', { |
977 'requestid': requestid, | 1018 'requestid': requestid, |
978 'command': request[b'name'], | 1019 'command': request[b'name'], |
979 'args': request[b'args'], | 1020 'args': request[b'args'], |
1021 'redirect': request.get(b'redirect'), | |
980 'data': entry['data'].getvalue() if entry['data'] else None, | 1022 'data': entry['data'].getvalue() if entry['data'] else None, |
981 } | 1023 } |
982 | 1024 |
983 def _makewantframeresult(self): | 1025 def _makewantframeresult(self): |
984 return 'wantframe', { | 1026 return 'wantframe', { |