Mojolicious::Guides::Routing
НАЗВАНИЕ
Mojolicious::Guides::Routing — Маршрутизация
ОБЗОР
Этот документ содержит простое и интересное руководство по работе с маршрутизатором Mojolicious и основные его понятия.
ПОНЯТИЯ
Основы, которые должен знать каждый Mojolicious разработчик.
Диспетчер
Основой каждого веб фреймворка является небольшой черный ящик, связывающий входящие запросы с кодом, генерующим соответствующий ответ.
GET /user/show/1 -> $self->render(text => 'Sebastian!');
Этот черный ящик обычно называют диспетчером. Существует множество реализаций, использующих различные подходы для создания таких связей, но практически все, так или иначе, основаны на связывании путей запросов с каким-либо генератором ответа.
/user/show/1 -> $self->render(text => 'Sebastian!'); /user/show/2 -> $self->render(text => 'Sara!'); /user/show/3 -> $self->render(text => 'Baerbel!'); /user/show/4 -> $self->render(text => 'Wolfgang!');
While it is very well possible to make all these connections static, it is also rather inefficient. That's why regular expressions are commonly used to make the dispatch process more dynamic.
qr|/user/show/(\d+)| -> $self->render(text => $users{$1});
В современных диспетчерах есть доступ ко всему, что может предложить HTTP.
Можно использовать не только путь запроса, но и, к примеру,
метод запроса или заголовки Host
, User-Agent
и Accept
.
GET /user/show/23 HTTP/1.1 Host: mojolicio.us User-Agent: Mozilla/5.0 (compatible; Mojolicious; Perl) Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Маршруты
Хотя регулярные выражения достаточно мощное средство, они не очень понятно выглядят и слишком избыточны для простого сопоставления пути.
qr|/user/show/(\d+)| -> $self->render(text => $users{$1});
Это как раз тот случай, когда можно применить маршруты. Они были спроектированы специально для представления путей со специальными метками (placeholder'ами).
/user/show/:id -> $self->render(text => $users{$id});
Единственная разница между статическим путем и маршрутом приведенным выше
— это специальная метка :id
. В любом месте маршрута может быть
более одной метки.
/user/:action/:id
Основная идея маршрутизатора Mojolicious в том, что извлеченные значения меток превращаются в хеш.
/user/show/23 -> /user/:action/:id -> {action => 'show', id => 23}
Этот хэш является центром каждого Mojolicious приложения, вы узнаете об этом чуть позже. Внутри приложения маршруты компилируются в регулярные выражения, по этому вы можете использовать лучшее обоих подходов поднакопив немного опыта.
/user/show/:id -> qr/(?-xism:^\/user\/show/([^\/\.]+))/
Завершающий слэш необязателен.
/user/show/23/ -> /user/:action/:id -> {action => 'show', id => 23}
Обратимость
Еще одно преимущество маршрутов перед регулярными выражениями — это возможность обратной операции. Выделенные значения могут быть превращены в путь когда угодно.
/sebastian -> /:name -> {name => 'sebastian'} {name => 'sebastian'} -> /:name -> /sebastian
Универсальные метки
Универсальные метки - самый простой вариант меток.
Они захватывают любые символы кроме /
и .
.
/hello -> /:name/hello -> undef /sebastian/23/hello -> /:name/hello -> undef /sebastian.23/hello -> /:name/hello -> undef /sebastian/hello -> /:name/hello -> {name => 'sebastian'} /sebastian23/hello -> /:name/hello -> {name => 'sebastian23'} /sebastian 23/hello -> /:name/hello -> {name => 'sebastian 23'}
Общую метку можно заключить в круглые скобки, чтобы отделить ее от окружающего текста.
/hello -> /(:name)hello -> undef /sebastian/23hello -> /(:name)hello -> undef /sebastian.23hello -> /(:name)hello -> undef /sebastianhello -> /(:name)hello -> {name => 'sebastian'} /sebastian23hello -> /(:name)hello -> {name => 'sebastian23'} /sebastian 23hello -> /(:name)hello -> {name => 'sebastian 23'}
Метки со специальными символами
Метки со специальными символами похожи на нестрогие метки, но соответствуют любым символам.
/hello -> /*name/hello -> undef /sebastian/23/hello -> /*name/hello -> {name => 'sebastian/23'} /sebastian.23/hello -> /*name/hello -> {name => 'sebastian.23'} /sebastian/hello -> /*name/hello -> {name => 'sebastian'} /sebastian23/hello -> /*name/hello -> {name => 'sebastian23'} /sebastian 23/hello -> /*name/hello -> {name => 'sebastian 23'}
Нестрогие метки
Нестрогие метки похожи на два типа меток, о которых говорилось выше, за исключением обязательных
круглых скобок и совпадением со всеми символами кроме /
.
/hello -> /(.name)/hello -> undef /sebastian/23/hello -> /(.name)/hello -> undef /sebastian.23/hello -> /(.name)/hello -> {name => 'sebastian.23'} /sebastian/hello -> /(.name)/hello -> {name => 'sebastian'} /sebastian23/hello -> /(.name)/hello -> {name => 'sebastian23'} /sebastian 23/hello -> /(.name)/hello -> {name => 'sebastian 23'}
ОСНОВЫ
Часто используемые функциональные возможности о которых должен знать каждый разработчик.
Простой маршрут
В каждом Mojolicious приложении есть объект-маршрутизатор, который можно использовать для создания маршрутов.
# Application package MyApp; use Mojo::Base 'Mojolicious'; sub startup { my $self = shift; # Router my $r = $self->routes; # Route $r->route('/welcome')->to(controller => 'foo', action => 'welcome'); } 1;
Простой статический маршрут, показанный выше, загрузит модуль
MyApp::Foo
, создаст экземпляр класса и вызовет метод welcome
.
# Controller package MyApp::Foo; use Mojo::Base 'Mojolicious::Controller'; # Action sub welcome { my $self = shift; # Render response $self->render(text => 'Hello there!'); } 1;
Маршруты обычно настраиваются в методе startup
приложения,
но объект-маршрутизатор доступен везде (в том числе на этапе исполнения).
Пункт назначения маршрута
После того как вы начали новый маршрут вызвав метод route
,
вы также можете указать ему пункт назначения в виде простого
хеша используя связанный метод to
.
# /welcome -> {controller => 'foo', action => 'welcome'} $r->route('/welcome')->to(controller => 'foo', action => 'welcome');
Теперь, если маршрут совпадает с запросом - маршрутизатор будет использовать значения указанного хеша чтобы проверить и найти подходящий код для формирования ответа.
Stash — хранилище данных приложения
Сформированный хеш соответствующего маршрута фактически основа всего цикла запроса Mojolicious. Мы называем это - stash, попросту это глобальное пространство имен, существующее до момента формирования ответа.
# /bye -> {controller => 'foo', action => 'bye', mymessage => 'Bye!'} $r->route('/bye') ->to(controller => 'foo', action => 'bye', mymessage => 'Bye!');
Есть несколько специальных stash значений, например таких как,
controller
и action
, однако их можно переопределить любыми
данными, необходимыми для формирования ответа. После обработки
запроса значения stash могут быть изменены когда угодно.
sub bye { my $self = shift; # Получить сообщение из stash my $message = $self->stash('mymessage'); # Изменить сообщение в stash $self->stash(mymessage => 'Welcome!'); }
Специальные значения в stash (С<controller> и action)
Когда диспетчер видит, значения controller
и action
в stash он
всегда будет пытаться превратить их в класс и метод для обработки.
Значение controller
приводится к ВерблюжемуСтилю и к нему добавляется
префикс namespace
(установленный по умолчанию для класса приложений)
до тех пор пока значение action не изменится полностью. Из-за этого
оба значения чувствительны к регистру
# Создаем приложение package MyApp; use Mojo::Base 'Mojolicious'; sub startup { my $self = shift; # Инициализация роутера my $r = $self->routes; # /bye -> {controller => 'foo', action => 'bye'} -> MyApp::Foo->bye $r->route('/bye')->to(controller => 'foo', action => 'bye'); } 1; # Контроллер package MyApp::Foo; use Mojo::Base 'Mojolicious::Controller'; # Action sub bye { my $self = shift; # Выдать ответ $self->render(text => 'До свидания!'); } 1;
Классы контроллеров идеальны для организации кода в больших
проектах. Есть множество других стратегий обработки запроса, но так
как контроллеры используются чаще всего, для них есть краткая форма
записи controller#action
.
# /bye -> {controller => 'foo', action => 'bye', mymessage => 'Bye!'} $r->route('/bye')->to('foo#bye', mymessage => 'Bye!');
Во время преобразования регистра симвлов -
заменяется на ::
,
это позволяет использовать многоуровневые иерархии controller
.
# / -> {controller => 'foo-bar', action => 'hi'} -> MyApp::Foo::Bar->hi $r->route('/')->to('foo-bar#hi');
Маршрут к классу (namespace)
Время от времени может возникнуть необходимость обработки запроса в
совершенно другое пространство имен namespace
.
# /bye -> MyApp::Controller::Foo->bye $r->route('/bye') ->to(namespace => 'MyApp::Controller::Foo', action => 'bye');
Имя контроллера controller
всегда добавляется к пространству
имен namespace
, если оно установлено.
# /bye -> MyApp::Controller::Foo->bye $r->route('/bye')->to('foo#bye', namespace => 'MyApp::Controller');
Вы так же можете изменять стандартные пространства имен для всех маршрутов в приложении.
$r->namespace('MyApp::Controller');
Маршрут связанный с функцией обратного вызова (cb)
Переменная cb
из stash может быть использована для выполнения
функции обратного вызова вместо контроллера.
$r->route('/bye')->to(cb => sub { my $self = shift; $self->render(text => 'До свидания!'); });
Эта техника - основа Mojolicious::Lite, подробнее о которой можно узнать из поставляемой документации.
Более строгие специальные метки (placeholders)
Очень простой способ сделать заполнители более ограничительные альтернативы, вы просто составьте список возможных значений.
# /bender -> {controller => 'foo', action => 'bar', name => 'bender'} # /leela -> {controller => 'foo', action => 'bar', name => 'leela'} # /fry -> undef $r->route('/:name', name => [qw/bender leela/]) ->to(controller => 'foo', action => 'bar');
Вы можете также подогнать регулярные выражения за заполнителями, чтобы лучше
удовлетворять ваши потребности.
Только убедитесь, что не используются ^
и $
или захватывающие группы (...)
,
потому что внутренне заполнители становятся частью большого регулярного выражения,
хотя возможно использование (?:...)
.
# /23 -> {controller => 'foo', action => 'bar', number => 23} # /test -> undef $r->route('/:number', number => qr/\d+/) ->to(controller => 'foo', action => 'bar'); # /23 -> undef # /test -> {controller => 'foo', action => 'bar', name => 'test'} $r->route('/:name', name => qr/[a-zA-Z]+/) ->to(controller => 'foo', action => 'bar');
Таким образом вы получите легко читаемые маршруты и всю силу регулярных выражений.
Форматы
Расширения файлов такие как .html
и .txt
в конце
перенаправления автоматически определяются и сохраняются в
спрятанном значении format
.
# /foo -> {controller => 'foo', action => 'bar'} # /foo.html -> {controller => 'foo', action => 'bar', format => 'html'} # /foo.txt -> {controller => 'foo', action => 'bar', format => 'txt'} $r->route('/foo')->to(controller => 'foo', action => 'bar');
Это, например, допускает многочисленные шаблоны для различных форматов представления одного и того же кода. Также можно указать формат в шаблоне маршрута на соответствие ему, просто убедитесь, что более конкретные маршруты проходят в первую очередь.
# /foo.txt -> {controller => 'foo', action => 'text', format => 'txt'} $r->route('/foo.txt')->to(controller => 'foo', action => 'text'); # /foo -> {controller => 'foo', action => 'hyper'} # /foo.html -> {controller => 'foo', action => 'hyper', format => 'html'} $r->route('/foo')->to(controller => 'foo', action => 'hyper');
Ограничительные заполнители могут также использоваться для определения формата.
# /foo.rss -> {controller => 'foo', action => 'feed', format => 'rss'} # /foo.xml -> {controller => 'foo', action => 'feed', format => 'xml'} # /foo.txt -> undef $r->route('/foo', format => [qw/rss xml/]) ->to(controller => 'foo', action => 'feed');
Или можно просто отключить определение формата.
# /foo -> {controller => 'foo', action => 'bar'} # /foo.html -> undef $r->route('/foo', format => 0)->to(controller => 'foo', action => 'bar');
Метки и пункты назначения
Извлекаемые значения служебных меток просто переопределяют старые значения stash, если существовали ранее.
# /bye -> {controller => 'foo', action => 'bar', mymessage => 'bye'} # /hey -> {controller => 'foo', action => 'bar', mymessage => 'hey'} $r->route('/:mymessage') ->to(controller => 'foo', action => 'bar', mymessage => 'hi');
Еще один интересный эффект: если служебная метка находится в конце маршрута и в stash есть значение с таким же именем, метка автоматически становится необязательной.
# / -> {controller => 'foo', action => 'bar', mymessage => 'hi'} $r->route('/:mymessage') ->to(controller => 'foo', action => 'bar', mymessage => 'hi');
Это справедливо также для случаев, когда несколько меток
следуют друг за другом и разделены только символами /
.
# / -> {controller => 'foo', action => 'bar'} # /users -> {controller => 'users', action => 'bar'} # /users/list -> {controller => 'users', action => 'list'} $r->route('/:controller/:action') ->to(controller => 'foo', action => 'bar');
Специальные stash-значения, такие как controller
и action
тоже могут быть плейсхолдерами (метками), это позволяет
создавать невероятно гибкие маршруты.
Именованные маршруты
Именование ваших маршрутов позволит ссылаться на них из многих вспомогательных функций во всем фреймворке.
# /foo/abc -> {controller => 'foo', action => 'bar', name => 'abc'} $r->route('/foo/:name')->name('test') ->to(controller => 'foo', action => 'bar'); # Генерирует URL "/foo/abc" для маршрута "test" $self->url_for('test'); # Генерирует URL "/foo/Иван" для маршрута "test" $self->url_for('test', name => 'Иван');
Безымянные маршруты автоматически формируют имена, которые эквивалентны самому маршруту без учета словообразующих символов.
# из /foo/bar формируется имя foobar для маршрута $r->route('/foo/bar')->to('test#stuff'); # генерация URL "/foo/bar" $self->url_for('foobar');
Чтобы ссылаться на текущий маршрут, вы всегда можете использовать
зарезервированное имя current
.
# Генерация URL для текущего маршрута $self->url_for('current');
HTTP Методы
Метод via
объекта маршрута позволяет задать только некоторые конкретные методы HTTP.
# GET /bye -> {controller => 'foo', action => 'bye'} # POST /bye -> undef # DELETE /bye -> undef $r->route('/bye')->via('get')->to(controller => 'foo', action => 'bye'); # GET /bye -> {controller => 'foo', action => 'bye'} # POST /bye -> {controller => 'foo', action => 'bye'} # DELETE /bye -> undef $r->route('/bye')->via(qw/get post/) ->to(controller => 'foo', action => 'bye');
Вложенные маршруты
Кроме того, можно строить древовидные структуры из маршрутов для удаления повторяющихся участков кода. Маршрут с детьми не может сопоставляться со своими собственными, хотя только фактические концевые точки этих вложенных маршрутов могут.
# /foo -> undef # /foo/bar -> {controller => 'foo', action => 'bar'} my $foo = $r->route('/foo')->to(controller => 'foo'); $foo->route('/bar')->to(action => 'bar');
Stash просто перемещается от маршрута к маршруту и заменяет старые значения на новые.
# /foo -> undef # /foo/abc -> undef # /foo/bar -> {controller => 'foo', action => 'bar'} # /foo/baz -> {controller => 'foo', action => 'baz'} # /foo/cde -> {controller => 'foo', action => 'abc'} my $foo = $r->route('/foo')->to(controller => 'foo', action => 'abc'); $foo->route('/bar')->to(action => 'bar'); $foo->route('/baz')->to(action => 'baz'); $foo->route('/cde');
Маршруты Mojolicious::Lite
Маршруты Mojolicious::Lite являются, по сути, лишь небольшой удобной прослойкой вокруг всего, что описано выше, а также часть обычного маршрутизатора.
# GET /foo -> {controller => 'foo', action => 'abc'} $r->get('/foo')->to(controller => 'foo', action => 'abc');
Это делает процесс роста ваших Mojolicious::Lite прототипов в полноценное Mojolicious приложение очень простым.
# POST /bar $r->post('/bar' => sub { my $self = shift; $self->render(text => 'Также как в Mojolicious::Lite action!'); });
Даже более абстрактные концепции доступны.
# GET /yada # POST /yada my $yada = $r->under('/yada'); $yada->get(sub { my $self = shift; $self->render(text => 'Привет!'); }); $yada->post(sub { my $self = shift; $self->render(text => 'Убирайся!'); });
ДОПОЛНИТЕЛЬНО
Более мощные, но реже используемые возможности.
Точки маршрута
Промежуточные точки очень похожи на обычные вложенные маршруты, но могут сопоставляться, даже если они имеют дочерние маршруты.
# /foo -> {controller => 'foo', action => 'baz'} # /foo/bar -> {controller => 'foo', action => 'bar'} my $foo = $r->waypoint('/foo')->to(controller => 'foo', action => 'baz'); $foo->route('/bar')->to(action => 'bar');
Все дочерние маршруты будут игнорироваться, если промежуточные точки совпадают.
Мосты
Мосты в отличие от вложенных маршрутов и точек маршрута всегда совпадают и приводят к дополнительным циклам диспечеризации.
# /foo -> undef # /foo/bar -> {controller => 'foo', action => 'baz'} # {controller => 'foo', action => 'bar'} my $foo = $r->bridge('/foo')->to(controller => 'foo', action => 'baz'); $foo->route('/bar')->to(action => 'bar');
Фактически код "моста" должен возвращать истинное значение или цепочка диспетчеризации будет нарушена, это делает мосты очень мощным инструментом для проверки подлинности.
# /foo -> undef # /foo/bar -> {cb => sub {...}} # {controller => 'foo', action => 'bar'} my $foo = $r->bridge('/foo')->to(cb => sub { my $self = shift; # проверка подлинности прошла return 1 if $self->req->headers->header('X-Bender'); # подлинность не подтверждена return; }); $foo->route('/bar')->to(controller => 'foo', action => 'bar');
Shortcuts
Вы также можете добавить ваши собственные shortcuts, чтобы сделать формирование маршрутов более выразительным.
# Simple "resource" shortcut $r->add_shortcut(resource => sub { my ($r, $name) = @_; # Generate "/$name" route my $resource = $r->route("/$name")->to("$name#"); # Handle POST requests $resource->post->to('#create')->name("create_$name"); # Handle GET requests $resource->get->to('#show')->name("show_$name"); return $resource; }); # POST /user -> {controller => 'user', action => 'create'} # GET /user -> {controller => 'user', action => 'show'} $r->resource('user');
Сокращения могут привести к чему угодно, маршрутизация, мосты или может быть даже и к тем и другим. И следите за тем, чтобы не попасть в трясину.
Условия
Иногда вам может потребоваться немного больше возможностей, например, для проверки
User-Agent
заголовка в нескольких маршрутах.
Здесь в работу вступают условия. В основном, это плагины маршрутизатора.
# Простое условие "User-Agent" $r->add_condition( agent => sub { my ($r, $c, $captures, $pattern) = @_; # Пользователь передал регулярное выражение return unless $pattern && ref $pattern eq 'Regexp'; # Если совпал "User-Agent" в загловке, то вернуть истинное значение my $agent = $c->req->headers->user_agent; return 1 if $agent && $agent =~ $pattern; # Не удачно return; } ); # /firefox_only (Firefox) -> {controller => 'foo', action => 'bar'} $r->route('/firefox_only')->over(agent => qr/Firefox/) ->to(controller => 'foo', action => 'bar');
Метод add_condition
регистрирует новое условие в маршрутизаторе,
в то время как over
фактически применяет его к маршруту.
Плагины условий
Вы также можете поместить ваши условия в повторно используемые плагины.
# Плагин package Mojolicious::Plugin::WerewolfCondition; use Mojo::Base 'Mojolicious::Plugin'; use Astro::MoonPhase; sub register { my ($self, $app) = @_; # Добавление условия "werewolf" $app->routes->add_condition( werewolf => sub { my ($r, $c, $captures, $days) = @_; # Держите оборотней за пределами! return if abs(14 - (phase(time))[2]) > ($days / 2); # Все нормально, это не оборотень return 1; } ); } 1;
Теперь загрузите плагин. Условия готовы для использования в ваших приложениях.
# Приложение package MyApp; use Mojo::Base 'Mojolicious'; sub startup { my $self = shift; # Плагин $self->plugin('werewolf_condition'); # Маршрутизатор my $r = $self->routes; # /hideout (храните их в течении четырех дней после полнолуния) $r->route('/hideout')->over(werewolf => 4) ->to(controller => 'foo', action => 'bar'); } 1;
Встраиваемые приложения
Вы можете легко встраивать целые приложения, используя их вместо контроллера. Это, например, позволяет использовать специфический язык Mojolicious::Lite в обычных контроллерах Mojolicious.
# Контроллер package MyApp::Bar; use Mojolicious::Lite; # /hello get '/hello' => sub { my $self = shift; my $name = $self->param('name'); $self->render(text => "Привет $name!"); }; 1;
С методом detour
, который очень похож на to
, вы можете позволить
маршруту частично совпадать и использовать только оставшийся путь во
встроенном приложении.
# /foo/* $r->route('/foo')->detour('bar#', name => 'Mojo');
Минимальным встраиваемым приложением является не более, чем подклассом
Mojo, содержащим метод handler
, принимающим объекты
Mojolicious::Controller.
package MyApp::Bar; use Mojo::Base 'Mojo'; sub handler { my ($self, $c) = @_; $c->res->code(200); my $name = $c->param('name'); $c->res->body("Привет $name!"); } 1;
Можно также использовать Mojolicious::Plugin::Mount для монтирования целого автономного приложения, связанного с определенным префиксом.
use Mojolicious::Lite; # Все приложение монтируется для маршрута "/prefix" plugin mount => {'/prefix' => '/home/sri/myapp.pl'}; # Normal route get '/' => sub { shift->render_text('Hello World!') }; app->start;
Плагины приложения
Встраивание Mojolicious приложений это просто, но может быть еще проще, если добавить к нему подобный плагин:
# Плагин package Mojolicious::Plugin::MyEmbeddedApp; use Mojo::Base 'Mojolicious::Plugin'; sub register { my ($self, $app) = @_; # Автоматически добавлять маршрут $app->routes->route('/foo')->detour(app => EmbeddedApp::app()); } package EmbeddedApp; use Mojolicious::Lite; get '/bar' => 'bar'; 1; __DATA__ @@ bar.html.ep Привет Мир!
Значение переменной app
копилки(stash) можно использовать для уже
созданного экземпляра приложения. Теперь просто загружаете плагин
и все готово.
# Приложение package MyApp; use Mojo::Base 'Mojolicious'; sub startup { my $self = shift; # Плагин $self->plugin('my_embedded_app'); } 1;
WebSockets (Веб сокеты)
Вы можете запретить доступ к установлению соединения WebSocket,
используя метод websocket
.
# /ws (WebSocket handshake) $r->websocket('/echo')->to(controller => 'foo', action => 'echo'); # Controller package MyApp::Foo; use Mojo::Base 'Mojolicious::Controller'; # Action sub echo { my $self = shift; $self->on_message(sub { my ($self, $message) = @_; $self->send_message("echo: $message"); }); } 1;
Интернационализированные идентификаторы ресурсов
Интернационализированные идентификаторы ресурсов (IRI) обрабатываются прозрачно. Поэтому получаемые пути гарантированно не экранированы и декодированы в Perl символы.
use utf8; # /☃ (юникодный снеговик) -> {controller => 'foo', action => 'snowman'} $r->route('☃')->to(controller => 'foo', action => 'snowman');
Только не забывайте использовать прагму utf8, иначе "юникодный снеговик" станет очень грустным.
Самоанализ (интроспекция)
Команда routes
может быть использована из командной строки, чтобы получить список
все имеющихся маршрутов вместе с именем и внутренними регулярными выражениями.
$ script/myapp routes /foo/:name GET fooname (?-xism:^/foo/([^\/\.]+)) /baz/*everything POST bazeverything (?-xism:^/baz/(.+)) /bar/(.test) * bartest (?-xism:^/bar/([^\/]+))
ДОПОЛНИТЕЛЬНО
Вы можете продолжить перейдя на страницу Mojolicious::Guides или на wiki-страничку Mojolicious http://github.com/kraih/mojo/wiki, в котором содержится много документации и примеров от различных авторов.