Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 3059a9d

Browse files
committed
context pattern, prototype support in zerorpc cli.
If the environment variable ZERORPC_CONNECT_TO is defined, zerorpc's cli will use its content as the default endpoint to connect to. When opening a server context from zerorpc's cli, a subshell is opened, with ZERORPC_CONNECT_TO pointing to a temporary ipc. The IPC is a proxy to the opened context. This proxy is really simple and stupid right now, and doesn't support more than one client at a time (it shouldn't be too much of a problem for most cases anyway).
1 parent 925a314 commit 3059a9d

File tree

1 file changed

+124
-9
lines changed

1 file changed

+124
-9
lines changed

bin/zerorpc

Lines changed: 124 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,12 @@ import json
2929
import sys
3030
import inspect
3131
import os
32+
import random
33+
import gevent
3234
from pprint import pprint
3335

3436
import zerorpc
37+
from zerorpc import zmq
3538

3639

3740
parser = argparse.ArgumentParser(
@@ -161,6 +164,111 @@ def zerorpc_inspect(client, method=None, long_doc=True, include_argspec=True):
161164
longest_name_len = max(len(name) for name, doc in r)
162165
return (longest_name_len, r)
163166

167+
def context_shell(client):
168+
try:
169+
# We want to avoid making zerorpc dependent upon gevent_subprocess until
170+
# the context feature conquer the world and become indispensable.
171+
import gevent_subprocess
172+
except ImportError as e:
173+
print e
174+
print 'You might want to install gevent_subprocess to',
175+
print 'use the sub shell feature of the zerorpc''s cli.'
176+
print 'ex: pip install gevent_subprocess'
177+
return -1
178+
179+
# This ugly broken proxy assume that you will never try to run more than one
180+
# zerorpc client at a time... No one should take this code as a good
181+
# practice! Since the whole thing is just some convenience for zerorpc's cli
182+
# it shouldn't be so much a big deal anyway.
183+
184+
context_name = client._zerorpc_name()
185+
class Proxy(zerorpc.SocketBase):
186+
def __init__(self, channel):
187+
self._channel = channel
188+
self._zmqid = None
189+
self._msgid = None
190+
self._sender_task = None
191+
self._recver_task = None
192+
super(Proxy, self).__init__(zmq.XREP)
193+
194+
def close(self):
195+
if self._sender_task:
196+
self._sender_task.kill()
197+
if self._recver_task:
198+
self._recver_task.kill()
199+
super(Proxy, self).close()
200+
201+
def _sender(self):
202+
while True:
203+
event = self._events.recv()
204+
self._zmqid = event.header.get('zmqid', None)
205+
self._msgid = event.header.get('message_id', None)
206+
self._channel.emit_event(event)
207+
208+
def _recver(self):
209+
while True:
210+
event = self._channel.recv()
211+
self._events.emit_event(event, self._zmqid)
212+
213+
def run(self):
214+
self._sender_task = gevent.spawn(self._sender)
215+
self._recver_task = gevent.spawn(self._recver)
216+
217+
def _sender_error(self):
218+
# reply in loop that the context is closed...
219+
while True:
220+
event = self._events.recv()
221+
self._zmqid = event.header.get('zmqid', None)
222+
self._msgid = event.header.get('message_id', None)
223+
self._events.emit('ERR', ('ContextClosed',
224+
'Context closed! You might want to exit your sub-shell and open'
225+
' another one.', None),
226+
dict(zmqid=self._zmqid, response_to=self._msgid))
227+
228+
def error_mode(self, e):
229+
if self._sender_task:
230+
self._sender_task.kill()
231+
self._sender_task = None
232+
if self._recver_task:
233+
self._recver_task.kill()
234+
self._recver_task = None
235+
# send an error over the wire to stop any pending zerorpc client on
236+
# the other side of the IPC.
237+
self._events.emit('ERR', (type(e).__name__, str(e), None),
238+
dict(zmqid=self._zmqid, response_to=self._msgid))
239+
self._sender_task = gevent.spawn(self._sender_error)
240+
241+
# So here, we roughly inter-connect an IPC to the remote Context.
242+
ipc_endpoint = '/tmp/zerorpc_context_proxy_{0}'.format(random.random())
243+
proxy_endpoint = 'ipc://{0}'.format(ipc_endpoint)
244+
client_events = client._multiplexer._events
245+
client._multiplexer.close()
246+
proxy = Proxy(client_events)
247+
proxy.bind(proxy_endpoint)
248+
proxy.run()
249+
250+
print 'You are in a subshell, zerorpc\'s cli will now connect to this sub rpc',
251+
print 'context by default. To close the context, simply exit the sub-shell.'
252+
print 'ZERORPC_CONNECT_TO="{0}"'.format(proxy_endpoint)
253+
print '-- context "{0}" open'.format(context_name)
254+
print
255+
env = os.environ.copy()
256+
env['ZERORPC_CONNECT_TO'] = proxy_endpoint
257+
subshell = gevent_subprocess.Popen(os.environ.get('SHELL', 'sh'), env=env)
258+
try:
259+
subshell.wait()
260+
except Exception as e:
261+
print '-- '
262+
print '-- context "{0}" closed on error: {1}'.format(context_name, e)
263+
proxy.error_mode(e)
264+
subshell.wait()
265+
else:
266+
print '-- context "{0}" closed'.format(context_name)
267+
finally:
268+
try:
269+
os.unlink(ipc_endpoint)
270+
except OSError:
271+
pass
164272

165273
def run_client(args):
166274
client = zerorpc.Client(timeout=args.timeout, heartbeat=args.heartbeat,
@@ -187,12 +295,10 @@ def run_client(args):
187295
else:
188296
call_args = args.params
189297
results = client(args.command, *call_args)
190-
if getattr(results, 'next', None) is None:
191-
if args.print_json:
192-
json.dump(results, sys.stdout)
193-
else:
194-
pprint(results)
195-
else:
298+
if isinstance(results, zerorpc.ClientBase):
299+
# context
300+
return context_shell(results)
301+
elif getattr(results, 'next', None) is not None:
196302
# streaming responses
197303
if args.print_json:
198304
first = True
@@ -207,20 +313,29 @@ def run_client(args):
207313
else:
208314
for result in results:
209315
pprint(result)
316+
else:
317+
# req/rep
318+
if args.print_json:
319+
json.dump(results, sys.stdout)
320+
else:
321+
pprint(results)
210322

211323

212324
def main():
213325
args = parser.parse_args()
214326

215-
if args.bind or args.connect:
327+
proxy_endpoint = os.environ.get('ZERORPC_CONNECT_TO', None)
328+
if args.bind or args.connect or proxy_endpoint:
216329
if args.command:
217330
args.params.insert(0, args.command)
218331
args.command = args.address
219332
args.address = None
220333

221334
if not (args.bind or args.connect or args.address):
222-
parser.print_help()
223-
return -1
335+
if not proxy_endpoint:
336+
parser.print_help()
337+
return -1
338+
args.address = proxy_endpoint
224339

225340
if args.client:
226341
return run_client(args)

0 commit comments

Comments
 (0)