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', {