Автор оригинала: 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 : TrueTrue 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 для реализации клиентов и серверов. на разных языках.