13 мар. 2009 г.

Twisted: Perspective Broker

Введение

Представьте, что у вас есть две программы - которым нужно обмениваться друг-с-другом данными. Вы можете использовать для этого любой протокол - который посчитаете нужным. Но попробуйте подумать - чем же будут обмениваться ваши программы. Возьмите в качестве примера протокол RADIUS. RADIUS-сервер обрабывает полученные UDP-датаграммы и переводит их в свою внутреннюю структуру объектов, после чего выполняет ряд действий и производит обратную трансформацию. В конечном итоге если обе ваших программы написаны с использованием Twisted - вы можете решить задачу обмена данными эффективным средством -Perspective Broker.И при этом - вы будете работать с объектами, а не с их трансформацией - эту задачу Twisted возьмет на себя. Perspective Broker (далее простоPB) строится на следующих центральных концепциях:

  • сеарилизация - выбор объекта некотороего типа, перевод его в бинарный вид, отправка респонденту и восстановление по получению.
  • вызов удаленных процедур - локальный объект выполняет задание необходимое для удаленного респондента. Для этого специально определяются методы доступные для удаленного вызова.

Установка цели

Для того, чтобы что-то понять - нужно что-то делать. И понимание любого инструментального средства - ограничивается тем как мы его используем. Давайте построим небольшую модель и реализуем её с помощьюPB. В качестве сервиса возьмем DHCP-сервер распределяющий клиентам компании-провайдера статические IP-адреса. Логика подсказывает, что в данном случае нужно определять истинность клиента и настраивать его в соответствии с правилами сети. Для конкретной реализации DHCP-сервера понадобились следующие данные и создание следующего объекта:

class IPResultStruct:
ip = None
mac = None
vlan = None
port = None
relay_agent = None
dns = None
submask = None
gateway = None

Вот отсюда и будем плясать.

Вызов удаленных методов

У нас должно быть два приложения - отправляющее и принимающее. Отправляющий сервис производит запросы к базе данных и сообщает изменения DHCP-серверу. Для нашего первого шага - нужно слегка упростить задачу. Вместо конкретного объекта будем пока передавать строку.

from twisted.spread import pb
from twisted.internet import reactor
class Receiver(pb.Root):
def remote_gotObject(self, st):
print 'got object:', st
return True
if __name__ == '__main__':
reactor.listenTCP(8789, pb.PBServerFactory(Receiver()))
reactor.run()

Методы вызываемые удаленно имеют префикс remote_- как их вызывать клиенту - увидим ниже. pb.Root объект передается специализированной фабрике pb.PBServerFactory. Данная фабрика имеет несколько отличий: объект протокола- создается для нового соединения и знает как обмениваться данными по PB протоколу. Объект передаваемый конструктору (Корневой(Root)-объект) - лишь упрощает взаимодействие с клиентом. Клиент запрашивает только описание объекта - и это позволяет создавать вам нужную и безопасную модель взаимодействия.

from twisted.spread import pb
from twisted.internet import reactor
from twisted.python import util
host = "localhost"
someObject = "hello network"
factory = pb.PBClientFactory()
reactor.connectTCP(host, 8789, factory)
d = factory.getRootObject()
d.addCallback(lambda object: object.callRemote("gotObject", someObject))
d.addCallback(lambda answer: 'server answer: '+ str(answer) )
d.addErrback(lambda reason: 'error: '+str(reason.value))
d.addCallback(util.println)
d.addCallback(lambda _: reactor.stop())
reactor.run()

Клиентская сторона использует pb.PBClientFactory для создания соединения на заданные хост и порт. Этот процесс разбивается на два этапа - TCP-соединение и получение корневого объекта - используя метод .getRootObject() - объекта фабрики.

Передача данных по сети всегда сопряжена с временными задержками. Поэтому тип объекта, возвращаемого методом .getRootObject, - Deffered. Если соединение прошло успешно и описание корневого объекта полученно успешно - будут выполняться запросы (Callback) - в противном случае будет выполнен обработчик ошибок (Errback). Впрочем как и всегда - в случае работы с Deffered. Остановимся собственно на удаленном вызове: .

object.callRemote("gotObject", "hello network")

данный метод выполнит обращение к методу remote_gotObject удаленного ссылочного ( Referenceable ) объекта. Если метод его описан как remote_someMethod - соответственно вызывать нужно callRemote("someMethod") и так далее...

Замечание:

Ссылочный объект(Referenceable object)-называется так потому, что непосредственно не копируется к клиенту. Особенности русского языка не могут дать точного термина - это и ссылочный и описываемый объект. Клиент работает с описанием методов объекта и запрашивает у сервера нужные процедуры. В отличии, например, от Копируемого объекта (Copyable object)-который можно скопирывать к клиенту вместе со всеми свойствами и указать какие методы и свойства сохранить, а какие удалить.

На последок - обратите внимение на тип фабрики создаваемый для TCP-сервера (PBServerFactory) и TCP-клиента (PBClientFactory). И при возникновении вопросов не чурайтесь изучить получившийся объект. Данный пример достаточно прост - но он дает важное понимание того - как работает вызов процедур и как передавать объекты между приложениями.

Передача объектов

Итак настал момент передать не просто строку, а некий объект нашему клиенту. Измените определение объекта someobject на следующее (не забыв про определение класса IPResultStruct, разумеется):

someObject = IPResultStruct()
someObject.ip = "127.0.0.1"

Запустите и насладитесь моментом ;-) .

got object: Unpersistable('instance of class pb_simple.IPResultStruct deemed insecure')

Если поиграться с другими объектами - вы быстро выясните - что объекты с базовыми типами данных (списки, словари, кортежи, строки и числа) - передаются посредством PB - без каких-либо проблем. А стоит вам попытаться передать собственный instance как возникает исключение. В чем же дело?

.

А дело в безопасности. PB ограничиваетсериализациюобъектов - можно обмениваться только безопасными объектами. Рассуждения о необходимости и правильности таких поступков стоит искать у авторов данной идеи. Ограничимся тут непосредственно решением.

Копируемые объекты

В PB чтобы передать собственный объект - нужно наследовать его от классаpb.Copyable- это сообщитPB о том, что объект можно копирывать. Создадим небольшую библиотечку прототипов - назовём её pb_simple.py и запишем туда видоизменённое описание класса IPResultStruct:

class IPResultStruct(pb.Copyable):
ip = None
mac = None
vlan = None
port = None
relay_agent = None
dns = None
submask = None
gateway = None
def getCopyableState(self, state):
return self.__dict__.copy()

Однако данное изменение не приведёт ни к чему новому - исключение никуда не исчезнет.

Перемещённый объект

Всё дело в том, что указание на копируемость объекта - еще не достаточный фактор, чтобы его передать. На обратном конце - объект должен быть восстановиться. Для этого и нужно описание перемещённого (Remote) объекта.

class IPResultRemote(pb.RemoteCopy):
def setCopyableState(self, state):
self.__dict__ = state

Методы .getCopyableState и .setCopyableState, как ясно - призванны произвести манипуляции с аттрибутами объекта. Кроме того определение перемещённого (Remote) объекта позволит назначить необходимые методы уже на стороне клиента, т.к. по умолчанию коприуемый объект сераилизуется без методов.

Регистрация сериализации

Чтобы объект можно было скопирывать - нужно предоставить PB информацию о том, как сеарилизовывать копируемый объект:

Теперь проблемы с копирыванием объекта возникнуть не должно.

pb.setUnjellyableForClass(IPResultStruct, IPResultRemote)

Вот теперь всё должно быть в порядке.

Финал

class IPResultStruct(pb.Copyable):
ip = None
mac = None
vlan = None
port = None
relay_agent = None
dns = None
submask = None
gateway = None
def getCopyableState(self, state):
return self.__dict__.copy()
class IPResultRemote(pb.RemoteCopy):
def setCopyableState(self, state):
self.__dict__ = state
pb.setUnjellyableForClass(IPResultStruct, IPResultRemote)

А вот и сообщение нашего сервера - о получении нужного нам объекта.

got object: <pb_simple.ipresultremote>

Комментариев нет: