[Из песочницы] Полезное и приятное для разработчика в Mojolicious
Про Mojolicious на Хабре уже несколько раз писали. Фреймворк успешно развивается и, на мой взгляд, становится удобнее для быстрой разработки с каждым днем.Под катом я собрал несколько приемов работы с фреймворком, которые серьезно упрощают жизнь мне и, быть может, будут полезны для кого-то еще.
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 и решениях на нем построенных, существует множество способов решать задачи просто, быстро и красиво. Я не претендую на то, что мои способы самые-самые, но они, как минимум, весьма полезны в работе.