13.11. xmlrpc.client — XML-RPC 客户端类库

目的:用于XML-RPC通信的客户端库。

XML-RPC是建立在HTTP和XML之上的轻量级远程过程调用协议。 xmlrpclib模块允许Python程序与以任何语言编写的XML-RPC服务器进行通信。

本节中的所有示例均使用xmlrpc_server.py中定义的服务器,该服务器在源代码发行版中可用,并包含在此处以供参考。

xmlrpc_server.py

from xmlrpc.server import SimpleXMLRPCServer
from xmlrpc.client import Binary
import datetime

class ExampleService:

    def ping(self):
        """Simple function to respond when called
        to demonstrate connectivity.
        """
        return True

    def now(self):
        """Returns the server current date and time."""
        return datetime.datetime.now()

    def show_type(self, arg):
        """Illustrates how types are passed in and out of
        server methods.

        Accepts one argument of any type.

        Returns a tuple with string representation of the value,
        the name of the type, and the value itself.

        """
        return (str(arg), str(type(arg)), arg)

    def raises_exception(self, msg):
        "Always raises a RuntimeError with the message passed in"
        raise RuntimeError(msg)

    def send_back_binary(self, bin):
        """Accepts single Binary argument, and unpacks and
        repacks it to return it."""
        data = bin.data
        print('send_back_binary({!r})'.format(data))
        response = Binary(data)
        return response

if __name__ == '__main__':
    server = SimpleXMLRPCServer(('localhost', 9000),
                                logRequests=True,
                                allow_none=True)
    server.register_introspection_functions()
    server.register_multicall_functions()

    server.register_instance(ExampleService())

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

连接到服务器

将客户端连接到服务器的最简单方法是实例化ServerProxy对象,并为其提供服务器的URI。例如,演示服务器在本地主机的端口9000上运行。

xmlrpc_ServerProxy.py

import xmlrpc.client

server = xmlrpc.client.ServerProxy('http://localhost:9000')
print('Ping:', server.ping())

在这种情况下,服务的ping() 方法不带任何参数并返回单个布尔值。

$ python3 xmlrpc_ServerProxy.py

Ping: True

其他选项可用于支持替代运输。现成的同时支持 HTTP 和 HTTPS,并且都具有基本身份验证。为了实现新的通信通道,只需要一个新的传输类。例如,通过 SMTP 实现 XML-RPC 可能是一个有趣的练习。

xmlrpc_ServerProxy_verbose.py

import xmlrpc.client

server = xmlrpc.client.ServerProxy('http://localhost:9000',
                                   verbose=True)
print('Ping:', server.ping())

verbose选项提供调试信息,可用于解决通信错误。

$ python3 xmlrpc_ServerProxy_verbose.py

send: b'POST /RPC2 HTTP/1.1..Host: localhost:9000..
Accept-Encoding: gzip..Content-Type: text/xml..
User-Agent: Python-xmlrpc/3.5..Content-Length: 98....'
send: b"<?xml version='1.0'?>.<methodCall>.<methodName>
ping</methodName>.<params>.</params>.</methodCall>."
reply: 'HTTP/1.0 200 OK..'
header: Server header: Date header: Content-type header:
Content-length body: b"<?xml version='1.0'?>.<methodResponse>.
<params>.<param>.<value><boolean>1</boolean></value>.</param>
.</params>.</methodResponse>."
Ping: True

如果需要备用系统,则可以从 UTF-8 更改默认编码。

xmlrpc_ServerProxy_encoding.py

import xmlrpc.client

server = xmlrpc.client.ServerProxy('http://localhost:9000',
                                   encoding='ISO-8859-1')
print('Ping:', server.ping())

服务器自动检测正确的编码。

$ python3 xmlrpc_ServerProxy_encoding.py

Ping: True

allow_none选项控制Python的None值是否自动转换为nil值或是否导致错误。

xmlrpc_ServerProxy_allow_none.py

import xmlrpc.client

server = xmlrpc.client.ServerProxy('http://localhost:9000',
                                   allow_none=False)
try:
    server.show_type(None)
except TypeError as err:
    print('ERROR:', err)

server = xmlrpc.client.ServerProxy('http://localhost:9000',
                                   allow_none=True)
print('Allowed:', server.show_type(None))

如果客户端不允许None,则会在本地引发错误,但如果未将其配置为允许None,则也可以从服务器内部引发该错误。

$ python3 xmlrpc_ServerProxy_allow_none.py

ERROR: cannot marshal None unless allow_none is enabled
Allowed: ['None', "<class 'NoneType'>", None]

数据类型

XML-RPC 协议可识别一组有限的通用数据类型。这些类型可以作为参数或返回值传递,并可以组合以创建更复杂的数据结构。

xmlrpc_types.py

import xmlrpc.client
import datetime

server = xmlrpc.client.ServerProxy('http://localhost:9000')

data = [
    ('boolean', True),
    ('integer', 1),
    ('float', 2.5),
    ('string', 'some text'),
    ('datetime', datetime.datetime.now()),
    ('array', ['a', 'list']),
    ('array', ('a', 'tuple')),
    ('structure', {'a': 'dictionary'}),
]

for t, v in data:
    as_string, type_name, value = server.show_type(v)
    print('{:<12}: {}'.format(t, as_string))
    print('{:12}  {}'.format('', type_name))
    print('{:12}  {}'.format('', value))

简单类型是:

$ python3 xmlrpc_types.py

boolean     : True
              <class 'bool'>
              True
integer     : 1
              <class 'int'>
              1
float       : 2.5
              <class 'float'>
              2.5
string      : some text
              <class 'str'>
              some text
datetime    : 20160618T19:31:47
              <class 'xmlrpc.client.DateTime'>
              20160618T19:31:47
array       : ['a', 'list']
              <class 'list'>
              ['a', 'list']
array       : ['a', 'tuple']
              <class 'list'>
              ['a', 'tuple']
structure   : {'a': 'dictionary'}
              <class 'dict'>
              {'a': 'dictionary'}

可以嵌套支持的类型以创建任意复杂度的值。

xmlrpc_types_nested.py

import xmlrpc.client
import datetime
import pprint

server = xmlrpc.client.ServerProxy('http://localhost:9000')

data = {
    'boolean': True,
    'integer': 1,
    'floating-point number': 2.5,
    'string': 'some text',
    'datetime': datetime.datetime.now(),
    'array1': ['a', 'list'],
    'array2': ('a', 'tuple'),
    'structure': {'a': 'dictionary'},
}
arg = []
for i in range(3):
    d = {}
    d.update(data)
    d['integer'] = i
    arg.append(d)

print('Before:')
pprint.pprint(arg, width=40)

print('.After:')
pprint.pprint(server.show_type(arg)[-1], width=40)

该程序将包含所有受支持类型的词典列表传递到示例服务器,该服务器返回数据。元组将转换为列表,并且datetime实例将转换为DateTime对象,但否则数据将保持不变。

$ python3 xmlrpc_types_nested.py

Before:
[{'array': ('a', 'tuple'),
  'boolean': True,
  'datetime': datetime.datetime(2016, 6, 18, 19, 27, 30, 45333),
  'floating-point number': 2.5,
  'integer': 0,
  'string': 'some text',
  'structure': {'a': 'dictionary'}},
 {'array': ('a', 'tuple'),
  'boolean': True,
  'datetime': datetime.datetime(2016, 6, 18, 19, 27, 30, 45333),
  'floating-point number': 2.5,
  'integer': 1,
  'string': 'some text',
  'structure': {'a': 'dictionary'}},
 {'array': ('a', 'tuple'),
  'boolean': True,
  'datetime': datetime.datetime(2016, 6, 18, 19, 27, 30, 45333),
  'floating-point number': 2.5,
  'integer': 2,
  'string': 'some text',
  'structure': {'a': 'dictionary'}}]

After:
[{'array': ['a', 'tuple'],
  'boolean': True,
  'datetime': <DateTime '20160618T19:27:30' at 0x101ecfac8>,
  'floating-point number': 2.5,
  'integer': 0,
  'string': 'some text',
  'structure': {'a': 'dictionary'}},
 {'array': ['a', 'tuple'],
  'boolean': True,
  'datetime': <DateTime '20160618T19:27:30' at 0x101ecfcc0>,
  'floating-point number': 2.5,
  'integer': 1,
  'string': 'some text',
  'structure': {'a': 'dictionary'}},
 {'array': ['a', 'tuple'],
  'boolean': True,
  'datetime': <DateTime '20160618T19:27:30' at 0x101ecfe10>,
  'floating-point number': 2.5,
  'integer': 2,
  'string': 'some text',
  'structure': {'a': 'dictionary'}}]

XML-RPC 支持将日期作为本机类型,并且xmlrpclib可以使用两个类之一来表示传出代理中或从服务器接收到的日期值。

xmlrpc_ServerProxy_use_datetime.py

import xmlrpc.client

server = xmlrpc.client.ServerProxy('http://localhost:9000',
                                   use_datetime=True)
now = server.now()
print('With:', now, type(now), now.__class__.__name__)

server = xmlrpc.client.ServerProxy('http://localhost:9000',
                                   use_datetime=False)
now = server.now()
print('Without:', now, type(now), now.__class__.__name__)

默认情况下,使用内部版本的DateTime,但是use_datetime选项打开支持使用datetime模块。

$ python3 source/xmlrpc.client/xmlrpc_ServerProxy_use_datetime.py

With: 2016-06-18 19:18:31 <class 'datetime.datetime'> datetime
Without: 20160618T19:18:31 <class 'xmlrpc.client.DateTime'> DateTime

传递对象

Python 类的实例被视为结构,并作为字典传递,而对象的属性作为字典中的值。

xmlrpc_types_object.py

import xmlrpc.client
import pprint

class MyObj:

    def __init__(self, a, b):
        self.a = a
        self.b = b

    def __repr__(self):
        return 'MyObj({!r}, {!r})'.format(self.a, self.b)

server = xmlrpc.client.ServerProxy('http://localhost:9000')

o = MyObj(1, 'b goes here')
print('o  :', o)
pprint.pprint(server.show_type(o))

o2 = MyObj(2, o)
print('.o2 :', o2)
pprint.pprint(server.show_type(o2))

传递对象

Python 类的实例被视为结构,并作为字典传递,而对象的属性作为字典中的值。

xmlrpc_types_object.py

导入xmlrpc.client
导入pprint

MyObj类:

    def __init __(self,a,b):
        self.a = a
        self.b = b

    def __repr __(自己):
        返回'MyObj({!r},{!r})'。format(self.a,self.b)

服务器= xmlrpc.client.ServerProxy('http:/// localhost:9000')

o = MyObj(1,'b去这里')
打印('o:',o)
pprint.pprint(server.show_type(o))

o2 = MyObj(2,o)
print('。o2:',o2)
pprint.pprint(server.show_type(o2))

当该值从服务器发送回客户端时,结果是客户端上的字典,因为值中没有任何编码来告诉服务器(或客户端)应将其实例化为类的一部分。

$ python3 xmlrpc_types_object.py

o  : MyObj(1, 'b goes here')
["{'b': 'b goes here', 'a': 1}", "<class 'dict'>",
{'a': 1, 'b': 'b goes here'}]

o2 : MyObj(2, MyObj(1, 'b goes here'))
["{'b': {'b': 'b goes here', 'a': 1}, 'a': 2}",
 "<class 'dict'>",
 {'a': 2, 'b': {'a': 1, 'b': 'b goes here'}}]

二进制数据

传递给服务器的所有值都将被编码并自动转义。但是,某些数据类型可能包含无效的 XML 字符。例如,二进制图像数据可能包括 ASCII 控制范围为 0 到 31 的字节值。要传递二进制数据,最好使用Binary类对其进行编码以进行传输。

xmlrpc_Binary.py

import xmlrpc.client
import xml.parsers.expat

server = xmlrpc.client.ServerProxy('http://localhost:9000')

s = b'This is a string with control characters.00'
print('Local string:', s)

data = xmlrpc.client.Binary(s)
response = server.send_back_binary(data)
print('As binary:', response.data)

try:
    print('As string:', server.show_type(s))
except xml.parsers.expat.ExpatError as err:
    print('.ERROR:', err)

如果将包含 NULL 字节的字符串传递给show_type(),则 XML 解析器在处理响应时会引发异常。

$ python3 xmlrpc_Binary.py

Local string: b'This is a string with control characters.00'
As binary: b'This is a string with control characters.00'

ERROR: not well-formed (invalid token): line 6, column 55

Binary 对象还可以用于通过 pickle。与通过网络发送多少可执行代码有关的常规安全问题在这里适用(即除非通信通道安全,否则不要这样做)。


import xmlrpc.client
import pickle
import pprint

class MyObj:

    def __init__(self, a, b):
        self.a = a
        self.b = b

    def __repr__(self):
        return 'MyObj({!r}, {!r})'.format(self.a, self.b)

server = xmlrpc.client.ServerProxy('http://localhost:9000')

o = MyObj(1, 'b goes here')
print('Local:', id(o))
print(o)

print('.As object:')
pprint.pprint(server.show_type(o))

p = pickle.dumps(o)
b = xmlrpc.client.Binary(p)
r = server.send_back_binary(b)

o2 = pickle.loads(r.data)
print('.From pickle:', id(o2))
pprint.pprint(o2)

Binary实例的data属性包含该对象的 Pickled 版本,因此必须先对其进行 Pickled 才能使用它。这导致另一个对象(具有新的id值)。

$ python3 xmlrpc_Binary_pickle.py

Local: 4327262304
MyObj(1, 'b goes here')

As object:
["{'a': 1, 'b': 'b goes here'}", "<class 'dict'>",
{'a': 1, 'b': 'b goes here'}]

From pickle: 4327262472
MyObj(1, 'b goes here')

异常处理

由于 XML-RPC 服务器可以用任何语言编写,因此无法直接传输异常类。相反,服务器中引发的异常将转换为Fault对象,并在客户端本地作为异常引发。

xmlrpc_exception.py

import xmlrpc.client

server = xmlrpc.client.ServerProxy('http://localhost:9000')
try:
    server.raises_exception('A message')
except Exception as err:
    print('Fault code:', err.faultCode)
    print('Message   :', err.faultString)

原始错误消息保存在faultString属性中,并且faultCode设置为 XML-RPC 错误号。

$ python3 xmlrpc_exception.py

Fault code: 1
Message   : <class 'RuntimeError'>:A message

将呼叫合并为一条消息

多重调用是 XML-RPC 协议的扩展,它允许同时发送多个调用,并收集响应并将其返回给调用者。

xmlrpc_MultiCall.py

import xmlrpc.client

server = xmlrpc.client.ServerProxy('http://localhost:9000')

multicall = xmlrpc.client.MultiCall(server)
multicall.ping()
multicall.show_type(1)
multicall.show_type('string')

for i, r in enumerate(multicall()):
    print(i, r)

要使用MultiCall实例,请像使用ServerProxy一样调用其上的方法,然后调用不带参数的对象以实际运行远程功能。返回值是一个迭代器,它从所有调用中产生结果。

$ python3 xmlrpc_MultiCall.py

0 True
1 ['1', "<class 'int'>", 1]
2 ['string', "<class 'str'>", 'string']

如果其中一个调用导致Fault,则当从迭代器生成结果并且没有更多结果可用时,将引发异常。

xmlrpc_MultiCall_exception.py

import xmlrpc.client

server = xmlrpc.client.ServerProxy('http://localhost:9000')

multicall = xmlrpc.client.MultiCall(server)
multicall.ping()
multicall.show_type(1)
multicall.raises_exception('Next to last call stops execution')
multicall.show_type('string')

try:
    for i, r in enumerate(multicall()):
        print(i, r)
except xmlrpc.client.Fault as err:
    print('ERROR:', err)

由于来自raises_exception()的第三个响应会生成异常,因此无法访问来自show_type()的响应。

$ python3 xmlrpc_MultiCall_exception.py

0 True
1 ['1', "<class 'int'>", 1]
ERROR: <Fault 1: "<class 'RuntimeError'>:Next to last call stops execution">

也可以看看

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

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


暂无话题~