AGPsource Game Platform – как же работает механизм роутинга?
В предвестии выпуска 3.1 версии игровой платформы мы решили чуть больше пролить свет на общую архитектуру и далее попробуем описать весь процесс обработки запроса, описывая каждую стадию. Всего основных шагов 7, не считая выполнение конкретного обработчика действия для этого URL.
1. Перезапись URL на стороне веб-сервера. Запрашиваемый URL обрабатывается веб-сервером (сейчас Apache, потом лучше всего фронт-енд вынести на Nginx). Используя правила для mod_rewrite из корневого файла .htaccess, производится перенаправление запроса на index.php в корне сайта, при этом запрашиваемый URL передается как GET-переменная __route__. Все остальные переменные из запроса сохраняются в неизменном виде.
2. Определение главной страницы входа. На входе в скрипт проверяется существование такого URL и его сравнение с относительным адресом корня сайта (’/'). Если адреса нет, он пустой или указывает на корень сайта, он подменяется на адрес по-умолчанию, обычно это ‘game/index’. В принципе, это можно реализовать и через правила для mod_rewrite, что, видимо, будет сделано в следующих версиях при переходе на Nginx.
3. Подключение основных файлов платформы. Здесь несколько стадий, который выполняются последовательно:
- Подключение ядра платформы ( файл ‘inc/agplatform_core.php’);
- Установка путей для подключения файлов, мы указываем, что все далее подключаемые файлы будут указаны относительно директории /inc/lib, там хранятся все используемые библиотеки проекта.
- Инициализируем основные компоненты Zend Framework – Registry и устанавливаем автозагрузчик классов Zend_Loader.
- Объявляется функция для инициализации отладочного инструмента – FirePHP, который используется для проверки и отладки на всех стадиях. Поэтому эта стадия вынесена из самой платформы, так как только так можно задействовать FirePHP для отладки даже ядра системы и механизма роутинга. В релизной системе можно отключить или даже удалить все отладочные вызовы, что повысит быстродействие и уменьшит трафик.
- Подключаем описание интерфейса для менеджеров сигналов (файл ‘IconnectManager.php’)
- Подключаем модуль системы роутинга (файл ‘agplatform_routing.php’);
- Подключается файл с описанием исключений (файл ‘agplatform_exceptions.php’);
- Подключение всех менеджеров сигналов. Их четыре и они размещаются в корневых директориях, где лежат другие файлы этих модулей. В корне – системный менеджер, отвечающий за сигналы платформы, в директории Admin – менеджер системы управления игрой и админ-панели, в AGPsource – менеджер стандартной библиотеки классов, в Game – специфические для конкретного проекта классы и обработчики.
4. Инициализация системы роутинга. Мы создаем экземпляр класса AGPlatform_Routing, но если быть точным – это синглтон, поэтому мы получаем экземпляр объекта через статический метод getInstance.
- Если объект ещё не был создан, то вызывается его конструктор, который, в свою очередь:
- Вызывает метод _constructRoutesTable для создания полной таблицы роутинга, для чего опрашиваются все подключенные ранее менеджеры сигналов, а результат сводится в единый массив URL, которые обрабатываются игровой платформой.
- Инициализируется внутренний массив параметров $param, который передается во все обработчики сигналов. Пока он пустой.
- Читается стандартный конфигурационный файл /inc/config/game_config.ini, который возвращается как ассоциативный массив и сохраняется в реестре, а также добавляется к массиву обязательных параметров (см. предыдущий шаг).
5. Локализация. Далее системы пытается выяснить, есть ли в запросе или его параметрах указание на язык локализации. Для этого мы пробуем выделить первую часть из адреса, а также проверяем, присутствует ли параметр lang в запросе или в сессионных переменных. Также проверяется, есть ли указанный язык в списке поддерживаемых языков из конфигурационного файла. В случае отсутствия указания языка или его поддержки, используется язык по умолчанию (устанавливается в конфиге). Поиск языка происходит в следующем порядке:
- Проверяется первая часть из запрашиваемого URL (например, вида ru/game/new_user). Если там найден язык, эта часть из URL удаляется и в дальнейшем все обработчики получают URL вида game/new_user, без кода языка.
- Проверяется массив переменных из запроса – $_REQUEST['lang'];
- Проверяется массив сессионных переменных.
- Установленный код языка передается в статичный метод ядра для инициализации локализации – AGPlatform_Core::set_locale, где сохраняется в реестре для использования далее. Следует отметить, что больше никаких действий не производится, поэтому если вы не используете локализацию при обработке какого-либо URL, то падений производительности нет, так как подсистема локализации инициализируется только после явного вызова ассоциированных с ней сигналов.
6. Построение таблицы роутинга. Обработанный URL передается объекту роутинга в метод dispatchURL, который и производит дальнейшую обработку запрашиваемого адреса.
- Запрашиваемый URL предварительно обрабатывается и приводится к стандартному виду (метод prepareURL) – переводится в нижний регистр и убирается начальный слеш, если присутствует. Также может проверяться длина запроса (пока она максимум 4096 символов).
- Если URL не пустой и проверка не вернула false, адрес сохраняется в списке параметров.
- Обрабатываются все переменные из запроса в методе prepareHTTPRequest, в дальнейшем они сохраняются в массиве обязательных переменных под ключом ‘httprequest’.
- Обработка параметров происходит для всех типов запроса, кроме application/x-amf, которые пропускаются (они бинарные и преобразуются внутри AMF сервера).
- Проверяются все имена переменных, максимальная длина названия – 1024.
- Проверяются все значения переменных, максимальный размер – 4096 символов.
- Служебные переменные,имена которых начинаются с символа подчеркивания ‘_’, исключаются из обработки.
- Следует отметить, что приложение всегда может получить доступ к исходным значения параметров через суперглобальный массив $_REQUEST, однако это не рекомендуется без особой надобности.
- Формируется массив сигналов, которые соответствуют указанному URL.
- Вызывается метод getRoutingToURL, который ищет совпадение в массиве адресов и, находя первый адрес, совпадающий с тем, что есть в таблице, возвращает ассоциированый с ним массив сигналов. Обратите внимание, что URL без начального слеша, в нижнем регистре и ищется полное совпадение (то есть, URL вида user/add/1 и user/add это разные адреса, соответственно все параметры и переменные должны передаваться в GET/POST, а не в адресе). В случае отсутствия URL в таблице роутинга, возвращается определенный по умолчанию сигнал или массив сигналов ( default_empty_url_signal)
- Вызывается метод buildFullSignalsTable, который строит полную таблицу сигналов для обработки. Для этого он объединяет в один массив следующие сигналы:
- Обязательные начальные сигналы, которые вызываются ДО определенных для конкретного URL (описаны в переменной класса pre_dispatch_signals);
- Список сигналов для этого URL, найденный методом getRoutingToURL;
- Обязательные заключительные сигналы из таблицы в переменной post_dispatch_signals.
- Окончательный массив сигналов сохраняется в списке обязательных переменных.
- Для всех указанных сигналов производится проверка и подключение сигналов к их обработчикам
- Проверка сигнала состоит в выделении префикса (пространства имен сигналов, строки до первого знака подчеркивания).
- Проверяется, существует ли такое пространство имён
- Проверка всех менеджеров сигналов через их переменную $prefix, которая хранит массив пространств имен для тех сигналов, за которые отвечает этот менеджер. Учтите, что хотя разные менеджеры могут отвечать за одно и то же пространство имен, но механизм проверки проверит лишь первый менеджер из списка. Изменение способа проверки планируется в версии 3.5.
- Вызывается метод signalManager для менеджера, который обслуживает данный сигнал. Внутри менеджера ищется описание обработчика для сигнала и если он найдет, вызывается метод connect механизма роутинга ( метод класса AGPlatform_Routing).
- Для каждого сигнала создается своя таблица обработчиков – их может быть несколько и они вызываются в порядке подключения или согласно приоритету. По умолчанию приоритет 1000. Таблица обработчиков для сигналов хранится в переменной _connecting экземпляра роутера.
7. Обработка таблицы сигналов. Производится обработка окончательной таблицы сигналов. Это финальный момент обработки всего запроса ядром платформы.
- Если не установлен флаг прерывания обработки, текущий сигнал помещается в массив стандартных параметров.
- Генерируется сигнал путем вызова метода emit роутера, которому передается текущий сигнал и массив обязательных параметров.
- В случае генерации исключения в любом месте обработки выставляется флаг остановки обработки сигнала и завершения работы роутера. Стек ошибки и бектрейс пишется в лог и выводится через FirePHP.
- При генерации сигнала, в методе emit проверяется таблица уже обработанных сигналов, а также таблица, где указаны все сигналы, которые могут генерироваться несколько раз. Обычно сигнал может быть сгенерирован только один раз, иначе будут коллизии, если, к примеру, сигнал устанавливает соединение с базой данных. Однако есть определенные сигналы, которые безопасно вызывать несколько раз (хотя и в обычных обработчиках стоят проверки, поэтому это не приведет к фатальным последствиям), поэтому они описываются в отдельной служебной таблице.
- Для проверки, был ли уже обработан сигнал, служит метод isEmited($signal);
- Если сигнал не обрабатывался ранее или разрешен к повторной обработке, проверяется, есть ли подключенные к этому сигналу обработчики. Если нет – производится попытка их подключить. Далее передается вызов в служебный метод _emit().
- Подключенные обработчики сортируются исходя из своего приоритета.
- Проверяется, является ли обработчик вызываемой сущностью – функцией или методом класса. Стандартно обработчики сигналов – это публичные статические методы классов.
- Метод-обработчик вызывается через call_user_func_array, в качестве параметров передаются две переменные – массив обязательных параметров, который формируется роутером и опциональные, которые передаются только конкретному сигналу (в отличие от стандартных параметров, передаваемых во все обработчики).
- Результат выполнения обработчика заносится в специальный массив (emit_results), ключом служит имя сигнала (так как обработчиков может быть несколько, формируется массив).
- В случае ошибки - исключение AGPlatform_Unknown_Connect_Exception.