2016-12-09 11 views
0

Pythonのxmlrpcモジュールを参照してPickle-RPCを実装しました。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) 

答えて

1

あなたがそれを考え出す、その__getattr__メソッドをオーバーライドを経由してオブジェクトの属性へのアクセスを傍受する必要がありますメソッド呼び出しの代わりに属性へのアクセスに過ぎず、メソッドを呼び出さずに代わりに参照されるattrの値を返す特定のメッセージをサーバーに送信します直接ibute。これはまさにwhat Pyro4 doesです。コード:https://github.com/irmen/Pyro4/blob/master/src/Pyro4/core.py#L255

また、シリアル化フォーマットとしてpickleを使用することは、大きなセキュリティ上の問題です。任意のリモートコード実行が可能です。だから、聖なるすべての愛のために、あなたは絶対にクライアントを信頼し、決してインターネットからのランダムな接続を受け入れないようにしてください。

+0

Pyro4の 'Proxy'クラスは、正しく理解すれば、動的に管理された' _pyroAttrs'を使ってリモート属性を受け取ります。コミュニケーションなしにメソッド/プロパティを動的に解決することはできません。 P.S. Pyro4はとても良いライブラリです。私は何かを聞きたい。その1つ、Pyro 4はTCPとUDPの違いをどのように吸収しますか?ありがとうございました。 – sira

+0

私はあなたの質問を理解していません。 Pyro4はTCP/IPソケットを排他的に使用しているため、Pyroのやり方ではUDP(パケットの順序を保証していない信頼性の低いプロトコル)で信頼性の高いRPCを実行する方法はありませんが、これを議論する場所:) –

+0

ああ、私は参照してください。ありがとうございました。 – sira

関連する問題