Когда я стал использовать Common Lisp для разработки web-приложений, то больше всего меня расстраивала система диспетчеризации запросов web-сервер Hunchentoot, точнее фактическое её отсутствие (а входящий в поставку define-easy-handler просто ужасен). Для решения данной проблемы я решил написать cl-routes: реализацию системы маршрутов для Common Lisp, но без привязки к модели MVC. Первоначально я планировал портировать Python-реализацию этой идеи на Common Lisp и принялся изучать её исходный код. Первое, что меня смутило - размер исходного кода, слишком много букв... А второе меня так просто добило: все маршруты транслируются в регулярные выражения и при обработке запроса идёт простой перебор всех выражений до первого совпадения. Брр... Фактически, системы маршрутизации RoR и Django работают точно так же, поиск ведётся прямым перебором. В документации к Django написано про производительность буквально следующее:
Each regular expression in a urlpatterns is compiled the first time it's accessed. This makes the system blazingly fast.ага, как же, если количество маршрутов будет исчисляться сотнями или даже тысячами (что вполне реалистично для больших приложений), то такая реализация может легко стать главным тормозом всего приложения. И между прочим эта проблема достаточно хорошо известна в RoR, где предлагаются различные пути для увеличения производительности готовых приложений за счёт отказа от многих преимуществ маршрутов и увеличения нагрузки на разработчиков. В Django, в общем, есть кое-какие пути для увеличения производительности (за счёт задания префиксов на группы) (я правда не знаю, повышает ли это реально производительность или нет, но принципиально может), но это опять таки ведёт к увеличении нагрузки на программиста и не всегда может быть применено.
В общем, к существующим системам у меня есть две основные претензии:
- Использование универсального и тяжеловесного механизма регулярных выражений, который просто не нужен для разбора url
- Поиск в линейном списке (с некоторыми оговорками для Python-систем) - ведь структура сайта обычно естественным образом отображается в виде дерева, а значит возможен и более эффективен (чем больше сайт, тем более эффективен) поиск в дереве
Кроме того, что в RoR, что в Django для внесения изменений в схему диспетчеризации необходимо внести изменения в несколько файлов, я же предпочитаю держать определение схемы url и её обработчик в одном месте, не размазывая логику по разным частям программы. В RESTAS задать новый маршрут можно так:
(restas:define-route article ("articles/:author/:item"Т.е. в одном месте задаётся и шаблон url, и обработчик, при этом в теле обработчик сразу же доступны переменные, заданные в шаблоне url. Вложенный обработчик, по-умолчанию, может вернуть строку или "octets array", либо целое число (которое интерпретируется как код статуса ответа), либо pathname (в этом случае вызывается hunchentoot:handle-static-file, которая, в отличие от систем на базе Ruby или Python работает достаточно быстро и реальной необходимости в дополнительных серверах для "статики", таких как ngix, просто нет). Также, легко можно добавить поддержку и обработку любых типов возвращаемых объектов.
:method :get
:content-type "text/plain")
(format nil
"Author: ~A~Article: ~A"
author
item))
При определении маршрута через define-route необходимо указать символ (в приведённом примере - 'article), который будет являться его именем. Во-первых, это даёт возможность в любой момент переопределить маршрут (в том числе и шаблон url, лишь бы имя оставалось неизменным) простой отправкой кода в REPL (например, с помощью M-C-x в SLIME). Во-вторых, это позволяет использовать данный символ для генерации url:
(restas:genurl 'articleили для перенаправления:
:author "archimag"
:item "introduction-in-routes")
(restas:redirect 'article(возможны и другие использования)
:author "archimag"
:item "introduction-in-routes")
В Django шаблон url задаётся регулярным выражением и возможности сопоставить одному и тому же регулярному выражению несколько различных обработчиков (например, для различных типов запроса: get, post и т.п.) нет, в cl-routes маршрут это объект класса (либо производный от него) routes:route, для которого можно переопределить метод route-check-conditions, что позволяет проводить диспетчеризацию не только на основе шаблона url. Это используется в RESTAS и позволяет одному и тому же шаблону url сопоставить несколько обработчиков, например, для разных типов (get, post и т.п.) запросов (что позволяет сократить количество if-лапши в коде). В ближайшее время (на днях, может даже сегодня) я добавлю в RESTAS возможность проводить произвольные проверки во время поиска подходящего обработчика: например сейчас у меня есть необходимость в разных обработчиках для одного и того же url в зависимости от роли пользователя в системе.
Вот несколько возможных шаблонов url:
/forum/:chapter/:topic/:messageИли с объединением строк:
/forum/archives/:year/:month/:day
/forum/:chapter/rss
/book/:(name)-:(chapter).htmlТакже поддерживаются wildcard-параметры (с тем ограничением, что такой параметр может использоваться в шаблоне только один раз):
/:(feed).:(format)
/mydoc/*items
/*path/rss
А что если хочется дату ввиде YYYY-MM-DD или там чтобы :item был только цифровой. Руками в обработчике проверять?
ОтветитьУдалитьКстати, когда url много, имхо все-таки удобнее, когда они расположены рядом. В django при желании можно сделать так чтобы url был рядом с обработчиком - погугли snippet @url decorator. А вот в restas получается что отделить routes от обработчиков нельзя.
з.ы. Почему ни вкопировать нельзя ни стрелки не работают в поле комментария??? (firefox 3.0, ubuntu)
уппс.. не дочитал пост :)
ОтветитьУдалить> Руками в обработчике проверять?
ОтветитьУдалитьКонкретно этот момент (специальная поддержка для ограничений значений и типов переменных) будет реализован достаточно скоро, cl-routes это позволяет, я просто ещё не решил как лучше интегрировать это в define-route. Мало того, если будет заявленно, что переменная имеет тип integer, то помимо проверки, в код обработчика будет передаваться именно integer, а не просто строка.
> А вот в restas получается что отделить routes
> от обработчиков нельзя.
Можно, простейший способ - в теле define-route просто вызывать фукнцию, которую можно расположить где угодно.
Кроме того, cl-routes позволяет и более сложное использование, ибо маршрут - это объект класса route, от него можно унаследовать, переопределить generic-функции, ну много чего сделать интересного :) Просто это несколько более сложная тема, а простейший сейчас вариант - это использовать define-route
> Почему ни вкопировать нельзя ни стрелки
Не знаю, у меня на рабочей машине работает, а вот дома нет, я не разбирался...
> Кроме того, что в RoR, что в Django для внесения изменений в схему диспетчеризации необходимо внести изменения в несколько файлов
ОтветитьУдалитьНу ладно, может в RoR и так, но в джанго-то исправляем urls.py и всё. Какие несколько файлов?
> но в джанго-то исправляем urls.py и всё
ОтветитьУдалитьЕсли просто шаблон url поправить, то и в RoR, конечно, можно в одном файле всё изменить. А я имею ввиду необходимость размещения обработчика в одном месте, а определения маршрута, который он будет обрабатывать, в другом.