如何通过 RPC 访问属性
How to access properties via RPC
我参考 Python 的 xmlrpc 模块实现了 Pickle-RPC。
从客户端,我可以调用注册到 RPC 服务器的实例的方法。
s = ServerProxy(('127.0.0.1', 49152), DatagramRequestSender)
s.foo('bar')
我也想访问属性。
s = ServerProxy(('127.0.0.1', 49152), DatagramRequestSender)
s.foobar
我应该如何实施?
client.py
import pickle
import socket
from io import BytesIO
class StreamRequestSender:
max_packet_size = 8192
address_family = socket.AF_INET
socket_type = socket.SOCK_STREAM
def send_request(self, address, request):
with socket.socket(self.address_family, self.socket_type) as client_socket:
with client_socket.makefile('wb') as wfile:
pickle.dump(request, wfile)
with client_socket.makefile('rb') as rfile:
response = pickle.load(rfile)
return response
class DatagramRequestSender(StreamRequestSender):
socket_type = socket.SOCK_DGRAM
def send_request(self, address, request):
with socket.socket(self.address_family, self.socket_type) as client_socket:
with BytesIO() as wfile:
pickle.dump(request, wfile)
client_socket.sendto(wfile.getvalue(), address)
data = client_socket.recv(self.max_packet_size)
with BytesIO(data) as rfile:
response = pickle.load(rfile)
return response
class ServerProxy:
def __init__(self, address, RequestSenderClass):
self.__address = address
self.__request_sender = RequestSenderClass()
def __send(self, method, args):
request = (method, args)
response = self.__request_sender.send_request(self.__address, request)
return response
def __getattr__(self, name):
return _Method(self.__send, name)
class _Method:
def __init__(self, send, name):
self.__send = send
self.__name = name
def __getattr__(self, name):
return _Method(self.__send, "{}.{}".format(self.__name, name))
def __call__(self, *args):
return self.__send(self.__name, args)
if __name__ == '__main__':
s = ServerProxy(('127.0.0.1', 49152), DatagramRequestSender)
print(s.pow(2, 160))
server.py
import pickle
import sys
from socketserver import StreamRequestHandler, DatagramRequestHandler, ThreadingTCPServer, ThreadingUDPServer
class RPCRequestHandler:
def handle(self):
method, args = pickle.load(self.rfile)
response = self.server.dispatch(method, args)
pickle.dump(response, self.wfile)
class StreamRPCRequestHandler(RPCRequestHandler, StreamRequestHandler):
pass
class DatagramRPCRequestHandler(RPCRequestHandler, DatagramRequestHandler):
pass
class RPCDispatcher:
def __init__(self, instance=None):
self.__instance = instance
def register_instance(self, instance):
self.__instance = instance
def dispatch(self, method, args):
_method = None
if self.__instance is not None:
try:
_method = self._resolve_dotted_attribute(self.__instance, method)
except AttributeError:
pass
if _method is not None:
return _method(*args)
else:
raise Exception('method "{}" is not supported'.format(method))
@staticmethod
def _resolve_dotted_attribute(obj, dotted_attribute):
attributes = dotted_attribute.split('.')
for attribute in attributes:
if attribute.startswith('_'):
raise AttributeError('attempt to access private attribute "{}"'.format(attribute))
else:
obj = getattr(obj, attribute)
return obj
class RPCServer(ThreadingUDPServer, RPCDispatcher):
def __init__(self, server_address, RPCRequestHandlerClass, instance=None):
ThreadingUDPServer.__init__(self, server_address, RPCRequestHandlerClass)
RPCDispatcher.__init__(self, instance)
if __name__ == '__main__':
class ExampleService:
def pow(self, base, exp):
return base ** exp
s = RPCServer(('127.0.0.1', 49152), DatagramRPCRequestHandler, ExampleService())
print('Serving Pickle-RPC on localhost port 49152')
print('It is advisable to run this example server within a secure, closed network.')
try:
s.serve_forever()
except KeyboardInterrupt:
print("\nKeyboard interrupt received, exiting.")
s.server_close()
sys.exit(0)
您必须通过覆盖对象的 __getattr__
方法来拦截对象的属性访问,弄清楚它只是一个属性访问而不是方法调用,然后向您的服务器发送特定消息它不调用该方法,而是直接 returns 引用属性的值。这正是what Pyro4 does. Code: https://github.com/irmen/Pyro4/blob/master/src/Pyro4/core.py#L255
另外:使用 pickle 作为序列化格式是一个巨大的安全问题。它允许任意远程代码执行。因此,为了所有神圣的爱,请确保您绝对信任客户,并且永远不要接受来自互联网的随机连接。
我参考 Python 的 xmlrpc 模块实现了 Pickle-RPC。
从客户端,我可以调用注册到 RPC 服务器的实例的方法。
s = ServerProxy(('127.0.0.1', 49152), DatagramRequestSender)
s.foo('bar')
我也想访问属性。
s = ServerProxy(('127.0.0.1', 49152), DatagramRequestSender)
s.foobar
我应该如何实施?
client.py
import pickle
import socket
from io import BytesIO
class StreamRequestSender:
max_packet_size = 8192
address_family = socket.AF_INET
socket_type = socket.SOCK_STREAM
def send_request(self, address, request):
with socket.socket(self.address_family, self.socket_type) as client_socket:
with client_socket.makefile('wb') as wfile:
pickle.dump(request, wfile)
with client_socket.makefile('rb') as rfile:
response = pickle.load(rfile)
return response
class DatagramRequestSender(StreamRequestSender):
socket_type = socket.SOCK_DGRAM
def send_request(self, address, request):
with socket.socket(self.address_family, self.socket_type) as client_socket:
with BytesIO() as wfile:
pickle.dump(request, wfile)
client_socket.sendto(wfile.getvalue(), address)
data = client_socket.recv(self.max_packet_size)
with BytesIO(data) as rfile:
response = pickle.load(rfile)
return response
class ServerProxy:
def __init__(self, address, RequestSenderClass):
self.__address = address
self.__request_sender = RequestSenderClass()
def __send(self, method, args):
request = (method, args)
response = self.__request_sender.send_request(self.__address, request)
return response
def __getattr__(self, name):
return _Method(self.__send, name)
class _Method:
def __init__(self, send, name):
self.__send = send
self.__name = name
def __getattr__(self, name):
return _Method(self.__send, "{}.{}".format(self.__name, name))
def __call__(self, *args):
return self.__send(self.__name, args)
if __name__ == '__main__':
s = ServerProxy(('127.0.0.1', 49152), DatagramRequestSender)
print(s.pow(2, 160))
server.py
import pickle
import sys
from socketserver import StreamRequestHandler, DatagramRequestHandler, ThreadingTCPServer, ThreadingUDPServer
class RPCRequestHandler:
def handle(self):
method, args = pickle.load(self.rfile)
response = self.server.dispatch(method, args)
pickle.dump(response, self.wfile)
class StreamRPCRequestHandler(RPCRequestHandler, StreamRequestHandler):
pass
class DatagramRPCRequestHandler(RPCRequestHandler, DatagramRequestHandler):
pass
class RPCDispatcher:
def __init__(self, instance=None):
self.__instance = instance
def register_instance(self, instance):
self.__instance = instance
def dispatch(self, method, args):
_method = None
if self.__instance is not None:
try:
_method = self._resolve_dotted_attribute(self.__instance, method)
except AttributeError:
pass
if _method is not None:
return _method(*args)
else:
raise Exception('method "{}" is not supported'.format(method))
@staticmethod
def _resolve_dotted_attribute(obj, dotted_attribute):
attributes = dotted_attribute.split('.')
for attribute in attributes:
if attribute.startswith('_'):
raise AttributeError('attempt to access private attribute "{}"'.format(attribute))
else:
obj = getattr(obj, attribute)
return obj
class RPCServer(ThreadingUDPServer, RPCDispatcher):
def __init__(self, server_address, RPCRequestHandlerClass, instance=None):
ThreadingUDPServer.__init__(self, server_address, RPCRequestHandlerClass)
RPCDispatcher.__init__(self, instance)
if __name__ == '__main__':
class ExampleService:
def pow(self, base, exp):
return base ** exp
s = RPCServer(('127.0.0.1', 49152), DatagramRPCRequestHandler, ExampleService())
print('Serving Pickle-RPC on localhost port 49152')
print('It is advisable to run this example server within a secure, closed network.')
try:
s.serve_forever()
except KeyboardInterrupt:
print("\nKeyboard interrupt received, exiting.")
s.server_close()
sys.exit(0)
您必须通过覆盖对象的 __getattr__
方法来拦截对象的属性访问,弄清楚它只是一个属性访问而不是方法调用,然后向您的服务器发送特定消息它不调用该方法,而是直接 returns 引用属性的值。这正是what Pyro4 does. Code: https://github.com/irmen/Pyro4/blob/master/src/Pyro4/core.py#L255
另外:使用 pickle 作为序列化格式是一个巨大的安全问题。它允许任意远程代码执行。因此,为了所有神圣的爱,请确保您绝对信任客户,并且永远不要接受来自互联网的随机连接。