Автор оригинала: Doug Hellmann.
Цель:
Клиентская библиотека для обмена данными 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),
logRequestsTrue,
allow_noneTrue)
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 поддерживаются из коробки, оба с базовой аутентификацией. Для реализации нового канала связи нужен только новый транспортный класс. Это может быть интересное упражнение, например, для реализации XML-RPC через SMTP.
xmlrpc_ServerProxy_verbose.py
import xmlrpc.client
server xmlrpc.client.ServerProxy('http://localhost:9000',
verboseTrue)
print('Ping:', server.ping())Параметр verbose предоставляет отладочную информацию, полезную для устранения ошибок связи.
$ python3 xmlrpc_ServerProxy_verbose.py send: b'POST /RPC2 HTTP/1.1\r\nHost: localhost:9000\r\n Accept-Encoding: gzip\r\nContent-Type: text/xml\r\n User-Agent: Python-xmlrpc/3.5\r\nContent-Length: 98\r\n\r\n' send: b"\n\n \n" reply: 'HTTP/1.0 200 OK\r\n' header: Server header: Date header: Content-type header: Content-length body: b"\nping \n\n \n\n \n" Ping: True\n\n \n\n \n 1
Кодировку по умолчанию можно изменить с 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_noneFalse)
try:
server.show_type(None)
except TypeError as err:
print('ERROR:', err)
server xmlrpc.client.ServerProxy('http://localhost:9000',
allow_noneTrue)
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', "", 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
True
integer : 1
1
float : 2.5
2.5
string : some text
some text
datetime : 20160618T19:31:47
20160618T19:31:47
array : ['a', 'list']
['a', 'list']
array : ['a', 'tuple']
['a', 'tuple']
structure : {'a': 'dictionary'}
{'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, width40)
print('\nAfter:')
pprint.pprint(server.show_type(arg)[-1], width40)Эта программа передает список словарей, содержащих все поддерживаемые типы, на примерный сервер, который возвращает данные. Кортежи преобразуются в списки, а экземпляры 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': ,
'floating-point number': 2.5,
'integer': 0,
'string': 'some text',
'structure': {'a': 'dictionary'}},
{'array': ['a', 'tuple'],
'boolean': True,
'datetime': ,
'floating-point number': 2.5,
'integer': 1,
'string': 'some text',
'structure': {'a': 'dictionary'}},
{'array': ['a', 'tuple'],
'boolean': True,
'datetime': ,
'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_datetimeTrue)
now server.now()
print('With:', now, type(now), now.__class__.__name__)
server xmlrpc.client.ServerProxy('http://localhost:9000',
use_datetimeFalse)
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:31datetime Without: 20160618T19:18:31 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('\no2 :', o2)
pprint.pprint(server.show_type(o2))Когда значение отправляется обратно клиенту с сервера, результатом является словарь на клиенте, поскольку в значениях ничего не закодировано, чтобы сообщить серверу (или клиенту), что он должен быть создан как часть класса.
$ python3 xmlrpc_types_object.py
o : MyObj(1, 'b goes here')
["{'b': 'b goes here', 'a': 1}", "",
{'a': 1, 'b': 'b goes here'}]
o2 : MyObj(2, MyObj(1, 'b goes here'))
["{'b': {'b': 'b goes here', 'a': 1}, 'a': 2}",
"",
{'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\x00'
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('\nERROR:', err)Если строка, содержащая байт NULL, передается в show_type () , в синтаксическом анализаторе XML возникает исключение при обработке ответа.
$ python3 xmlrpc_Binary.py Local string: b'This is a string with control characters\x00' As binary: b'This is a string with control characters\x00' 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('\nAs 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('\nFrom pickle:', id(o2))
pprint.pprint(o2)Атрибут данных экземпляра Binary содержит маринованную версию объекта, поэтому перед использованием ее необходимо распаковать. Это приводит к другому объекту (с новым значением id).
$ python3 xmlrpc_Binary_pickle.py
Local: 4327262304
MyObj(1, 'b goes here')
As object:
["{'a': 1, 'b': 'b goes here'}", "",
{'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 ::A message
Объединение вызовов в одно сообщение
Multicall – это расширение протокола 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', "", 1] 2 ['string', " ", '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', "", 1] ERROR: :Next to last call stops execution">
Смотрите также
- стандартная библиотечная документация для xmlrpc.client
- xmlrpc.server – реализация сервера XML-RPC.
- http.server – реализация HTTP-сервера.
- XML-RPC How To – описывает, как использовать XML-RPC для реализации клиентов и серверов. на разных языках.