简体   繁体   中英

How to access properties via RPC

I implemented Pickle-RPC in reference to Python's xmlrpc module.

From client side, I can call methods of the instance which are registered to the RPC server.

s = ServerProxy(('127.0.0.1', 49152), DatagramRequestSender)
s.foo('bar')

I also want to access properties.

s = ServerProxy(('127.0.0.1', 49152), DatagramRequestSender)
s.foobar

How should I implement?

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)

You'll have to intercept the attribute access on the object via overriding its __getattr__ method, figuring out that it is just an attribute access instead of a method call, and send a specific message to your server that doesn't call the method but instead returns the value of the referenced attribute directly. This is precisely what Pyro4 does . Code: https://github.com/irmen/Pyro4/blob/master/src/Pyro4/core.py#L255

Also: using pickle as a serialization format is a huge security problem. It allows arbitrary remote code execution. So for the love of all that is holy make sure you absolutely trust the clients and never accept random connections from over the internet.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM