13.12. xmlrpc.server — XML-RPC 服务器

未匹配的标注

目的:实现 XML-RPC 服务器

xmlrpc.server模块包含用于使用 XML-RPC 协议创建跨平台,独立于语言的服务器的类。客户端库除了 Python 外还存在许多其他语言,这使得 XML-RPC 成为构建 RPC 样式服务的简便选择。

Note
此处提供的所有示例都包括一个客户端模块以及与演示服务器进行交互的功能。要运行示例,请使用两个单独的外壳窗口,一个用于服务器,一个用于客户端。

一个简单的服务器

这个简单的服务器示例公开了一个使用目录名称并返回内容的函数。第一步是创建SimpleXMLRPCServer实例并告诉它侦听传入请求的位置(在这种情况下为 localhost 端口 9000)。然后,将一个功能定义为服务的一部分,并进行注册,以便服务器知道如何调用它。最后一步是将服务器置于无限循环中,以接收和响应请求。

Warning
此实现具有明显的安全隐患。不要在开放式 Internet 上的服务器上或可能存在安全性问题的任何环境中运行它。

xmlrpc_function.py

from xmlrpc.server import SimpleXMLRPCServer
import logging
import os

# Set up logging
logging.basicConfig(level=logging.INFO)

server = SimpleXMLRPCServer(
    ('localhost', 9000),
    logRequests=True,
)

# Expose a function
def list_contents(dir_name):
    logging.info('list_contents(%s)', dir_name)
    return os.listdir(dir_name)

server.register_function(list_contents)

# Start the server
try:
    print('Use Control-C to exit')
    server.serve_forever()
except KeyboardInterrupt:
    print('Exiting')

可以使用xmlrpc.client通过URL http:/// localhost:9000访问服务器。 html#module-xmlrpc.client“ xmlrpc.client:XML-RPC的客户端库”)。此客户端代码说明了如何从 Python 调用list_contents()服务。

xmlrpc_function_client.py

import xmlrpc.client

proxy = xmlrpc.client.ServerProxy('http://localhost:9000')
print(proxy.list_contents('/tmp'))

ServerProxy使用其基本URL连接到服务器,然后直接在代理上调用方法。在代理上调用的每个方法都转换为对服务器的请求。使用 XML 格式化参数,然后通过 POST 消息将其发送到服务器。服务器解压缩 XML,并根据从客户端调用的方法名称确定要调用的函数。将参数传递给函数,然后将返回值转换回 XML,以返回给客户端。

启动服务器将给出以下输出。

$ python3 xmlrpc_function.py

Use Control-C to exit

在第二个窗口中运行客户端将显示/ tmp目录的内容。

$ python3 xmlrpc_function_client.py

['com.apple.launchd.aoGXonn8nV', 'com.apple.launchd.ilryIaQugf',
'example.db.db',
'KSOutOfProcessFetcher.501.ppfIhqX0vjaTSb8AJYobDV7Cu68=',
'pymotw_import_example.shelve.db']

请求完成后,日志输出将显示在服务器窗口中。

$ python3 xmlrpc_function.py

Use Control-C to exit
INFO:root:list_contents(/tmp)
127.0.0.1 - - [18/Jun/2016 19:54:54] "POST /RPC2 HTTP/1.1" 200 -

输出的第一行来自list_contents()中的logging.info()调用。第二行来自服务器记录请求的服务器,因为logRequestsTrue

备用API名称

有时,模块或库中使用的函数名称不是外部 API 中应使用的名称。名称可能会更改,因为加载了特定于平台的实现,基于配置文件动态构建了服务 API ,或者可以用存根替换实际功能以进行测试。要使用其他名称注册函数,请将名称作为第二个参数传递给register_function()

xmlrpc_alternate_name.py

from xmlrpc.server import SimpleXMLRPCServer
import os

server = SimpleXMLRPCServer(('localhost', 9000))

def list_contents(dir_name):
    "Expose a function with an alternate name"
    return os.listdir(dir_name)

server.register_function(list_contents, 'dir')

try:
    print('Use Control-C to exit')
    server.serve_forever()
except KeyboardInterrupt:
    print('Exiting')

客户端现在应该使用名称dir()代替list_contents()

xmlrpc_alternate_name_client.py

import xmlrpc.client

proxy = xmlrpc.client.ServerProxy('http://localhost:9000')
print('dir():', proxy.dir('/tmp'))
try:
    print('.list_contents():', proxy.list_contents('/tmp'))
except xmlrpc.client.Fault as err:
    print('.ERROR:', err)

调用list_contents()会导致错误,因为服务器不再具有使用该名称注册的处理程序。

$ python3 xmlrpc_alternate_name_client.py

dir(): ['com.apple.launchd.aoGXonn8nV',
'com.apple.launchd.ilryIaQugf', 'example.db.db',
'KSOutOfProcessFetcher.501.ppfIhqX0vjaTSb8AJYobDV7Cu68=',
'pymotw_import_example.shelve.db']

ERROR: <Fault 1: '<class .Exception.>:method "list_contents"
is not supported'>

点缀的API名称

可以使用 Python 标识符通常不合法的名称来注册各个函数。例如,名称中可以包含句点(),以分隔服务中的命名空间。下一个示例扩展了“目录”服务,以添加“创建”和“删除”调用。使用前缀“ dir。”注册所有功能,以便同一台服务器可以使用不同的前缀提供其他服务。此示例中的另一个区别是某些函数返回None,因此必须告知服务器将None值转换为nil值。

xmlrpc_dotted_name.py

from xmlrpc.server import SimpleXMLRPCServer
import os

server = SimpleXMLRPCServer(('localhost', 9000), allow_none=True)

server.register_function(os.listdir, 'dir.list')
server.register_function(os.mkdir, 'dir.create')
server.register_function(os.rmdir, 'dir.remove')

try:
    print('Use Control-C to exit')
    server.serve_forever()
except KeyboardInterrupt:
    print('Exiting')

要在客户端中调用服务功能,只需用虚线名称引用它们即可。
xmlrpc_dotted_name_client.py

import xmlrpc.client

proxy = xmlrpc.client.ServerProxy('http://localhost:9000')
print('BEFORE       :', 'EXAMPLE' in proxy.dir.list('/tmp'))
print('CREATE       :', proxy.dir.create('/tmp/EXAMPLE'))
print('SHOULD EXIST :', 'EXAMPLE' in proxy.dir.list('/tmp'))
print('REMOVE       :', proxy.dir.remove('/tmp/EXAMPLE'))
print('AFTER        :', 'EXAMPLE' in proxy.dir.list('/tmp'))

假设当前系统上没有/tmp/EXAMPLE文件,则示例客户端脚本的输出如下。

$ python3 xmlrpc_dotted_name_client.py

BEFORE       : False
CREATE       : None
SHOULD EXIST : True
REMOVE       : None
AFTER        : False

任意API名称

另一个有趣的功能是能够用其他方式无效的 Python 对象属性名称注册函数。此示例服务注册了一个名称为“ multiply args”的函数。

xmlrpc_arbitrary_name.py

from xmlrpc.server import SimpleXMLRPCServer

server = SimpleXMLRPCServer(('localhost', 9000))

def my_function(a, b):
    return a * b

server.register_function(my_function, 'multiply args')

try:
    print('Use Control-C to exit')
    server.serve_forever()
except KeyboardInterrupt:
    print('Exiting')

由于注册名称包含空格,因此不能使用点符号直接从代理访问它。但是,使用getattr()是可行的。
xmlrpc_arbitrary_name_client.py

import xmlrpc.client

proxy = xmlrpc.client.ServerProxy('http://localhost:9000')
print(getattr(proxy, 'multiply args')(5, 5))

但是,避免使用这样的名称创建服务。提供此示例不一定是因为它是一个好主意,而是因为存在具有任意名称的现有服务,并且新程序可能需要能够调用它们。

$ python3 xmlrpc_arbitrary_name_client.py

25

公开对象的方法

前面的部分讨论了使用良好的命名约定和命名空间来建立 API 的技术。将命名空间纳入 API 的另一种方法是使用类的实例并公开其方法。可以使用一个方法的实例重新创建第一个示例。

xmlrpc_instance.py

from xmlrpc.server import SimpleXMLRPCServer
import os
import inspect

server = SimpleXMLRPCServer(
    ('localhost', 9000),
    logRequests=True,
)

class DirectoryService:
    def list(self, dir_name):
        return os.listdir(dir_name)

server.register_instance(DirectoryService())

try:
    print('Use Control-C to exit')
    server.serve_forever()
except KeyboardInterrupt:
    print('Exiting')

客户端可以直接调用该方法。

xmlrpc_instance_client.py

import xmlrpc.client

proxy = xmlrpc.client.ServerProxy('http://localhost:9000')
print(proxy.list('/tmp'))

输出显示目录的内容。

$ python3 xmlrpc_instance_client.py

['com.apple.launchd.aoGXonn8nV', 'com.apple.launchd.ilryIaQugf',
'example.db.db',
'KSOutOfProcessFetcher.501.ppfIhqX0vjaTSb8AJYobDV7Cu68=',
'pymotw_import_example.shelve.db']

但是,该服务的“ dir。”前缀已丢失。可以通过定义一个类来建立可以从客户端调用的服务树来恢复它。

xmlrpc_instance_dotted_names.py

from xmlrpc.server import SimpleXMLRPCServer
import os
import inspect

server = SimpleXMLRPCServer(
    ('localhost', 9000),
    logRequests=True,
)

class ServiceRoot:
    pass

class DirectoryService:

    def list(self, dir_name):
        return os.listdir(dir_name)

root = ServiceRoot()
root.dir = DirectoryService()

server.register_instance(root, allow_dotted_names=True)

try:
    print('Use Control-C to exit')
    server.serve_forever()
except KeyboardInterrupt:
    print('Exiting')

通过在启用allow_dotted_names的情况下注册ServiceRoot的实例,服务器有权在请求进入时使用getattr()查找命名方法,从而遍历对象树.

xmlrpc_instance_dotted_names_client.py

import xmlrpc.client

proxy = xmlrpc.client.ServerProxy('http://localhost:9000')
print(proxy.dir.list('/tmp'))

dir.list()的输出与以前的实现相同。

$ python3 xmlrpc_instance_dotted_names_client.py

['com.apple.launchd.aoGXonn8nV', 'com.apple.launchd.ilryIaQugf',
'example.db.db',
'KSOutOfProcessFetcher.501.ppfIhqX0vjaTSb8AJYobDV7Cu68=',
'pymotw_import_example.shelve.db']

分发调用

默认情况下,register_instance()查找名称不是以下划线开头的实例的所有可调用属性(“ _”)并使用其名称注册它们。要对公开的方法更加小心,可以使用自定义调度逻辑。

xmlrpc_instance_with_prefix.py

from xmlrpc.server import SimpleXMLRPCServer
import os
import inspect

server = SimpleXMLRPCServer(
    ('localhost', 9000),
    logRequests=True,
)

def expose(f):
    "Decorator to set exposed flag on a function."
    f.exposed = True
    return f

def is_exposed(f):
    "Test whether another function should be publicly exposed."
    return getattr(f, 'exposed', False)

class MyService:
    PREFIX = 'prefix'

    def _dispatch(self, method, params):
        # Remove our prefix from the method name
        if not method.startswith(self.PREFIX + '.'):
            raise Exception(
                'method "{}" is not supported'.format(method)
            )

        method_name = method.partition('.')[2]
        func = getattr(self, method_name)
        if not is_exposed(func):
            raise Exception(
                'method "{}" is not supported'.format(method)
            )

        return func(*params)

    @expose
    def public(self):
        return 'This is public'

    def private(self):
        return 'This is private'

server.register_instance(MyService())

try:
    print('Use Control-C to exit')
    server.serve_forever()
except KeyboardInterrupt:
    print('Exiting')

MyServicepublic()方法被标记为暴露给XML-RPC服务,而private()则没有。当客户端尝试访问MyService的一部分的函数时,将调用_dispatch()方法。它首先强制使用前缀(在这种情况下为“ prefix。”,但可以使用任何字符串)。然后它要求该函数具有一个名为exposed的属性,该属性具有真实值。为了方便,使用装饰器在函数上设置了暴露标志。下面的示例包括一些示例客户端调用。

xmlrpc_instance_with_prefix_client.py

import xmlrpc.client

proxy = xmlrpc.client.ServerProxy('http://localhost:9000')
print('public():', proxy.prefix.public())
try:
    print('private():', proxy.prefix.private())
except Exception as err:
    print('.ERROR:', err)
try:
    print('public() without prefix:', proxy.public())
except Exception as err:
    print('.ERROR:', err)

结果输出以及预期的错误消息被捕获并报告。

$ python3 xmlrpc_instance_with_prefix_client.py

public(): This is public

ERROR: <Fault 1: '<class .Exception.>:method "prefix.private" is
not supported'>

ERROR: <Fault 1: '<class .Exception.>:method "public" is not
supported'>

还有其他几种方法可以覆盖调度机制,包括直接从SimpleXMLRPCServer进行子类化。有关更多详细信息,请参考模块中的文档字符串。

自省API

与许多网络服务一样,可以查询 XML-RPC 服务器以询问其支持什么方法并学习如何使用它们。 SimpleXMLRPCServer包括一组用于执行此自省的公共方法。默认情况下,它们是关闭的,但可以通过register_introspection_functions()启用。可以通过定义_listMethods()_methodHelp()

xmlrpc_introspection.py

from xmlrpc.server import (SimpleXMLRPCServer,
                           list_public_methods)
import os
import inspect

server = SimpleXMLRPCServer(
    ('localhost', 9000),
    logRequests=True,
)
server.register_introspection_functions()

class DirectoryService:

    def _listMethods(self):
        return list_public_methods(self)

    def _methodHelp(self, method):
        f = getattr(self, method)
        return inspect.getdoc(f)

    def list(self, dir_name):
        """list(dir_name) => [<filenames>]

        Returns a list containing the contents of
        the named directory.

        """
        return os.listdir(dir_name)

server.register_instance(DirectoryService())

try:
    print('Use Control-C to exit')
    server.serve_forever()
except KeyboardInterrupt:
    print('Exiting')

在这种情况下,便利功能list_public_methods()扫描实例以返回不以下划线开头的可调用属性的名称(_)。重新定义_listMethods()以应用所需的任何规则。类似地,对于此基本示例,_methodHelp()返回函数的文档字符串,但可以编写该文档字符串以从另一个来源构建帮助字符串。

该客户端查询服务器并报告所有可公开调用的方法。

xmlrpc_introspection_client.py

import xmlrpc.client

proxy = xmlrpc.client.ServerProxy('http://localhost:9000')
for method_name in proxy.system.listMethods():
    print('=' * 60)
    print(method_name)
    print('-' * 60)
    print(proxy.system.methodHelp(method_name))
    print()

系统方法包括在结果中。

$ python3 xmlrpc_introspection_client.py

============================================================
list
------------------------------------------------------------
list(dir_name) => [<filenames>]

Returns a list containing the contents of
the named directory.

============================================================
system.listMethods
------------------------------------------------------------
system.listMethods() => ['add', 'subtract', 'multiple']

Returns a list of the methods supported by the server.

============================================================
system.methodHelp
------------------------------------------------------------
system.methodHelp('add') => "Adds two integers together"

Returns a string containing documentation for the specified method.

============================================================
system.methodSignature
------------------------------------------------------------
system.methodSignature('add') => [double, int, int]

Returns a list describing the signature of the method. In the
above example, the add method takes two integers as arguments
and returns a double result.

This server does NOT support system.methodSignature.

另请参见

-xmlrpc.server的标准库文档
-[xmlrpc.client](pymotw.com/3/xmlrpc.client/index.h...“ xmlrpc.client:XML-RPC的客户端库”) – XML-RPC客户端。
-如何使用XML-RPC –介绍如何使用XML-RPC来以多种语言实现客户端和服务器。

本文章首发在 LearnKu.com 网站上。

本译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。
上一篇 下一篇
Summer
贡献者:2
讨论数量: 0
发起讨论 只看当前版本


暂无话题~