Рубрики
Без рубрики

xmlrpc.client – Клиентская библиотека для XML-RPC

Автор оригинала: 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
ping\n\n\n\n"
reply: 'HTTP/1.0 200 OK\r\n'
header: Server header: Date header: Content-type header:
Content-length body: b"\n\n
\n\n1\n
\n\n\n"
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_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:31  datetime
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">

Смотрите также