Как я познакомился с Mojolicious

21 июня 2011

Mojolicious (произносится «моджолишес») — это новый и активно развивающийся веб-фреймворк, написанный на Perl. И написал его не абы кто, а Sebastian Riedel, который является одним из разработчиков Catalyst. В последнее время Mojolicious стал темой активного обсуждения в сообществе Perl-программистов, так что я просто не мог его не попробовать.

1. Что пишут о Mojolicious?

Если вбить в гугле слово Mojolicious, можно найти следующие его обзоры:

Почитав все это, я так и не понял самого главного — чем же так хорош Mojolicious, что все о нем говорят. Действительно он мне нужен, или это просто чьи-то заморочки на тему идеальной архитектуры приложения, совершенного кода и тп? И в конце концов любопытство взяло надо мной вверх.

2. Установка и настройка

Mojolicious можно поставить из CPAN или, если вы пользуетесь FreeBSD, из портов. Кстати, во FreeBSD теперь стандартным интерпретатором Perl стал 5.12, ибо 5.10 больше не поддерживается. Не забудьте обновиться!

pkg_upgrade -o perl-5.12.3 perl-5.10.1_3
pkg_add -r p5-Mojolicious

Что приятно, фреймворк не тянет за собой никаких зависимостей. Catalyst, как я слышал, тянет за собой Moose, а это очень много пакетов. Далее я создал свой первый проект:

mkdir -p ~/projects/mojolicious/test_app/
cd ~/projects/mojolicious/test_app/
mojo generate app TestApp

Появилась следующая структура каталогов:

lib - здесь хранится весь код
log - сюда пишутся логи
public - статические файлы (css, js, картинки)
script - ядро Моджолишеса
t - все тесты
tempaltes - шаблоны страниц

Также было создано немного кода, который сразу можно проверить в действии. Наше приложение может работать под Apache или Nginx, но во время отладки удобнее всего использовать встроенный веб-сервер Моджолишеса:

./test_app/script/test_app daemon --restart

По умолчанию сервер слушает 3000-ый порт.

Если хотите знать мое мнение, встроенный веб-сервер — это охренительно. Больше не нужно извращаться, поднимая локальный Apache. Или заниматься написанием кода в VIM через SSH-соединение. Нет, VIM+SSH — это тоже хорошо. До тех пор, пока с интернетом все в порядке. Ну или пока требуется делать только небольшие правки.

У меня со встроенным веб-сервером возникла только одна проблема. Под Виндой (Windows 7 + Strawberry Perl) ключ --restart почему-то не работает. В результате, сервер приходится перезапускать вручную после внесения изменений в код.

Дополнение: В новых версиях Mojolicious убрали ключик --restart. Вместо него предлагается использовать сервер morbo:

morbo script/test_app

Как использовать Mojolicious в режиме CGI, FastCGI и PSGI, можно прочитать в Mojolicious::Guides::Cookbook. Все достаточно просто. Например, прикручивание к Apache в режиме CGI осуществляется одной строчкой в httpd.conf:

ScriptAlias /test_app "/path/to/script/test_app"

Затем говорим:

apachectl restart

… и заходим на http://example.ru/test_app/. Лично я в первый раз получил Internal Server Error. Просмотр /var/log/httpd-error.log подтвердил мою догадку:

[Thu Jun 09 10:49:42 2011] [error] [client 192.168.100.50]
  (13)Permission denied: exec of '/usr/local/www/apache22/
  data/test_app/script/test_app' failed

…, а команды …

cd /usr/local/www/apache22/data/
chown -R eax:www ./test_app
chmod -R g+rx ./test_app

… исправили ситуацию. Вроде, все работает. Однако если заглянуть в логи приложения, оказывается, что кое-что работает не совсем правильно:

Fri Jun 10 10:50:49 2011 debug Mojolicious:34 [27390]: Your secret
  passphrase needs to be changed!!!

Избавиться от этих сообщений очень просто — достаточно дописать в ./test_app/lib/TestApp.pm следующее:

# Этот метод вызывается однажды при запуске сервера
sub startup {
  my $self = shift;

  # ВОТ, ЧТО ДОПИСЫВАЕМ:
  # Secret passphrase можно получить с помощью pwgen -s 16
  $self->secret('*pOpRTm;M<;5?fk{');

  # ...
}

3. Роуты и обработка аргументов

Немного покурив документацию, я более-менее понял общий принцип работы фреймворка. Например, чтобы добавить на наш сайт новую страницу, нужно дописать в метод TestApp::startup() следующее:

  # для страницы /example-page/, запрашиваемую методом GET:
  $r->route('/example-page/')->via('get')->to('example-page#index');

  # для той же страницы, запрашиваемой методом POST:
  #   ->via('post')->to('example-page#result');

  # если метод не важен:
  # $r->route('/example-page/')->to('example-page#result');

Разумеется, можно делать не только статические страницы, но и ЧПУ в стиле post12345.html, используя части адреса в качестве аргументов скрипта. Подробности о роутах можно прочитать здесь.

Далее создаем test_app/lib/TestApp/Example/Page.pm:

package TestApp::Example::Page;
use Mojo::Base 'Mojolicious::Controller';

# выводим example-page
sub index {
  my ($self) = @_;

  # мне очень хотелось написать о возможности вести логи,
  # но не знал, куда запихнуть эту одну строчку кода :)
  $self->app->log->debug("rendering /example-page/ ....");

  $self->render(var1 => '123', var2 => '456');
}

Как вы могли заметить, тут имеет место связь с роутами:

$r->route('/имя-страницы/')->to('имя-класса#метод');

Имена страниц при этом, разумеется, могут быть любыми. Если нужно получить переданные пользователем аргументы, это делается так:

# получить имена всех параметров:
my @names = $self->param;

# получить параметр по его имени:
my $value = $self->param("name");

# получить параметр, переданный методом GET:
my $value = $self->req->query_params->param("name");

# получить параметр, переданный методом POST:
my $value = $self->req->body_params->param("name");

Подробности относительно обработки запросов можно посмотреть здесь.

4. Готовим шаблоны

Страница уже почти готова, осталось только сделать для нее шаблон. Для этого создаем файл templates/example/page/index.html.ep:

% layout 'default';
% title 'Превееед!';

<p>Это - тестовая страница.
  Переменная var1 = <%= $var1 %>, а var2 = <%= $var2 %>.
  Перейти <a href="<%= url_for '/' %>">на главную</a>.</p>

Первая строчка определяет лейаут (см templates/layouts/). Далее идет определение заголовка страницы и собственно код страницы. Как видите, в имени шаблона присутствует имя класса и его метода. «Html» в названии шаблона как бы намекает на формат, в котором мы отдаем данные. В том же каталоге можно создать файл index.json.ep или index.xml.ep для вывода данных в соответствующих форматах.

Расширение файла .ep означает «Embedded Perl». То есть, в шаблоне на самом деле идет Perl-код вперемешку с HTML, как это часто делается в PHP. В первых двух строчках, к примеру, происходит обычный вызов функций layout() и title(). Подробнее о шаблонах Mojolicious можно прочитать здесь .

5. Пишем тесты

Далее я написал тест для только что созданной страницы. Это не обязательно, но сильно упрощает жизнь, когда из заказчика ВНЕЗАПНО начинает переть креатив. Тесты позволяют убедиться, что при изменении сайта ничего не отвалилось. И да, я в курсе, что TDD подразумевает написание тестов перед написанием кода, но я же сейчас в фреймворке разбираюсь, а не работаю над реальным проектом. Итак, файл t/example-page.t:

#!/usr/bin/perl

use strict;
use Test::More;
use Test::Mojo;

use_ok 'TestApp';

my $t = Test::Mojo->new(app => 'TestApp');
$t->get_ok('/example-page/');
$t->status_is(200);
$t->content_type_is('text/html;charset=UTF-8');
$t->content_like(qr#<a href="#i);

done_testing();

Обратили внимание, что тут автоматически тестируется пользовательский интерфейс? Запускаем тест:

./script/test_app test

Если все сделано правильно, будут запущены все имеющиеся тесты (скрипты test_app/t/*.t). При успешном их прохождении будет выведено сообщение «Result: PASS». Конечно, тестировать можно не только страницы, но и любые классы и функции. Подробнее о Test::More и Test::Mojo можно прочитать на CPAN.

6. Так что же дает Mojolicious?

Я вижу следующие преимущества от использования Mojolicious:

  • Хотим — используем CGI, хотим — FastCGI, хотим — mod_perl. Нравится Apache — прикручиваем к Apache. Нравится nginx — прикручиваем к нему. Не нравится ни тот, ни другой — используем собственный веб-сервер Моджолишеса;
  • Как вы могли заметить, Mojolicious является MVC фреймворком, а это сейчас модно. То есть, он позволяет нам отделить данные от логики;
  • Безопасность. Все, что выводится в шаблонах, если обратное не указано явно, подвергается замене символов в стиле PHP-функции htmlspecialchars(), так что о вездесущих XSS-уязвимостях можно забыть. Все кукисы подписываются с помощью HMAC-MD5 (для чего, собственно, и нужен passphrase). Так что в кукисах хранится только то, что мы сами в них запишем;
  • Не нужно писать свои велосипеды с mod_rewrite;
  • Шаблонизатор из коробки;
  • Модульное тестирование из коробки;
  • Быстрое получение прототипов с помощью Mojolicious::Lite;
  • Вменяемые сообщения об ошибках в окне браузера и логах;
  • Можно сделать свое веб-приложение устанавливаемым;
  • Легкость;
  • Гибкость, обеспечиваемая stash’ем (глобальным хранилищем), плагинами, хуками и хелперами. Хелперы — это небольшие функции типа url_for, которую вы могли заметить в приведенном ранее шаблоне;
  • Хорошая документация, активное сообщество программистов.

По поводу плагинов. Их много. Особенно интересными, на мой взгляд, являются Session, Database, Mail, Config, I18n, Cache, CSRFProtect, JSUrlFor и ContentManagement.

7. Дополнительная инфа

В качестве источников дополнительной информации, помимо постов, перечисленных в начале этой заметки, я бы рекомендовал следующие:

Ну или спрашивайте меня прямо в комментариях к этой заметке. Не могу назвать себя большим знатоком Mojolicious’а, но постараюсь помочь, чем смогу.

Дополнение: Обратите также внимание на заметку Мой первый опыт использования MongoDB. Упомянутая в ней сокращалка ссылок написана на Mojolicious.

Метки: .


Вы можете прислать свой комментарий мне на почту, или воспользоваться комментариями в Telegram-группе.