如何在 Pyro v.4.63 中正确使用第三方库

How to properly work with third-party libraries in Pyro v.4.63

我对使用远程第三方库有点困惑:

1) 例如, 我有服务器代码:

import Pyro4
import Pyro4.naming
import Pyro4.utils.flame
Pyro4.config.REQUIRE_EXPOSE = False
Pyro4.config.FLAME_ENABLED = True
Pyro4.config.SERIALIZERS_ACCEPTED = set(["pickle"])
Pyro4.config.SERIALIZER = 'pickle'  # for flameserver


def _main():
    """Start RPC server."""
    ip_address = Pyro4.socketutil.getIpAddress(None, workaround127=True)
    ns_daemon = Pyro4.naming.NameServerDaemon(host=ip_address, port=RPC_PORT)
    Pyro4.utils.flame.start(ns_daemon)

    from remote_management.rpc.component.shell import Shell
    shell_uri = ns_daemon.register(Shell())
    ns_daemon.nameserver.register("shell", shell_uri)
    from remote_management.rpc.component.cpu import CPU
    cpu_uri = ns_daemon.register(CPU())
    ns_daemon.nameserver.register("cpu", cpu_uri)

    ns_daemon.requestLoop()
    ns_daemon.close()

if __name__ == "__main__":
    _main()

CPU组件是这样的:

import psutil

class CPU(object):
    def ps(self):
        return psutil.cpu_times()

当我在客户端使用时是这样的:

import Pyro4
import Pyro4.utils.flame
import Pyro4.errors

Pyro4.config.SERIALIZER = "pickle"

proxy = Pyro4.Proxy("PYRONAME:cpu@myhost:47976")
print proxy.ps()

我收到一个错误:

Traceback (most recent call last):
  File "t.py", line 145, in <module>
    print proxy.ps()
  File "/home/korolev/.envs/auto/lib/python2.7/site-packages/Pyro4/core.py", line 187, in __call__
    return self.__send(self.__name, args, kwargs)
  File "/home/korolev/.envs/auto/lib/python2.7/site-packages/Pyro4/core.py", line 464, in _pyroInvoke
    data = serializer.deserializeData(msg.data, compressed=msg.flags & message.FLAGS_COMPRESSED)
  File "/home/korolev/.envs/auto/lib/python2.7/site-packages/Pyro4/util.py", line 170, in deserializeData
    return self.loads(data)
  File "/home/korolev/.envs/auto/lib/python2.7/site-packages/Pyro4/util.py", line 451, in loads
        return pickle.loads(data)

ImportError: No module named psutil._pslinux

如您所见,它无法反序列化。如果我将 return 更改为:

return tuple(psutil.cpu_times()),然后就可以了

2) 我认为下一个问题具有相同的性质: 服务器代码几乎相同,除了组件

from remote_management.rpc.component.registrator import ModuleRegistrator
reg_uri = ns_daemon.register(ModuleRegistrator())
ns_daemon.nameserver.register("module_registrator", reg_uri)

如果我需要远程模块,但本地没有,为什么我不能这样做:

注册器组件:

class ModuleRegistrator(object):
    def register(self, module):
        module = importlib.import_module(module)
        self._pyroDaemon.register(module)
        return module

所以当我在客户端使用它时:

proxy = Pyro4.Proxy("PYRONAME:module_registrator@myhost:47976")
print proxy.register("psutil").cpu_times()

我得到一个错误:

Traceback (most recent call last):
  File "t.py", line 145, in <module>
    print proxy.register('psutil').cpu_times()
  File "/home/korolev/.envs/auto/lib/python2.7/site-packages/Pyro4/core.py", line 279, in __getattr__
    raise AttributeError("remote object '%s' has no exposed attribute or method '%s'" % (self._pyroUri, name))
AttributeError: remote object 'PYRONAME:module_registrator@myhost:47976' has no exposed attribute or method 'register'

请帮助我理解这个错误以及我遗漏了什么?与第三方远程合作时处理此类事情的最佳方法是什么?提前致谢!

首先,您似乎在使用 Pyro4 的 'flame' 功能,它需要 pickle 序列化协议。这很强大,但并非没有问题。 你确定你 want/need 使用火焰?我希望您了解它的安全隐患。 http://pyro4.readthedocs.io/en/latest/flame.html

现在,你的实际问题由三部分组成(尽量让你以后的问题更多地只涉及一个问题,这样可以更简单地回答它们)

回答问题的第一部分:

正如您自己发现的那样,您不能假设所有类型都可以通过网络传输并神奇地在另一端工作。特别是 pickle 这只适用于其中的模块 类型已定义,在双方都可用。 在您的情况下,psutil 模块似乎在您的客户端不可用,因此当它尝试反序列化 psutil.cpu_times().

返回的对象时出现 pickle 错误

解决方案也可以是在您的客户端安装 psutil。 您还发现了一个替代解决方案:不要通过网络传递自定义类型的对象,而是尽可能坚持使用内置类型。当您将结果转换为元组或列表或字典时, 这些可以毫无问题地序列化和反序列化(如果它们包含的所有对象也是内置类型)。

回答你问题的第二部分:

这段代码看起来像是您在尝试为第一个问题中的问题寻找解决方法? 无论如何,错误应该很清楚;这意味着它所说的:您的代理连接到的远程对象没有公开您尝试调用的方法。你应该添加一个 @Pyro4.expose 装饰 正确暴露它。

也就是说,您已经在使用 Flame,那么为什么不使用它的远程模块功能呢?参见 http://pyro4.readthedocs.io/en/latest/flame.html#flame-object-and-examples

例如(我假设你在客户端安装了psutil):

import Pyro4.utils.flame

Pyro4.config.SERIALIZER = "pickle"
flame = Pyro4.utils.flame.connect("yourhost:9999")

psutilmodule = flame.module("psutil")
print("remote cpu:", psutilmodule.cpu_times())

最后,

解决您的问题"what is the best approach to handle things like this when working with third-party remotely":

  • 尽量不要公开您正在使用的 API 的详细信息和类型,尽可能将其减少为内置类型。 http://pyro4.readthedocs.io/en/latest/tipstricks.html#consider-using-basic-data-types-instead-of-custom-classes 如果你做的正确,你也可以简单地使用 Pyro 的默认序列化器(蛇)而不是泡菜,并且没有任何安全问题。 (这确实需要一个不依赖于火焰的解决方案,请参阅下一个项目符号)
  • 不要使用 Flame,除非您完全确定您可以接受它的安全隐患并且您无法通过其他方式解决您的问题。而是创建一个普通的 Pyro 服务器 class,带有您自己编写的专用自定义 API。
  • 仅公开您实际需要远程访问的远程方法。