comparison mercurial/wireprotoserver.py @ 36066:2ad145fbde54

wireprotoserver: add context manager mechanism for redirecting stdio Today, proto.redirect() sets up redirecting stdio and proto.restore() undoes that. The API is a bit wonky because restore() is only implemented on the HTTP protocol. Furthermore, not all calls to redirect() are obviously paired with calls to restore(). For example, the call to restore() for "unbundle" requests is handled by the response handler for the HTTP protocol. This commit introduces a new method on the protocol handler interface to maybe capture stdio. It emits a file object or None depending on whether stdio capture is used by the transport. To prove it works, the "pushkey" wire protocol command has been updated to use the new API. I'm not convinced this is the best mechanism to capture stdio. I may need to come up with something better once the new wire protocol emerges into existence. But it is strictly better than before because it removes variance in the wire protocol handler interface. It therefore gets us closer to a unified interface between the SSH and HTTP transports. Differential Revision: https://phab.mercurial-scm.org/D2081
author Gregory Szorc <gregory.szorc@gmail.com>
date Wed, 07 Feb 2018 20:17:47 -0800
parents bf676267f64f
children caca3ac2ac04
comparison
equal deleted inserted replaced
36065:bf676267f64f 36066:2ad145fbde54
6 6
7 from __future__ import absolute_import 7 from __future__ import absolute_import
8 8
9 import abc 9 import abc
10 import cgi 10 import cgi
11 import contextlib
11 import struct 12 import struct
12 import sys 13 import sys
13 14
14 from .i18n import _ 15 from .i18n import _
15 from . import ( 16 from . import (
69 The file is in the form:: 70 The file is in the form::
70 71
71 (<chunk-size>\n<chunk>)+0\n 72 (<chunk-size>\n<chunk>)+0\n
72 73
73 chunk size is the ascii version of the int. 74 chunk size is the ascii version of the int.
75 """
76
77 @abc.abstractmethod
78 def mayberedirectstdio(self):
79 """Context manager to possibly redirect stdio.
80
81 The context manager yields a file-object like object that receives
82 stdout and stderr output when the context manager is active. Or it
83 yields ``None`` if no I/O redirection occurs.
84
85 The intent of this context manager is to capture stdio output
86 so it may be sent in the response. Some transports support streaming
87 stdio to the client in real time. For these transports, stdio output
88 won't be captured.
74 """ 89 """
75 90
76 @abc.abstractmethod 91 @abc.abstractmethod
77 def redirect(self): 92 def redirect(self):
78 """may setup interception for stdout and stderr 93 """may setup interception for stdout and stderr
148 # If httppostargs is used, we need to read Content-Length 163 # If httppostargs is used, we need to read Content-Length
149 # minus the amount that was consumed by args. 164 # minus the amount that was consumed by args.
150 length -= int(self._req.env.get(r'HTTP_X_HGARGS_POST', 0)) 165 length -= int(self._req.env.get(r'HTTP_X_HGARGS_POST', 0))
151 for s in util.filechunkiter(self._req, limit=length): 166 for s in util.filechunkiter(self._req, limit=length):
152 fp.write(s) 167 fp.write(s)
168
169 @contextlib.contextmanager
170 def mayberedirectstdio(self):
171 oldout = self._ui.fout
172 olderr = self._ui.ferr
173
174 out = util.stringio()
175
176 try:
177 self._ui.fout = out
178 self._ui.ferr = out
179 yield out
180 finally:
181 self._ui.fout = oldout
182 self._ui.ferr = olderr
153 183
154 def redirect(self): 184 def redirect(self):
155 self._oldio = self._ui.fout, self._ui.ferr 185 self._oldio = self._ui.fout, self._ui.ferr
156 self._ui.ferr = self._ui.fout = stringio() 186 self._ui.ferr = self._ui.fout = stringio()
157 187
391 count = int(self._fin.readline()) 421 count = int(self._fin.readline())
392 while count: 422 while count:
393 fpout.write(self._fin.read(count)) 423 fpout.write(self._fin.read(count))
394 count = int(self._fin.readline()) 424 count = int(self._fin.readline())
395 425
426 @contextlib.contextmanager
427 def mayberedirectstdio(self):
428 yield None
429
396 def redirect(self): 430 def redirect(self):
397 pass 431 pass
398 432
399 def _client(self): 433 def _client(self):
400 client = encoding.environ.get('SSH_CLIENT', '').split(' ', 1)[0] 434 client = encoding.environ.get('SSH_CLIENT', '').split(' ', 1)[0]