Основы работы с маршрутизатором запросов Mojolicious и его основные принципы # Copyright (C) 2008-2010, Sebastian Riedel. =encoding utf8 =head1 НАЗВАНИЕ Mojolicious::Guides::Routing - Маршрутизация =head1 ОБЗОР Этот документ содержит простое и приятное руководство по работе с маршрутизатором L и основные его понятия. =head1 ПОНЯТИЯ Основы, которые должен знать каждый L разработчик. =head2 Диспетчер Основой каждого веб фреймворка является небольшой черный ящик, связывающий входящие запросы с кодом, генерующим соответствующий ответ. GET /user/show/1 -> $self->render(text => 'Себастьян!'); Этот черный ящик обычно называют диспетчером. Существует множество реализаций, использующих различные подходы для создания таких связей, но практически все, так или иначе, основаны на связывании путей запросов с каким-либо генератором ответа. /user/show/1 -> $self->render(text => 'Себастьян!'); /user/show/2 -> $self->render(text => 'Сара!'); /user/show/3 -> $self->render(text => 'Беирбел!'); /user/show/4 -> $self->render(text => 'Вольфганг!'); Конечно возможно сделать все эти связи статичными, но, безусловно, это не эффективно. По этому часто применяются регулярные выражения, чтобы сделать процесс маршрутизации более динамичным. qr|/user/show/(\d+)| -> $self->render(text => $users{$1}); В современных диспетчерах есть доступ ко всему, что может предложить HTTP. Можно использовать не только путь запроса, но и, к примеру, метод запроса или заголовки C, C и C. GET /user/show/23 HTTP/1.1 Host: mojolicious.org User-Agent: Mozilla/5.0 (compatible; Mojolicious; Perl) Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 =head2 Маршруты Хотя регулярные выражения достаточно мощное средство, они не очень понятно выглядят и слишком избыточны для простого сопоставления пути. qr|/user/show/(\d+)| -> $self->render(text => $users{$1}); Это как раз тот случай, когда можно применить маршруты. Они были спроектированы специально для представления путей со специальными метками (placeholder'ами). /user/show/:id -> $self->render(text => $users{$id}); Единственная разница между статическим путем и маршрутом приведенным выше - это специальная метка C<:id>. В любом месте маршрута может быть более одной метки . /user/:action/:id Основная идея маршрутизатора L в том, что извлеченные значения меток превращаются в хеш. /user/show/23 -> /user/:action/:id -> {action => 'show', id => 23} Этот хэш - центр каждого L приложения, вы узнаете об этом чуть позже. Внутри приложения маршруты компилируются в регулярные выражения, по этому вы можете использовать лучшее обоих подходов поднакопив немного опыта. /user/show/:id -> qr/(?-xism:^\/user\/show/([^\/\.]+))/ =head2 Обратимость Еще одно преимущество маршрутов перед регулярными выражениями - это возможность обратной операции. Выделенные значения могут быть превращены в путь когда угодно. /sebastian -> /:name -> {name => 'sebastian'} {name => 'sebastian'} -> /:name -> /sebastian =head2 Универсальные метки Универсальные метки - самый простой вариант меток. Они захватывают любые символы кроме C и С<.>. /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'} =head2 Нестрогие метки Очень похожи на универсальные метки, за исключением обязательных скобок и совпадением со всеми символами кроме C. /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'} =head2 Метки со специальными символами Метки со специальными символами похожи на нестрогие метки, но соответствуют любым символам. /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'} =head1 ОСНОВЫ Часто используемые функциональные возможности о которых должен знать каждый разработчик. =head2 Простой маршрут В каждом L приложении есть объект-маршрутизатор, который можно использовать для создания маршрутов. # Приложение package MyApp; use base 'Mojolicious'; sub startup { my $self = shift; # Маршрутизатор my $r = $self->routes; # Маршрут $r->route('/welcome')->to(controller => 'foo', action => 'welcome'); } 1; Простой статический маршрут, показанный выше, загрузит модуль C, создаст экземпляр класса и вызовет метод C. # Контроллер package MyApp::Foo; use base 'Mojolicious::Controller'; # Действие sub welcome { my $self = shift; # Ответ $self->render(text => 'Привет!'); } 1; Маршруты обычно настраиваются в методе C приложения, но объект-маршрутизатор доступен везде (в том числе на этапе исполнения). =head2 Пункт назначения маршрута После того как вы начали новый маршрут вызвав метод C, вы также можете указать ему пункт назначения в виде простого хеша используя связанный метод C. # /welcome -> {controller => 'foo', action => 'welcome'} $r->route('/welcome')->to(controller => 'foo', action => 'welcome'); Теперь, если маршрут совпадает с запросом - маршрутизатор будет использовать значения указанного хеша чтобы проверить и найти подходящий код для формирования ответа. =head2 Stash - хранилище данных приложения Сформированный хеш соответствующего маршрута фактически основа всего цикла запроса L. Мы называем это - stash, попросту это глобальное пространство имен, существующее до момента формирования ответа. # /bye -> {controller => 'foo', action => 'bye', mymessage => 'Пока!'} $r->route('/bye') ->to(controller => 'foo', action => 'bye', mymessage => 'Пока!'); Есть несколько специальных stash значений, например таких как, C и C, однако их можно переопределить любыми данными, необходимыми для формирования ответа. После обработки запроса значения stash могут быть изменены когда угодно. =head2 Специальные значения в stash (С и C) Когда диспетчер видит, значения C и C в stash он всегда будет пытаться превратить их в класс и метод для обработки. Значение C приводится к ВерблюжемуСтилю и к нему добавляется префикс C (установленный по умолчанию для класса приложений) до тех пор пока значение action не изменится полностью. Из-за этого оба значения чувствительны к регистру. # Приложение package MyApp; use 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 base 'Mojolicious::Controller'; # Действие sub bye { my $self = shift; # Ответ $self->render(text => 'До свидания!'); } 1; Классы контроллеров идеальны для организации кода в больших проектах. Есть множество других стратегий обработки запроса, но так как контроллеры используются чаще всего, для них есть краткая форма записи C. # /bye -> {controller => 'foo', action => 'bye', mymessage => 'Пока!'} $r->route('/bye')->to('foo#bye', mymessage => 'Пока!'); Во время преобразования регистра симвлов C<-> заменяется на C<::>, это позволяет использовать многоуровневые иерархии C. # / -> {controller => 'foo-bar', action => 'Привет'} -> MyApp::Foo::Bar->hi $r->route('/')->to('foo-bar#hi'); =head2 Маршрут к классу (C) Время от времени может возникнуть необходимость обработки запроса в совершенно другое пространство имен C. # /bye -> MyApp::Controller::Foo->bye $r->route('/bye') ->to(namespace => 'MyApp::Controller::Foo', action => 'bye'); Имя контроллера C всегда добавляется к пространству имен C, если оно установлено. # /bye -> MyApp::Controller::Foo->bye $r->route('/bye')->to('foo#bye', namespace => 'MyApp::Controller'); Вы так же можете изменять стандартные пространства имен для всех маршрутов. $r->namespace('MyApp::Controller'); =head2 Маршрут связанный с функцией обратного вызова (C) Переменная C из stash может быть использована для выполнения функции обратного вызова вместо контроллера. $r->route('/bye')->to(cb => sub { my $self = shift; $self->render(text => 'Счастливо!'); }); Эта техника - основа L, подробнее о которой можно узнать из поставляемой документации. =head2 Форматы Расширения файлов такие как C<.html> и C<.txt> в конце перенаправления автоматически определяются и сохраняются в спрятанном значении C. # /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'); Это, например, допускает многочисленные шаблоны для различных форматов представления одного и того же кода. =head2 Метки и пункты назначения Извлекаемые значения служебных меток просто переопределяют старые значения 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'); Это справедливо также для случаев, когда несколько меток следуют друг за другом и разделены только символами C. # / -> {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-значения, такие как C и C тоже могут быть плейсхолдерами (метками), это позволяет создавать невероятно гибкие маршруты. =head2 Именованные маршруты Именование ваших маршрутов позволит ссылаться на них из многих вспомогательных функций во всем фреймворке. # /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 => 'Иван'); =head2 HTTP Методы Метод C объекта маршрута позволяет задать только некоторые конкретные методы 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'); =head2 Вложенные маршруты Кроме того, можно строить древовидные структуры из маршрутов для удаления повторяющихся участков кода, которые требуют конечные точки несмотря на поиск. # /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'); =head1 Дополнительно Более мощные, но реже используемые возможности. Точки маршрута Точки маршрута очень похожи на нормальные вложенные маршруты, но могут совпадать, даже если они не являются конечной точкой сами по себе. # /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'); =head2 Мосты Мосты в отличие от вложенных маршрутов и точек маршрута всегда совпадают и приводят к дополнительным циклам диспечеризации. # /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'); =head2 Более строгие специальные метки (placeholders) Вы можете настроить регулярные выражения следом за метками, для удовлетворения ваших потребностей, просто убедитесь, что вы не используете C<^>, C<$> и C<()>, потому что метки становятся частью более крупного регулярного выражения внутри приложения. # /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'); Таким образом вы получите легко читаемые маршруты и всю силу регулярных выражений. =head2 Условия Иногда вам может потребоваться немного больше возможностей, например, для проверки C заголовка в нескольких маршрутах. Здесь в работу вступают условия. В основном, это плагины маршрутизатора. # Простое условие "User-Agent" $r->add_condition( agent => sub { my ($r, $tx, $captures, $pattern) = @_; # Пользователь передал регулярное выражение return unless $pattern && ref $pattern eq 'Regexp'; # Если совпал "User-Agent" в загловке, то вернуть захваченные значения my $agent = $tx->req->headers->user_agent; return $captures 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'); Метод C регистрирует новое условие в маршрутизаторе, в то время как C фактически применяет его к маршруту. =head2 Плагины условий Вы также можете поместить ваши условия в повторно используемые плагины. # Плагин package Mojolicious::Plugin::WerewolfCondition; use base 'Mojolicious::Plugin'; use Astro::MoonPhase; sub register { my ($self, $app) = @_; # Добавим условие для "оборотней" $app->routes->add_condition( werewolf => sub { my ($r, $tx, $captures, $days) = @_; # Гнать оборотней прочь! return if abs(14 - (phase(time))[2]) > ($days / 2); # Всё в порядке, оборотней нет return $captures; } ); } 1; Теперь загрузите плагин. Условия готовы для использования в ваших приложениях. # Приложение package MyApp; use base 'Mojolicious'; sub startup { my $self = shift; # Плагин $self->plugin('werewolf_condition'); # Маршруты my $r = $self->routes; # /убежище (прячте их 4 дня после полнолуния) $r->route('/hideout')->over(werewolf => 4) ->to(controller => 'foo', action => 'bar'); } 1; =head2 Встраиваемые приложения Вы можете легко встраивать целые приложения, используя метод C, он принимает класс или экземпляр L приложения. # Приложение package MyApp; use base 'Mojolicious'; sub startup { my $self = shift; # Маршруты my $r = $self->routes; # Встроенное приложение Mojolicious::Lite по адресу "/foo/*" $r->route('/foo')->detour('MyApp::Foo'); } 1; Это, например, позволяет использовать специфический язык L в обычных контроллерах L. # Контроллер package MyApp::Foo; use Mojolicious::Lite; # Действие get '/bar' => sub { my $self = shift; $self->render(text => 'Привет мир!'); }; 1; =head2 Плагины приложения Встраивание L приложений это просто, но может быть еще проще, если добавить к нему подобный плагин: # Плагин package Mojolicious::Plugin::MyEmbeddedApp; use base 'Mojolicious::Plugin'; sub register { my ($self, $app) = @_; # Автоматически добавляем маршрут $app->routes->route('/foo')->detour(EmbeddedApp::app()); } package EmbeddedApp; use Mojolicious::Lite; get '/bar' => 'bar'; 1; __DATA__ @@ bar.html.ep Привет Мир! Просто загрузите плагин и все готово. # Приложение package MyApp; use base 'Mojolicious'; sub startup { my $self = shift; # Плагин $self->plugin('my_embedded_app'); } 1; =head2 WebSockets (Веб сокеты) Вы можете запретить доступ к установлению соединения WebSocket , используя метод C. # /ws (установка WebSocket соедиения) $r->route('/echo')->websocket->to(controller => 'foo', action => 'echo'); # Контроллер package MyApp::Foo; use base 'Mojolicious::Controller'; # Действие sub echo { my $self = shift; $self->receive_message( sub { my ($self, $message) = @_; $self->send_message("echo: $message"); } ); } 1; =head2 Интернационализированные идентификаторы ресурсов Интернационализированные идентификаторы ресурсов (IRI) обрабатываются прозрачно. Поэтому получаемые пути гарантированно неэкранированы и декодированы в Perl символы. use utf8; # /☃ (юникодный снеговик) -> {controller => 'foo', action => 'snowman'} $r->route('/☃')->to(controller => 'foo', action => 'snowman'); Только не забывайте использовать прагму L, иначе "юникодный снеговик" станет очень грустным. =head2 Самоанализ (интроспекция) Команда C, вызванная из командной строки, распечатает все доступные маршруты включая регулярные выражения. % script/myapp routes /foo/:name (?-xism:^/foo/([^\/\.]+)) /bar/(.test) (?-xism:^/bar/([^\/]+)) /baz/(*everything) (?-xism:^/baz/(.+)) =cut ------------------------------------------------------------------------------- http://translated.by/you/introduction-to-the-mojolicious-router-and-its-underlying-concepts/into-ru/trans/ © 2008-2010, Sebastian Riedel. Original (English): Introduction to the Mojolicious router and its underlying concepts (http://github.com/kraih/mojo/raw/master/lib/Mojolicious/Guides/Routing.pod) Translation: © r3code, cubloid, Foxcool, xoma, korshak, vti, koban, zhdinar. translated.by crowd