Про Mojolicious на Хабре уже несколько раз писали. Фреймворк успешно развивается и, на мой взгляд, становится удобнее для быстрой разработки с каждым днем.
Под катом я собрал несколько приемов работы с фреймворком, которые серьезно упрощают жизнь мне и, быть может, будут полезны для кого-то еще.
Среди методов маршрута в Mojolicious находится метод over(). Метод позволяет накладывать условия на маршрут так, что клиент сможет попасть в указанный в маршруте контроллер только удовлетворив условиям. Об этом уже писал powerman. Использовать метод можно, например, так:
Файл AppName.pm
Код самого хэлпера у меня выглядит так (пользователей у меня мало, так что я храню привилегированных прямо в конфиге):
Данный плагин показался мне самым удобным способом реализации аутентификации пользователей в силу простоты своего использования. Для использования плагина достаточно в AppName.pm подключить его как обычный perl-модуль и добавить примерно такой код:
Плагин также экспортирует условие для маршрутов и маршрут выше может быть переписан так:
При этом внутри контроллеров не нужно думать ни о чем: если пользователь не удовлетворил условиям, он просто в эти контроллеры не попадет.
Этот механизм позволяет использовать консольные комманды в приложении.
Главная прелесть заключается в том, что можно добавлять свои команды, которые будут выполняться в контексте приложения и иметь доступ к его внутренностям (см. документацию). Таким образом, например, по крону я синхронизирую хранящиеся локально в приложении данные с внутренними веб-сервисами компании:
Mojolicious предоставляет множество хуков, которые срабатывают при определенных событиях и через которые можно влиять на работу приложения.
Один из них я использую всегда — before_render. Этот хук срабатывает до того, как контроллер передает данные в шаблон и позволяет решить две основные задачи:
Из хука доступны аргументы вызова рендерера, среди которых, в случае если контроллер решит «упасть» и показать ошибку 500, будет анонимный хэш {exception}, содержащий информацию о причинах проблем. Я использую его для того, чтобы «отловить» ошибку, сообщить о ней через API в Redmine и заставить приложение среагировать — исправить проблему, либо все же показать сообщение об ошибке, но свое.
Кстати, в процессе сообщения об ошибке приложение ищет у себя на диске файл templates/exception.production.html.ep — HTML-шаблон страницы ошибки. Если шаблона нет — используется коробочный, что может удивить пользователей.
Поскольку хуку доступно все содержимое контроллера, можно прямо из него собрать необходимые данные о работе контроллера, и передать из через stash() клиенту. Удобно, если фронтэнд ведет себя не так как задумано и встает вопрос соответствуют ли данные (например, json), передаваемые клиенту тому, что должно быть передано.
Код хука добавляется в AppName.pm и может выглядеть так:
Если приложение получается большим — оно может содержать несколько файлов контроллеров и хелперов (подключенных как плагины). По-умолчанию, все файлы Mojo будет искать в AppName/lib, чтобы не плодить множество визуального мусора в одном каталоге, можно разделить файлы по подкаталогам и подключать в AppName.pm, например, так:
Как всегда, в Perl и решениях на нем построенных, существует множество способов решать задачи просто, быстро и красиво. Я не претендую на то, что мои способы самые-самые, но они, как минимум, весьма полезны в работе.
Под катом я собрал несколько приемов работы с фреймворком, которые серьезно упрощают жизнь мне и, быть может, будут полезны для кого-то еще.
over
Среди методов маршрута в Mojolicious находится метод over(). Метод позволяет накладывать условия на маршрут так, что клиент сможет попасть в указанный в маршруте контроллер только удовлетворив условиям. Об этом уже писал powerman. Использовать метод можно, например, так:
Файл AppName.pm
package AppName; use Modern::Perl; use Mojo::Base 'Mojolicious'; use utf8; # This method will run once at server start sub startup { my $self = shift; $self->plugin('AppName::Helpers::Core'); # библиотеку хэлперов можно подключить из внешнего файла как плагин my $r = $self->routes; # объект маршрутизатора # добавление нового условия в маршрутизатор # здесь условие передает поле user_id из данных сессии пользователя хелперу isAdmin, который должен что-то вернуть $r->add_condition( isAdmin => sub { my ($route, $c, $captures, $pattern) = @_; return 1 if $c->isAdmin($c->session->{'user_id'}); return undef; } ); # при GET-запросе /users пользователь попадет в метод users контроллера sd только если isAdmin что-то вернет $r->get('/users')->over(isAdmin => 1)->to('sd#users'); # если хэлпер ничего не вернул - пользователь будет отправлен по этому маршруту, если подобного маршрута не найдется - пользователь получит ошибку 404 $r->any('/(*everything')->to('user#main'); } 1;
Код самого хэлпера у меня выглядит так (пользователей у меня мало, так что я храню привилегированных прямо в конфиге):
package AppName::Helpers::Core; use base 'Mojolicious::Plugin'; use Modern::Perl; sub register { my ($self, $app) = @_; $app->helper( isAdmin => sub { # хэлпер для проверки, входит ли данный юзер в список администраторов # invocation: # $whatever->isAdmin($user_login) # outputs: 1|2, undef my ($self, $user_login) = @_; return 1 if $user_login eq $user foreach (@{$self->config->{PrivilegedUsers}->{Administrators}}); return 2 if $user_login eq $manager foreach (@{$self->config->{PrivilegedUsers}->{Managers}}); return undef; } ); } 1;
Mojolicious::Plugin::Authentication
Данный плагин показался мне самым удобным способом реализации аутентификации пользователей в силу простоты своего использования. Для использования плагина достаточно в AppName.pm подключить его как обычный perl-модуль и добавить примерно такой код:
$self->plugin('authentication', autoload_user => 1, # данный метод отвечает за то, что вернет обращение $c->current_user в контроллере load_user => sub { my $self = shift; my $uid = shift; return { 'id' => $uid, 'name' => $self->session->{'user_id'}, } if $uid; return undef; }, validate_user => sub { # непосредственно аутентифицирует пользователя my $self = shift; my $username = shift || ''; my $password = shift || ''; my $extradata = shift || {}; my $user = $self->APIrequest(...); # в моем случае за хранение пользователей отвечает веб-сервис в интранете, которому приложение передает пару логин-пароль и ожидает получить идентификатор пользователя if (ref($user) eq 'HASH') { $self->error("API internal error while logging in user: ".$username); return undef; } if ($user->[0] !~ m/^Error:/) { $self->session->{'user_id'} = $username; return $user->[0]; } $self->info("Login for user '$username' failed: $user->[0]"); return undef; } );
Плагин также экспортирует условие для маршрутов и маршрут выше может быть переписан так:
$r->get('/users')->over(authenticated => 1)->over(isAdmin => 1)->to('sd#users');
При этом внутри контроллеров не нужно думать ни о чем: если пользователь не удовлетворил условиям, он просто в эти контроллеры не попадет.
Mojolicious::Commands
Этот механизм позволяет использовать консольные комманды в приложении.
Главная прелесть заключается в том, что можно добавлять свои команды, которые будут выполняться в контексте приложения и иметь доступ к его внутренностям (см. документацию). Таким образом, например, по крону я синхронизирую хранящиеся локально в приложении данные с внутренними веб-сервисами компании:
$ ./AppName GetCMDB -h Usage: APPLICATION GetCMDB
Хуки
Mojolicious предоставляет множество хуков, которые срабатывают при определенных событиях и через которые можно влиять на работу приложения.
Один из них я использую всегда — before_render. Этот хук срабатывает до того, как контроллер передает данные в шаблон и позволяет решить две основные задачи:
Диагностика ошибок
Из хука доступны аргументы вызова рендерера, среди которых, в случае если контроллер решит «упасть» и показать ошибку 500, будет анонимный хэш {exception}, содержащий информацию о причинах проблем. Я использую его для того, чтобы «отловить» ошибку, сообщить о ней через API в Redmine и заставить приложение среагировать — исправить проблему, либо все же показать сообщение об ошибке, но свое.
Кстати, в процессе сообщения об ошибке приложение ищет у себя на диске файл templates/exception.production.html.ep — HTML-шаблон страницы ошибки. Если шаблона нет — используется коробочный, что может удивить пользователей.
Диагностика проблем фронтэнда
Поскольку хуку доступно все содержимое контроллера, можно прямо из него собрать необходимые данные о работе контроллера, и передать из через stash() клиенту. Удобно, если фронтэнд ведет себя не так как задумано и встает вопрос соответствуют ли данные (например, json), передаваемые клиенту тому, что должно быть передано.
Код хука добавляется в AppName.pm и может выглядеть так:
$self->hook(before_render => sub { my ($c, $args) = @_; if ($args->{'exception'}) { # при проблемах с контроллером, готовим свой %snapshot для передачи на страницу ошибки my %snapshot = map {$_ => $c->stash->{$_}} grep {!/mojo.active_session|mojo.captures|mojo.routed|mojo.secrets|mojo.started|^config$|^exception$/ and defined $c->stash->{$_}} keys %{$c->stash}; # сообщение в Redmine через специальный хелпер $c->RedmineReport(); } # снапшот данных контроллера для дэбага фронтэнда $c->stash(snapshot => { map {$_ => $c->stash->{$_}} grep {!/mojo.active_session|mojo.captures|mojo.routed|mojo.secrets|mojo.started|^config$|^exception$/ and defined $c->stash->{$_}} keys %{$c->stash} }); return; });
Много данных
Если приложение получается большим — оно может содержать несколько файлов контроллеров и хелперов (подключенных как плагины). По-умолчанию, все файлы Mojo будет искать в AppName/lib, чтобы не плодить множество визуального мусора в одном каталоге, можно разделить файлы по подкаталогам и подключать в AppName.pm, например, так:
# Подключит хэлперы из файлов Core.pm, Lib.pm, CMDB.pm из каталога AppName/lib/Helpers $self->plugin('AppName::Helpers::Core'); $self->plugin('AppName::Helpers::Lib'); $self->plugin('AppName::Helpers::CMDB'); # Подключит контроллеры из файлов в каталоге AppName/lib/Controllers $r = $r->namespaces(['AppName::Controllers']);
Вместо заключения
Как всегда, в Perl и решениях на нем построенных, существует множество способов решать задачи просто, быстро и красиво. Я не претендую на то, что мои способы самые-самые, но они, как минимум, весьма полезны в работе.
