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>