Personal tools
You are here: Home Статьи Разные мелкие заметки Где соединяется Zope3 & Zodb
Document Actions

Где соединяется Zope3 & Zodb

by Егор Шершнев, Андрей Орлов last modified 2007-10-18 12:32

Статья дает ответ на вопрос, вынесенный в заголовок, а кроме того, является неплохим тренингом по использованию отладчика Zope и пониманию исходного кода.

Где соединяется Zope3 & Zodb:

Введение:

Так вышло, что однажды, в беседе с коллегой я употребил словосочетание "корень ZODB", на что услышал реплику о том, что у ZODB нет корня, точнее, он есть, но это вовсе не то, что я так называю. В рамках данной статьи попытаемся немножко разобраться в том, что же такое ZODB, где её искать и попытаемся прояснить некоторые вещи, знание которых будет полезным при изучении рассматриваемых вопросов.

Подготовка:

Прежде чем искать корень Zope :), надо подготовить то, что доступно под этим названием через WEB-интерефейс, чтобы его можно было легко опознать. Запустите Zope, обратитесь к нему из браузера примерно так:

            http://localhost80/@@contents.html

(дальнейшие исследования требуют, чтобы Zope был установлен на локальной машине, иначе проводить их придется с местным админом, который никогда не рад творить что-то со своим любимым сервером, пока тот работает).

По указанному адресу находится то, что принято называть "корневой папкой Zope", она-то и будет искаться в дальнейшем. Отметим корневую папку, создав в ней 2 объекта Folder ("Папка") с именами myfolder1 и myfolder2.

Запуск zope в отладочном режиме:

Первое, что нам понадобится для поиска ZODB и созданных нами в корневой папке Zope объектов, это запустить Zope в отладочном режиме. Всё, что для этого нужно - вместо привычного файла runzope (runzope.bat) воспользоваться специально предназначенным для запуска в соответствующем режиме файлом debugzope (для пользователей Linux) или debugzope.bat (для пользователей Windows) расположенным в папке bin той инстанции Zope, которую вы хотите исследовать.

Если всё настроено хорошо и проблем с запуском не возникло, вместо привычных надписей примерно следующего вида:

            ------
            2007-10-16T16:42:35 INFO root -- HTTP:localhost:8180 Server started.
                    Hostname: localhost
                    Port: 8080
            ------
            2007-10-16T16:42:35 INFO root -- FTP:localhost:8121 Server started.
                    Hostname: localhost
                    Port: 8021
            ------
            2007-10-16T16:42:35 INFO root Startup time: 3.785 sec real, 3.690 sec CPU

вы увидите на экране нечто следующее:

            >>>

Обратите внимание, что перед сообщением интерпретатора не должно быть никаких сообщений об ошибках, так как прглашение (>>>) появляется и в том случае, если запустить Zope по каким-то причинам не удалось.

Для того, чтобы убедиться в том, что всё прошло успешно и Zope запущен в отладочном режиме, введите в запустившемся интерпретаторе app и нажмите Enter. В результате у вас на экране должно появиться нечто такое:

            >>> app
            <zope.app.debug.debug.Debugger object at 0xb796616c>
            >>>

Объект zope.app.debug.debug.Debugger есть ни что иное как отладочный объект Zope, от которого и начнётся наше погружение в некоторые его глубины.

Отметим, что все описываемые процедуры должны выполняться в запущенном интерпретаторе, потому что при запуске Zope в отладочном режиме доступ к нему из браузера невозможен.

В поисках корневого объекта Zope:

Итак, мы в интерпретаторе, команда app выдаёт указанный выше результат. Погрузимся на следующую глубину, набрав в интерпретаторе app.db. В результате вы должны увидет нечто примерно следующее:

            >>> app.db
            <ZODB.DB.DB object at 0xb7097a4c>
            >>>

Как можно догадаться, это есть всеми любимая база данных Zope, больше известная как ZODB. У этой базы данный и вправду есть корень. Попробуем выяснить, что же он из себя представляет выполнив команду app.db.open().root(), в результате чего мы увидим нечто следующее:

            {'Application': <zope.app.folder.folder.Folder object at 0x8c6fc6c>,
             'zope.app.generations': <persistent.dict.PersistentDict object at 0x8c6fdac>}

Как вы можете видеть, это словарь, который плюс ко всему прочему как раз и является корнем (root) ZODB. Теперь пойдём ещё дальше и посмотрим что же представляет из себя объект этого словаря с ключом Application. Это видно и из предыдущего примера, но мы акцентируем внимание на главном, выполним в интерпретаторе строку app.db.open().root()['Application'], получив в результате следующее:

            >>> app.db.open().root()['Application']
            <zope.app.folder.folder.Folder object at 0x8c8232c>
            >>>

Как можно увидеть, полученный объект - обычная папка (Folder). Вот именно он и есть тот самый корневой объект, в котором мы заблаговременно создавали 2 папки с заявленными ранее именами и которые хотели использовать для идентификации. Чтобы убедиться, что это они и есть, выполним в интерпретаторе команду list(app.db.open().root()['Application'].keys()), в результате чего получим следующий результат:

            >>> list(app.db.open().root()['Application'].keys())
            [u'myfolder1', u'myfolder2']
            >>>  

Вот это и есть так называемый Application, корневой объект сервера приложений или просто прикладной объект. В следующих разделах будет показана инициализация и использование этого объекта в запросах.

Как открывается zodb при старте:

Старт Zope и открытие Zodb управляются несколькими файлами на zcml. Попробуем проследить как открывается zodb.

В файле zope.conf нетрудно найти тег zodb, описывающий открытие базы данных. Финальное определение этого тега находится в модуле ZODB. В частности, в файле ZODB/component.xml определяется синтаксис и параметры всех баз данных, возможных для открытия, а в ZODB/config.py для каждой базы данных определена небольшая фабрика, предоставляющая функцию открытия базы open().

При запуске Zope, управление передается в функцию zope.app.appsetup.appsetup.multidatabase, на вход которой поступает список фабрик, упомянутый в предыдущем абзаце. Все базы данных открываются, объединяются в единый кластер, который передается головному модулю, выполняющему две операции:

  1. создание сервера обслуживающего запросы, в каждый из которых передается база данных как параметр;
  2. порождение события zope.app.appsetup.interfaces.DatabaseOpened, вместе с которым обработчику события передается только что открытая база данных.

Создание сервера обслуживающего запросы мы рассмотрим как-нибудь в другой раз :), а вот обработчик события, который находится в zope.app.appsetup.bootstrap.bootStrapSubscriber, выполняет важное дело: создает прикладной объект zope в ZODB, если его там нет.

Что такое траверс:

Траверсом называют последовательный обход вершин дерева объектов в Zope, происходящий, обычно, в процессе обрабоки запроса, содержащего URL (или путь к объекту). При этом последовательность обхода является однозначным отображением пути запроса на все множество последовательностей обхода в Zope. Результатом траверса является запрос конкретного объекта и его метода, который будет использован для того, чтобы обработать запрос.

Естественно, обход дерева начинается "от корня дерева", который является прикладным объектом Zope3 до которого мы, помнится, погружались.

Как происходит первый шаг траверса:

Обработку запроса к Zope3 здесь описывать подробно не будем, так как удовлетворительное описание этого можно найти в другой литературе [1]. Цель этого описания - дать понимание того, где находится точка стыковки Zope3 и дерева объектов. При обработке запроса использование этой точки стыковки начинается в объекте, носящем забавное название "Публикатор" (ZopePublicator), который порожден от PublicationTraverse. Необходимый код можно найти в zope/app/publication/zopepublication.py, где в строке 69 начинается определение класса ZopePublicator. Рассмотрим несколько фрагментов этого кода.

Инициализация :

Обратите внимание на две константы, определенные как переменные класса:

                class ZopePublication(PublicationTraverse): 
                    version_cookie = 'Zope-Version'
                    root_name = 'Application'

Значения этих констант имеют очень важный смысл для всей работы публикатора:

version_cookie (Zope-Version)
Хранит название кука, в котором хранится версия объектов, которые будут задействованы в текущей транзакции. Не будем описывать подробно, что это, так как эта возможность в текущей версии Zope3 явно не поддерживается (хотя, наверно, работает);
root_name (Application)
Хранит название корневой папки zope3, видимой при траверсе - т.е. того самого "Корневого объекта приложения".

Чтобы получить "корневой объект приложения" используется функция "getApplication".

Инициализация ZopePublicator происходит в функции __init__ (стр.76) и включает в себя получение базы данных ZODB, которая передается сервером во время инициализации приложения :

                def __init__(self, db):
                    # db is a ZODB.DB.DB object.
                    self.db = db

Именно эта база данных была получена при конфигурации так, как было описано в предыдущем разделе, и именно она будет использована для получения корневого объекта.

Траверс корневого объекта приложения :

Рассмотрим функцию getApplication (строка 134). Она-то и выполняет в некотором роде первый шаг траверса - получение головного объекта. Как сейчас станет ясно, головной объект похож на что-то типа UnionFS - т.е. это несколько (две) иерархии наложенных одна на другую, причем только одна из них берется из базы данных, а вторая генерируется "на лету".

Итак, начнем со второй иерархии. В строке 139 проверяется, не равно ли имя, запрашиваемое из корневого объекта приложения '++etc++process':

                stack = request.getTraversalStack()

                if '++etc++process' in stack:
                    return applicationControllerRoot

Если имя совпало - возвращается корень приложения "специального вида" - объект applicationControllerRoot, который позволяет выполнять различные операции по управлению приложением (обратите внимание: такая тривиальная обработка ++etc++process - нетипичный случай обработки пространства имен, так как обычно все значительно более соответствует компонентной модели).

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

                version = request.get(self.version_cookie, '')
                conn = self.db.open(version)

Далее прописывается соединение в специальный слот запроса, который вызовет закрытие соединения при закрытии запроса:

                cleanup = Cleanup(conn.close)
                request.hold(cleanup)

Получаем корневой объект ZODB:

                root = conn.root()

И из него по известному нам имени (см. выше) вытаскиваем корневой объект приложения Zope:

                app = root.get(self.root_name, None)

А если его вдруг не оказывается, то испускаем исключение:

                if app is None:
                    raise SystemError("Zope Application Not Found")

Ну и наконец, последний штрих: обертываем прикладной объект, взятый из ZODB в специальную обертку, чтобы его можно было использовать как прикладной объект Zope и полученного монстра возвращаем :

                return ProxyFactory(app)

Немного про ProxyFactory
это такая штука, которая обертывает объект в специальный прокси, который будет отслеживать ограничения безопасности при обращении к нему.

Теперь стало ясно, что то, что называется корневым объектом Zope, в действительности может иметь самую различную природу:

                1. каким-то образом выбранным объектом ZODB;

                2. просто так созданным объектом;

                3. сложной комбинацией различных объектов;

Следовательно, ставить знак равенства между корнем Zope и корнем Zodb (и вообще каким-либо объектом Zodb) по крайней мере поспешно.

Заключение:

Как видно из статьи, не всё в Zope устроено так просто и очевидно, как могло показаться непытливым умам. Хочется верить, что данная статья пролила немного света на глубины Zope, научила пользоваться отладчиком и исходным кодом и заложила достаточную базу для дальнейших самостоятельных исследований в этой области.


Powered by Plone CMS, the Open Source Content Management System