Интерфейсные драйверы в alterator
На данный момент в alterator поддерживаются интерфейсы Qt и Http. Как же удаётся lookout одновременно работать со столь различными по подходу вариантами? Всё дело в волшебных пузырьках – то есть в драйверах.
Интерфейс драйвера
Каждый драйвер интерфейса предоставляет как минимум следующую информацию:
- О реализации основных виджетов, например button, label, textbox. Делается это через инструкцию declare-widget. Реализация описывается как правило с помощью объектов.
- Определение конструктора интерфейса (например для Qt – это соответствует созданию объекта QApplication), деструктура интерфейса и запуска интерфейса. Делается это через инструкции: declare-application-constructor, declare-application-destructor и declare-application-runner.
- Определение корневого виджета в который погружается любое описание интерфейса – declare-root-widget.
- Определение popup виджета в который погружается описание интерфейса, который мы желаем видеть как всплывающий диалог – declare-popup-widget.
- Определение процедуры, которая выдаёт список языковых предпочтений – это специфично для отдельных видов интерфейса. Инструкция – declare-language-selector.
- Дополнительные определения, например схема работы с tabbox оказалась настолько различной в разных реализациях интерфейсов, что потребовалась ручное определение функций.
Вот небольшой фрагмент описания готового драйвера для http-интферфейса:
Драйвер Qt
Драйвер Qt устроен относительно просто (суммарный объём исходного текста драйвера – чуть больше 120K).
Создание, удаление интерфейса соответствует в точности созданию и удалению
QApplication. Описание виджетов практически напрямую отображаются соответствующие объекты Qt:
QPushButton,
QLabel, и так далее. Корневой виджет – фактически
vbox, а Popup –
QDialog. Запуск интерфейса состоит в получении описания виджета с идентификатором «/», заключению его в popup и запуске диалога. По завершению работы с основным диалогом работа с интерфейсом заканчивается.
Большая часть драйвера описана на C++, остальное – на
Scheme. Мостик между этими языками настолько крошечный что при переходе на другую реализацию схемы менять придётся не много. Связующие функции делатся на две группы:
- Функции управления виджетами:
- dlg-make-виджет – серия функций для создания того или иного виджета. Указатель на qt_widget* хранится в scheme как SMOB.
- widget-set-attr – изменение аттрибута Qt-виджета
- widget-get-attr – получение аттрибута Qt-виджета
- widget-start – запуск диалога
- widget-stop – останов диалога (в принципе эти два метода можно свести к widget-set-attr)
- widget-delete – удаление виджета. Обращаю внимание что применяется отложенное удаление виджетов через deleteLater потому что иногда виджеты могут удалять сами себя, например, при замене содержимого диалога «на лету».
- Функции трансформирования объектов Scheme в объекты C++:
- bool2scm, scm2bool – в bool и обратно.
- str2scm,scm2str – в char* и обратно
- num2str,str2num – в int и обратно.
- symbol2scm,scm2symbol – символы в строки и обратно.
В части C++ построена иерархия объектов, наследников базового типа qt_widget. Каждый наследник является маленькой оболочкой вокруг того или иного виджета qt и в его задачу входит: разбирать входящие запросы на изменение/получение аттрибутов. Перехватывать события qt и передавать их вызов в соответствующий callback на уровне
Scheme. Часть слотов реализована так что их вызов происходит отложенным ибо может происходить изменение объекта «из-под самого себя», чего не могут пережить часть виджетов Qt. Также во избежание проблем с зацикливанием вызов callback'a Scheme происходит только в случае поступления события от пользователя, а не в случае попутного вызова в результате операций set.
В части Scheme построена небольшая иерархия
объектов. Иерархия потребовалась для введения уточнений и более удобной работы с
listbox и
popup.
Предпочитаемые языки определяются согласно переменным среды, то есть фактически точно также как действовал бы обычный gettext.
Драйвер Http
Более сложный драйвер. Состоит из трёх отдельных частей:
* сервер – обеспечивает хранение состояний пользовательских сессий, аутентификацию и обработку callback'ов.
* fcgi-скрипт – мостик между сервером и web-браузером. Принимает Http-запросы, считывает и выставляет
cookies.
* javascript – работает на клиентском браузере, здесь собственно заканчивается alterator и начинается движок интерфейса – *qooxdoo*.
Можно смотреть как на мостик между
Javascript и
Scheme.
Создание/запуск/останов/ – интерфейса состоит в открытии локального сокета, приёмке и обработке сообщений от клиентов.
Протокол общения между клиентом и сервером несколько специфический. Чтобы не писать лишних парсеров, каждый отдаёт данные в таком формате, который был бы наиболее приятен для собеседника. Так fcgi-скрипт пишет для javacript'a на xml. А обратно получает ответ в виде s-выражений.
Содержимое протокола очень простое. От клиента к серверу идут запросы двух типов:
- (alterator-request action “get”) – дай мне содержимое текущей страницы
- (alterator-request action “event” name ... state) – произошло событие такое-то, состояние виджетов которые мог бы «подкрутить» пользователь такое-то.
- (auth-request user password ... ) – хочу получить новую сессию как пользователь такой-то с паролем таким-то
От сервера к клиенту идут ответы, содержащие описания изменения состояния виджетов:
- <command action “new” – создать новый виджет
- ... action “set” – установить параметр виджета в некоторое значение
- ... action “delete” – удалить виджет
- ... action “clear” – очистить окно от всех виджетов. Все остальные команды после данной читать нет смысла. Ответом на неё служит всегда (alterator-request action “get”).
- .... action “forbidden” – отказано в доступе.
Чтобы лучше понять принцип работы, а заодно познакомиться с тем как устроена аутентификация пользователей рассмотрим некий типовой сеанс работы с alterator.
Пользователь открывает в браузере страницу с клиентским кодом
Alterator а
Javacsipt. Последний делает запрос “alterator-request action get”. В ответ приходит – “forbidden”. Тогда у пользователя спрашивается имя и пароль и делается запрос на создание новой сессии “auth-request”. В разультате ответа приходит команда “clear”, а заодно в
cookies возникают правильные идентификаторы пользователя и сессии. Далее следует повторный запрос ""action “get” – и на основе данной последовательности команд даются инструкции
qooxdoo как и что изобразить на экране, вешаются обработчики событий на виджеты, но не на все а только на которые требуется согласно описанию виджета. Предположим, что пользователь нажимает на кнопку, на которую назначено то или иное действие. Возникает запрос к серверу, на нём отрабатывает callback на scheme и если есть какие изменения в состоянии виджетов, то они отсылаются обратно на клиента.
Теперь перенесёмся на мостик между основным кодом alterator и web-сервером. Что там происходит. Получив запрос, выясняется, является ли это запросом типа “auth”, если да, то он отправляется дальше как есть, иначе просматриваются cookies и “alterator-request” заворачивается в “auth” запрос как конверт и отсылается дальше. При обратном движении, “auth” запрос изучается, если надо выставляются cookies, вытаскивается содержимое (обычный ответ), превращается в xml и отправляется клиенту.
Что же происходит на сервере? Когда запрос пройдёт успешно через систему аутентификации дальнейшая его обработка происходит в контексте конкретной пользовательской сессии (созданой заново или уже существующей). В результате работы в исходящей очереди сообщений накапливаются команды на изменение состояние, кои потом благополучно и отсылаются в качестве ответа.
Особый момент – организация запуска дополнительных модальных диалогов. Если в случае Qt интерфейса всё происходит просто и естественно, то в данном случае задача усложняется тем что:
- на клиенской стороне могут быть запрещены сплывающие окна (спасибо рекламе на некоторых уважаемых сайтах). А посему приходится имитировать переход в другой диалог, путём затирания имеющегося и последующим его пересозданием.
- на серверной стороне код может быть заблокирован вызванным модальным диалогом и по окончании работы последнего должен корректно продолжить свою работу.
Первая проблема решается сама собой. Поскольку пользователь в любой момент может нажать кнопку “Reload”, то всё-равно реализована поддержка «пересоздания» имеющегося виджета.
Вторая решается несколько хитрее. С каждой сессией связывается некий поток. При запуске дополнительного диалога, поток исполнения блокируется и вместо него запускается другой, обслуживающий запущенный popup. После завершения работы последнего, связанный с ним поток завершает свою работу, исходный поток разблокируется и продолжает обслуживание клиента. Когда происходит выход из основного диалога – сессия удаляется (клиент видит это как опять возникшее приглашение ввести пароль и имя пользователя). Потоки потоков и основного кода сервера, который принимает запросы из сокета «перестукиваются» через входящую и исходящие очереди сообщений сессии и упорядочивают своё взаимодействие парой condition variables.
Ну и наконец предпочитаемые языки определяются браузером пользователя, в отличие от локальных переменных как было в случае с Qt.
Таким образом драйвер написан частично на
Scheme и частично на
Javascript
Scheme часть представляет собой небольшую иерархию
объектов. Базовый объект в иерархии является простым универсальным хранилищем аттрибутов. Хранение полного набора требуемых аттрибутов на сервере требуется для поддержки функции «пересоздания» и корректной работы callback'ов, написанных на
Scheme.
Javascript часть представляет собой небольшой набор функций-обёрток для транслирования действий в движок
qooxdoo.
Так что и этот драйвер можно считать в общем-то не очень сложным ;) (общий объём кода не сильно превышает 100K).