1012 lines
36 KiB
Plaintext
1012 lines
36 KiB
Plaintext
|
|
=encoding utf8
|
|
|
|
=head1 NAME
|
|
|
|
Mojolicious::Guides::Routing - Routing requests
|
|
|
|
=head1 OVERVIEW
|
|
|
|
This document contains a simple and fun introduction to the L<Mojolicious> router and its underlying concepts.
|
|
|
|
=head1 CONCEPTS
|
|
|
|
Essentials every L<Mojolicious> developer should know.
|
|
|
|
=head2 Dispatcher
|
|
|
|
The foundation of every web framework is a tiny black box connecting incoming requests with code generating the
|
|
appropriate response.
|
|
|
|
GET /user/show/1 -> $c->render(text => 'Daniel');
|
|
|
|
This black box is usually called a dispatcher. There are many implementations using different strategies to establish
|
|
these connections, but pretty much all are based around mapping the path part of the request URL to some kind of
|
|
response generator.
|
|
|
|
/user/show/2 -> $c->render(text => 'Isabell');
|
|
/user/show/3 -> $c->render(text => 'Sara');
|
|
/user/show/4 -> $c->render(text => 'Stefan');
|
|
/user/show/5 -> $c->render(text => 'Fynn');
|
|
|
|
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+)! -> $c->render(text => $users{$1});
|
|
|
|
Modern dispatchers have pretty much everything HTTP has to offer at their disposal and can use many more variables than
|
|
just the request path, such as request method and headers like C<Host>, C<User-Agent> and C<Accept>.
|
|
|
|
GET /user/show/23 HTTP/1.1
|
|
Host: mojolicious.org
|
|
User-Agent: Mojolicious (Perl)
|
|
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
|
|
|
|
=head2 Routes
|
|
|
|
While regular expressions are quite powerful they also tend to be unpleasant to look at and are generally overkill for
|
|
ordinary path matching.
|
|
|
|
qr!/user/show/(\d+)! -> $c->render(text => $users{$1});
|
|
|
|
This is where routes come into play, they have been designed from the ground up to represent paths with placeholders.
|
|
|
|
/user/show/:id -> $c->render(text => $users{$id});
|
|
|
|
The only difference between a static path and the route above is the C<:id> placeholder. One or more placeholders can
|
|
be anywhere in the route.
|
|
|
|
/user/:action/:id
|
|
|
|
A fundamental concept of the L<Mojolicious> router is that extracted placeholder values are turned into a hash.
|
|
|
|
/user/show/23 -> /user/:action/:id -> {action => 'show', id => 23}
|
|
|
|
This hash is basically the center of every L<Mojolicious> application, you will learn more about this later on.
|
|
Internally, routes get compiled to regular expressions, so you can get the best of both worlds with a little bit of
|
|
experience.
|
|
|
|
/user/show/:id -> qr/(?-xism:^\/user\/show\/([^\/.]+))/
|
|
|
|
A trailing slash in the path is always optional.
|
|
|
|
/user/show/23/ -> /user/:action/:id -> {action => 'show', id => 23}
|
|
|
|
=head2 Reversibility
|
|
|
|
One more huge advantage routes have over regular expressions is that they are easily reversible, extracted placeholders
|
|
can be turned back into a path at any time.
|
|
|
|
/sebastian -> /:name -> {name => 'sebastian'}
|
|
{name => 'sebastian'} -> /:name -> /sebastian
|
|
|
|
Every placeholder has a name, even if it's just an empty string.
|
|
|
|
=head2 Standard placeholders
|
|
|
|
Standard placeholders are the simplest form of placeholders, they use a colon prefix and match all characters except
|
|
C</> and C<.>, similar to the regular expression 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'}
|
|
|
|
All placeholders can be surrounded by C<E<lt>> and C<E<gt>> to separate them from the surrounding text.
|
|
|
|
/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'}
|
|
|
|
The colon prefix is optional for standard placeholders that are surrounded by C<E<lt>> and C<E<gt>>.
|
|
|
|
/i♥mojolicious -> /<one>♥<two> -> {one => 'i', two => 'mojolicious'}
|
|
|
|
=head2 Relaxed placeholders
|
|
|
|
Relaxed placeholders are just like standard placeholders, but use a hash prefix and match all characters except C</>,
|
|
similar to the regular expression 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'}
|
|
|
|
They can be especially useful for manually matching file names with extensions, rather than using L<format
|
|
detection|/"Formats">.
|
|
|
|
/music/song.mp3 -> /music/#filename -> {filename => 'song.mp3'}
|
|
|
|
=head2 Wildcard placeholders
|
|
|
|
Wildcard placeholders are just like the two types of placeholders above, but use an asterisk prefix and match
|
|
absolutely everything, including C</> and C<.>, similar to the regular expression C<(.+)>.
|
|
|
|
/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'}
|
|
|
|
They can be useful for manually matching entire file paths.
|
|
|
|
/music/rock/song.mp3 -> /music/*filepath -> {filepath => 'rock/song.mp3'}
|
|
|
|
=head1 BASICS
|
|
|
|
Most commonly used features every L<Mojolicious> developer should know about.
|
|
|
|
=head2 Minimal route
|
|
|
|
The attribute L<Mojolicious/"routes"> contains a router you can use to generate route structures.
|
|
|
|
# Application
|
|
package MyApp;
|
|
use Mojo::Base 'Mojolicious', -signatures;
|
|
|
|
sub startup ($self) {
|
|
# Router
|
|
my $r = $self->routes;
|
|
|
|
# Route
|
|
$r->get('/welcome')->to(controller => 'foo', action => 'welcome');
|
|
}
|
|
|
|
1;
|
|
|
|
The minimal route above will load and instantiate the class C<MyApp::Controller::Foo> and call its C<welcome> method.
|
|
Routes are usually configured in the C<startup> method of the application class, but the router can be accessed from
|
|
everywhere (even at runtime).
|
|
|
|
# Controller
|
|
package MyApp::Controller::Foo;
|
|
use Mojo::Base 'Mojolicious::Controller', -signatures;
|
|
|
|
# Action
|
|
sub welcome ($self) {
|
|
# Render response
|
|
$self->render(text => 'Hello there.');
|
|
}
|
|
|
|
1;
|
|
|
|
All routes match in the same order in which they were defined, and matching stops as soon as a suitable route has been
|
|
found. So you can improve the routing performance by declaring your most frequently accessed routes first. A routing
|
|
cache will also be used automatically to handle sudden traffic spikes more gracefully.
|
|
|
|
=head2 Routing destination
|
|
|
|
After you start a new route with methods like L<Mojolicious::Routes::Route/"get">, you can also give it a destination
|
|
in the form of a hash using the chained method L<Mojolicious::Routes::Route/"to">.
|
|
|
|
# /welcome -> {controller => 'foo', action => 'welcome'}
|
|
$r->get('/welcome')->to(controller => 'foo', action => 'welcome');
|
|
|
|
Now if the route matches an incoming request it will use the content of this hash to try and find appropriate code to
|
|
generate a response.
|
|
|
|
=head2 HTTP methods
|
|
|
|
There are already shortcuts for the most common HTTP request methods like L<Mojolicious::Routes::Route/"post">, and for
|
|
more control L<Mojolicious::Routes::Route/"any"> accepts an optional array reference with arbitrary request methods as
|
|
first argument.
|
|
|
|
# PUT /hello -> undef
|
|
# GET /hello -> {controller => 'foo', action => 'hello'}
|
|
$r->get('/hello')->to(controller => 'foo', action => 'hello');
|
|
|
|
# PUT /hello -> {controller => 'foo', action => 'hello'}
|
|
$r->put('/hello')->to(controller => 'foo', action => 'hello');
|
|
|
|
# POST /hello -> {controller => 'foo', action => 'hello'}
|
|
$r->post('/hello')->to(controller => 'foo', action => 'hello');
|
|
|
|
# GET|POST /bye -> {controller => 'foo', action => 'bye'}
|
|
$r->any(['GET', 'POST'] => '/bye')->to(controller => 'foo', action => 'bye');
|
|
|
|
# * /whatever -> {controller => 'foo', action => 'whatever'}
|
|
$r->any('/whatever')->to(controller => 'foo', action => 'whatever');
|
|
|
|
There is one small exception, C<HEAD> requests are considered equal to C<GET>, but content will not be sent with the
|
|
response even if it is present.
|
|
|
|
# GET /test -> {controller => 'bar', action => 'test'}
|
|
# HEAD /test -> {controller => 'bar', action => 'test'}
|
|
$r->get('/test')->to(controller => 'bar', action => 'test');
|
|
|
|
You can also use the C<_method> query parameter to override the request method. This can be very useful when submitting
|
|
forms with browsers that only support C<GET> and C<POST>.
|
|
|
|
# PUT /stuff -> {controller => 'baz', action => 'stuff'}
|
|
# POST /stuff?_method=PUT -> {controller => 'baz', action => 'stuff'}
|
|
$r->put('/stuff')->to(controller => 'baz', action => 'stuff');
|
|
|
|
=head2 IRIs
|
|
|
|
IRIs are handled transparently, that means paths are guaranteed to be unescaped and decoded from bytes to characters.
|
|
|
|
# GET /☃ (Unicode snowman) -> {controller => 'foo', action => 'snowman'}
|
|
$r->get('/☃')->to(controller => 'foo', action => 'snowman');
|
|
|
|
=head2 Stash
|
|
|
|
The generated hash of a matching route is actually the center of the whole L<Mojolicious> request cycle. We call it the
|
|
stash, and it persists until a response has been generated.
|
|
|
|
# /bye -> {controller => 'foo', action => 'bye', mymessage => 'Bye'}
|
|
$r->get('/bye')->to(controller => 'foo', action => 'bye', mymessage => 'Bye');
|
|
|
|
There are a few stash values with special meaning, such as C<controller> and C<action>, but you can generally fill it
|
|
with whatever data you need to generate a response. Once dispatched the whole stash content can be changed at any time.
|
|
|
|
sub bye ($self) {
|
|
|
|
# Get message from stash
|
|
my $msg = $self->stash('mymessage');
|
|
|
|
# Change message in stash
|
|
$self->stash(mymessage => 'Welcome');
|
|
}
|
|
|
|
For a full list of reserved stash values see L<Mojolicious::Controller/"stash">.
|
|
|
|
=head2 Nested routes
|
|
|
|
It is also possible to build tree structures from routes to remove repetitive code. A route with children can't match
|
|
on its own though, only the actual endpoints of these nested routes can.
|
|
|
|
# /foo -> undef
|
|
# /foo/bar -> {controller => 'foo', action => 'bar'}
|
|
my $foo = $r->any('/foo')->to(controller => 'foo');
|
|
$foo->get('/bar')->to(action => 'bar');
|
|
|
|
The stash is simply inherited from route to route and newer values override old ones.
|
|
|
|
# /cats -> {controller => 'cats', action => 'index'}
|
|
# /cats/nyan -> {controller => 'cats', action => 'nyan'}
|
|
# /cats/lol -> {controller => 'cats', action => 'default'}
|
|
my $cats = $r->any('/cats')->to(controller => 'cats', action => 'default');
|
|
$cats->get('/')->to(action => 'index');
|
|
$cats->get('/nyan')->to(action => 'nyan');
|
|
$cats->get('/lol');
|
|
|
|
With a few common prefixes you can also greatly improve the routing performance of applications with many routes,
|
|
because children are only tried if the prefix matched first.
|
|
|
|
=head2 Special stash values
|
|
|
|
When the dispatcher sees C<controller> and C<action> values in the stash it will always try to turn them into a class
|
|
and method to dispatch to. The C<controller> value gets converted from C<snake_case> to C<CamelCase> using
|
|
L<Mojo::Util/"camelize"> and appended to one or more namespaces, defaulting to a controller namespace based on the
|
|
application class (C<MyApp::Controller>), as well as the bare application class (C<MyApp>), and these namespaces are
|
|
searched in that order. The action value is not changed at all, so both values are case-sensitive.
|
|
|
|
# Application
|
|
package MyApp;
|
|
use Mojo::Base 'Mojolicious', -signatures;
|
|
|
|
sub startup ($self) {
|
|
# /bye -> MyApp::Controller::Foo->bye
|
|
$self->routes->get('/bye')->to(controller => 'foo', action => 'bye');
|
|
}
|
|
|
|
1;
|
|
|
|
# Controller
|
|
package MyApp::Controller::Foo;
|
|
use Mojo::Base 'Mojolicious::Controller', -signatures;
|
|
|
|
# Action
|
|
sub bye ($self) {
|
|
# Render response
|
|
$self->render(text => 'Good bye.');
|
|
}
|
|
|
|
1;
|
|
|
|
Controller classes are perfect for organizing code in larger projects. There are more dispatch strategies, but because
|
|
controllers are the most commonly used ones they also got a special shortcut in the form of C<controller#action>.
|
|
|
|
# /bye -> {controller => 'foo', action => 'bye', mymessage => 'Bye'}
|
|
$r->get('/bye')->to('foo#bye', mymessage => 'Bye');
|
|
|
|
During camelization C<-> characters get replaced with C<::>, this allows multi-level C<controller> hierarchies.
|
|
|
|
# / -> MyApp::Controller::Foo::Bar->hi
|
|
$r->get('/')->to('foo-bar#hi');
|
|
|
|
You can also just specify the C<controller> in CamelCase form instead of snake_case.
|
|
|
|
# / -> MyApp::Controller::Foo::Bar->hi
|
|
$r->get('/')->to('Foo::Bar#hi');
|
|
|
|
For security reasons the dispatcher will always check if the C<controller> is actually a subclass of
|
|
L<Mojolicious::Controller> or L<Mojo> before dispatching to it.
|
|
|
|
=head2 Namespaces
|
|
|
|
You can use the C<namespace> stash value to change the namespace of a whole route with all its children.
|
|
|
|
# /bye -> MyApp::MyController::Foo::Bar->bye
|
|
$r->get('/bye')->to(namespace => 'MyApp::MyController::Foo::Bar', action => 'bye');
|
|
|
|
The C<controller> is always converted from C<snake_case> to C<CamelCase> with L<Mojo::Util/"camelize">, and then
|
|
appended to this C<namespace>.
|
|
|
|
# /bye -> MyApp::MyController::Foo::Bar->bye
|
|
$r->get('/bye')->to('foo-bar#bye', namespace => 'MyApp::MyController');
|
|
|
|
# /hey -> MyApp::MyController::Foo::Bar->hey
|
|
$r->get('/hey')->to('Foo::Bar#hey', namespace => 'MyApp::MyController');
|
|
|
|
You can also change the default namespaces for all routes in the application with the router attribute
|
|
L<Mojolicious::Routes/"namespaces">, which usually defaults to a namespace based on the application class
|
|
(C<MyApp::Controller>), as well as the bare application class (C<MyApp>).
|
|
|
|
$r->namespaces(['MyApp::MyController']);
|
|
|
|
=head2 Route to callback
|
|
|
|
The C<cb> stash value, which won't be inherited by nested routes, can be used to bypass controllers and execute a
|
|
callback instead.
|
|
|
|
$r->get('/bye')->to(cb => sub ($c) {
|
|
$c->render(text => 'Good bye.');
|
|
});
|
|
|
|
But just like in L<Mojolicious::Lite> you can also pass the callback directly, which usually looks much better.
|
|
|
|
$r->get('/bye' => sub ($c) {
|
|
$c->render(text => 'Good bye.');
|
|
});
|
|
|
|
=head2 Named routes
|
|
|
|
Naming your routes will allow backreferencing in many methods and helpers throughout the whole framework, most of which
|
|
internally rely on L<Mojolicious::Controller/"url_for"> for this.
|
|
|
|
# /foo/marcus -> {controller => 'foo', action => 'bar', user => 'marcus'}
|
|
$r->get('/foo/:user')->to('foo#bar')->name('baz');
|
|
|
|
# Generate URL "/foo/marcus" for route "baz" (in previous request context)
|
|
my $url = $c->url_for('baz');
|
|
|
|
# Generate URL "/foo/jan" for route "baz"
|
|
my $url = $c->url_for('baz', user => 'jan');
|
|
|
|
# Generate URL "http://127.0.0.1:3000/foo/jan" for route "baz"
|
|
my $url = $c->url_for('baz', user => 'jan')->to_abs;
|
|
|
|
You can assign a name with L<Mojolicious::Routes::Route/"name">, or let the router generate one automatically, which
|
|
would be equal to the route itself without non-word characters, custom names have a higher precedence though.
|
|
|
|
# /foo/bar ("foobar")
|
|
$r->get('/foo/bar')->to('test#stuff');
|
|
|
|
# Generate URL "/foo/bar"
|
|
my $url = $c->url_for('foobar');
|
|
|
|
To refer to the current route you can use the reserved name C<current> or no name at all.
|
|
|
|
# Generate URL for current route
|
|
my $url = $c->url_for('current');
|
|
my $url = $c->url_for;
|
|
|
|
To check or get the name of the current route you can use the helper
|
|
L<Mojolicious::Plugin::DefaultHelpers/"current_route">.
|
|
|
|
# Name for current route
|
|
my $name = $c->current_route;
|
|
|
|
# Check route name in code shared by multiple routes
|
|
$c->stash(button => 'green') if $c->current_route('login');
|
|
|
|
=head2 Optional placeholders
|
|
|
|
Extracted placeholder values will simply redefine older stash values if they already exist.
|
|
|
|
# /bye -> {controller => 'foo', action => 'bar', mymessage => 'bye'}
|
|
# /hey -> {controller => 'foo', action => 'bar', mymessage => 'hey'}
|
|
$r->get('/:mymessage')->to('foo#bar', mymessage => 'hi');
|
|
|
|
One more interesting effect, a placeholder automatically becomes optional if there is already a stash value of the same
|
|
name present, this works similar to the regular expression C<([^/.]+)?>.
|
|
|
|
# / -> {controller => 'foo', action => 'bar', mymessage => 'hi'}
|
|
$r->get('/:mymessage')->to('foo#bar', mymessage => 'hi');
|
|
|
|
# /test/123 -> {controller => 'foo', action => 'bar', mymessage => 'hi'}
|
|
# /test/bye/123 -> {controller => 'foo', action => 'bar', mymessage => 'bye'}
|
|
$r->get('/test/:mymessage/123')->to('foo#bar', mymessage => 'hi');
|
|
|
|
And if two optional placeholders are only separated by a slash, that slash can become optional as well.
|
|
|
|
# / -> {controller => 'foo', action => 'bar'}
|
|
# /users -> {controller => 'users', action => 'bar'}
|
|
# /users/list -> {controller => 'users', action => 'list'}
|
|
$r->get('/:controller/:action')->to('foo#bar');
|
|
|
|
Special stash values like C<controller> and C<action> can also be placeholders, which is very convenient especially
|
|
during development, but should only be used very carefully, because every controller method becomes a potential route.
|
|
All uppercase methods as well as those starting with an underscore are automatically hidden from the router and you can
|
|
use L<Mojolicious::Routes/"hide"> to add additional ones.
|
|
|
|
# Hide "create" method in all controllers
|
|
$r->hide('create');
|
|
|
|
This has already been done for all attributes and methods from L<Mojolicious::Controller>.
|
|
|
|
=head2 Restrictive placeholders
|
|
|
|
A very easy way to make placeholders more restrictive are alternatives, you just make a list of possible values, which
|
|
then work similar to the regular expression C<(bender|leela)>.
|
|
|
|
# /fry -> undef
|
|
# /bender -> {controller => 'foo', action => 'bar', name => 'bender'}
|
|
# /leela -> {controller => 'foo', action => 'bar', name => 'leela'}
|
|
$r->get('/:name' => [name => ['bender', 'leela']])->to('foo#bar');
|
|
|
|
You can also adjust the regular expressions behind placeholders directly, just make sure not to use C<^> and C<$> or
|
|
capturing groups C<(...)>, because placeholders become part of a larger regular expression internally, non-capturing
|
|
groups C<(?:...)> are fine though.
|
|
|
|
# /23 -> {controller => 'foo', action => 'bar', number => 23}
|
|
# /test -> undef
|
|
$r->get('/:number' => [number => qr/\d+/])->to('foo#bar');
|
|
|
|
# /23 -> undef
|
|
# /test -> {controller => 'foo', action => 'bar', name => 'test'}
|
|
$r->get('/:name' => [name => qr/[a-zA-Z]+/])->to('foo#bar');
|
|
|
|
This way you get easily readable routes and the raw power of regular expressions.
|
|
|
|
=head2 Placeholder types
|
|
|
|
And if you have multiple routes using restrictive placeholders you can also turn them into placeholder types with
|
|
L<Mojolicious::Routes/"add_type">.
|
|
|
|
# A type with alternatives
|
|
$r->add_type(futurama_name => ['bender', 'leela']);
|
|
|
|
# /fry -> undef
|
|
# /bender -> {controller => 'foo', action => 'bar', name => 'bender'}
|
|
# /leela -> {controller => 'foo', action => 'bar', name => 'leela'}
|
|
$r->get('/<name:futurama_name>')->to('foo#bar');
|
|
|
|
Placeholder types work just like restrictive placeholders, they are just reusable with the
|
|
C<E<lt>placeholder:typeE<gt>> notation.
|
|
|
|
# A type adjusting the regular expression
|
|
$r->add_type(upper => qr/[A-Z]+/);
|
|
|
|
# /user/ROOT -> {controller => 'users', action => 'show', name => 'ROOT'}
|
|
# /user/root -> undef
|
|
# /user/23 -> undef
|
|
$r->get('/user/<name:upper>')->to('users#show');
|
|
|
|
Some types like C<num> are used so commonly that they are available by default.
|
|
|
|
# /article/12 -> {controller => 'article', action => 'show', id => 12}
|
|
# /article/test -> undef
|
|
$r->get('/article/<id:num>')->to('articles#show');
|
|
|
|
For a full list of available placeholder types see also L<Mojolicious::Routes/"TYPES">.
|
|
|
|
=head2 Introspection
|
|
|
|
The command L<Mojolicious::Command::routes> can be used from the command line to list all available routes together
|
|
with names and underlying regular expressions.
|
|
|
|
$ ./myapp.pl routes -v
|
|
/foo/:name .... POST fooname ^/foo/([^/.]+)/?(?:\.([^/]+))?$
|
|
/bar ..U. * bar ^/bar
|
|
+/baz ...W GET baz ^/baz/?(?:\.([^/]+))?$
|
|
/yada .... * yada ^/yada/?(?:\.([^/]+))?$
|
|
|
|
=head2 Under
|
|
|
|
To share code with multiple nested routes you can use L<Mojolicious::Routes::Route/"under">, because unlike normal
|
|
nested routes, the routes generated with it have their own intermediate destination and result in additional dispatch
|
|
cycles when they match.
|
|
|
|
# /foo -> undef
|
|
# /foo/bar -> {controller => 'foo', action => 'baz'}
|
|
# {controller => 'foo', action => 'bar'}
|
|
my $foo = $r->under('/foo')->to('foo#baz');
|
|
$foo->get('/bar')->to('#bar');
|
|
|
|
The actual action code for this destination needs to return a true value or the dispatch chain will be broken, this can
|
|
be a very powerful tool for authentication.
|
|
|
|
# /blackjack -> {cb => sub {...}}
|
|
# {controller => 'hideout', action => 'blackjack'}
|
|
my $auth = $r->under('/' => sub ($c) {
|
|
|
|
# Authenticated
|
|
return 1 if $c->req->headers->header('X-Bender');
|
|
|
|
# Not authenticated
|
|
$c->render(text => "You're not Bender.", status => 401);
|
|
return undef;
|
|
});
|
|
$auth->get('/blackjack')->to('hideout#blackjack');
|
|
|
|
Broken dispatch chains can be continued by calling L<Mojolicious::Controller/"continue">, this allows for example,
|
|
non-blocking operations to finish before reaching the next dispatch cycle.
|
|
|
|
my $maybe = $r->under('/maybe' => sub ($c) {
|
|
|
|
# Wait 3 seconds and then give visitors a 50% chance to continue
|
|
Mojo::IOLoop->timer(3 => sub {
|
|
|
|
# Loser
|
|
return $c->render(text => 'No luck.') unless int rand 2;
|
|
|
|
# Winner
|
|
$c->continue;
|
|
});
|
|
|
|
return undef;
|
|
});
|
|
$maybe->get('/')->to('maybe#winner');
|
|
|
|
Every destination is just a snapshot of the stash at the time the route matched, and only the C<format> value is shared
|
|
by all of them. For a little more power you can introspect the preceding and succeeding destinations with
|
|
L<Mojolicious::Controller/"match">.
|
|
|
|
# Action of the fourth dispatch cycle
|
|
my $action = $c->match->stack->[3]{action};
|
|
|
|
=head2 Formats
|
|
|
|
File extensions like C<.html> and C<.txt> at the end of a route are automatically detected and stored in the stash
|
|
value C<format>.
|
|
|
|
# /foo -> {controller => 'foo', action => 'bar'}
|
|
# /foo.html -> {controller => 'foo', action => 'bar', format => 'html'}
|
|
# /foo.txt -> {controller => 'foo', action => 'bar', format => 'txt'}
|
|
$r->get('/foo')->to('foo#bar');
|
|
|
|
This for example, allows multiple templates in different formats to share the same action code. Restrictive
|
|
placeholders can also be used to limit the allowed formats.
|
|
|
|
# /foo.txt -> undef
|
|
# /foo.rss -> {controller => 'foo', action => 'bar', format => 'rss'}
|
|
# /foo.xml -> {controller => 'foo', action => 'bar', format => 'xml'}
|
|
$r->get('/foo' => [format => ['rss', 'xml']])->to('foo#bar');
|
|
|
|
A C<format> value can also be passed to L<Mojolicious::Controller/"url_for">.
|
|
|
|
# /foo/bar.txt -> {controller => 'foo', action => 'bar', format => 'txt'}
|
|
$r->get('/foo/:action')->to('foo#')->name('baz');
|
|
|
|
# Generate URL "/foo/bar.txt" for route "baz"
|
|
my $url = $c->url_for('baz', action => 'bar', format => 'txt');
|
|
|
|
Or you can just disable format detection with a special type of restrictive placeholder, which gets inherited by nested
|
|
routes, and then re-enable it on demand.
|
|
|
|
# /foo -> {controller => 'foo', action => 'bar'}
|
|
# /foo.html -> undef
|
|
$r->get('/foo' => [format => 0])->to('foo#bar');
|
|
|
|
# /foo -> {controller => 'foo', action => 'bar'}
|
|
# /foo.html -> undef
|
|
# /baz -> undef
|
|
# /baz.txt -> {controller => 'baz', action => 'yada', format => 'txt'}
|
|
# /baz.html -> {controller => 'baz', action => 'yada', format => 'html'}
|
|
# /baz.xml -> undef
|
|
my $inactive = $r->under([format => 0]);
|
|
$inactive->get('/foo')->to('foo#bar');
|
|
$inactive->get('/baz' => [format => ['txt', 'html']])->to('baz#yada');
|
|
|
|
=head2 WebSockets
|
|
|
|
With the method L<Mojolicious::Routes::Route/"websocket"> you can restrict access to WebSocket handshakes, which are
|
|
normal C<GET> requests with some additional information.
|
|
|
|
# /echo (WebSocket handshake)
|
|
$r->websocket('/echo')->to('foo#echo');
|
|
|
|
# Controller
|
|
package MyApp::Controller::Foo;
|
|
use Mojo::Base 'Mojolicious::Controller', -signatures;
|
|
|
|
# Action
|
|
sub echo ($self) {
|
|
$self->on(message => sub ($self, $msg) {
|
|
$self->send("echo: $msg");
|
|
});
|
|
}
|
|
|
|
1;
|
|
|
|
The connection gets established when you respond to the WebSocket handshake request with a C<101> response status,
|
|
which happens automatically if you subscribe to an event with L<Mojolicious::Controller/"on"> or send a message with
|
|
L<Mojolicious::Controller/"send"> right away.
|
|
|
|
GET /echo HTTP/1.1
|
|
Host: mojolicious.org
|
|
User-Agent: Mojolicious (Perl)
|
|
Connection: Upgrade
|
|
Upgrade: websocket
|
|
Sec-WebSocket-Key: IDM3ODE4NDk2MjA1OTcxOQ==
|
|
Sec-WebSocket-Version: 13
|
|
|
|
HTTP/1.1 101 Switching Protocols
|
|
Server: Mojolicious (Perl)
|
|
Date: Tue, 03 Feb 2015 17:08:24 GMT
|
|
Connection: Upgrade
|
|
Upgrade: websocket
|
|
Sec-WebSocket-Accept: SWsp5N2iNxPbHlcOTIw8ERvyVPY=
|
|
|
|
=head2 Catch-all route
|
|
|
|
Since routes match in the order in which they were defined, you can catch all requests that did not match in your last
|
|
route with an optional wildcard placeholder.
|
|
|
|
# * /*
|
|
$r->any('/*whatever' => {whatever => ''} => sub ($c) {
|
|
my $whatever = $c->param('whatever');
|
|
$c->render(text => "/$whatever did not match.", status => 404);
|
|
});
|
|
|
|
=head2 Conditions
|
|
|
|
Conditions such as C<headers>, C<agent> and C<host> from L<Mojolicious::Plugin::HeaderCondition> can be applied to any
|
|
route with the method L<Mojolicious::Routes::Route/"requires">, and allow even more powerful route constructs.
|
|
|
|
# / (Origin: http://perl.org)
|
|
$r->get('/')->requires(headers => {Origin => qr/perl\.org/})->to('foo#bar');
|
|
|
|
# / (Firefox)
|
|
$r->get('/')->requires(agent => qr/Firefox/)->to('browser-test#firefox');
|
|
|
|
# / (Internet Explorer)
|
|
$r->get('/')->requires(agent => qr/Internet Explorer/)->to('browser-test#ie');
|
|
|
|
# http://docs.mojolicious.org/Mojolicious
|
|
$r->get('/')->requires(host => 'docs.mojolicious.org')->to('perldoc#index');
|
|
|
|
Just be aware that conditions are too complex for the routing cache, which normally speeds up recurring requests, and
|
|
can therefore reduce performance.
|
|
|
|
=head2 Hooks
|
|
|
|
Hooks operate outside the routing system and allow you to extend the framework itself by sharing code with all requests
|
|
indiscriminately through L<Mojolicious/"hook">, which makes them a very powerful tool especially for plugins.
|
|
|
|
# Application
|
|
package MyApp;
|
|
use Mojo::Base 'Mojolicious', -signatures;
|
|
|
|
sub startup ($self) {
|
|
|
|
# Check all requests for a "/test" prefix
|
|
$self->hook(before_dispatch => sub ($c) {
|
|
$c->render(text => 'This request did not reach the router.') if $c->req->url->path->contains('/test');
|
|
});
|
|
|
|
# These will not be reached if the hook above renders a response
|
|
my $r = $self->routes;
|
|
$r->get('/welcome')->to('foo#welcome');
|
|
$r->post('/bye')->to('foo#bye');
|
|
}
|
|
|
|
1;
|
|
|
|
Post-processing the response to add or remove headers is a very common use.
|
|
|
|
# Make sure static files are cached
|
|
$app->hook(after_static => sub ($c) {
|
|
$c->res->headers->cache_control('max-age=3600, must-revalidate');
|
|
});
|
|
|
|
# Remove a default header
|
|
$app->hook(after_dispatch => sub ($c) {
|
|
$c->res->headers->remove('Server');
|
|
});
|
|
|
|
Same for pre-processing the request.
|
|
|
|
# Choose template variant based on request headers
|
|
$app->hook(before_dispatch => sub ($c) {
|
|
return unless my $agent = $c->req->headers->user_agent;
|
|
$c->stash(variant => 'ie') if $agent =~ /Internet Explorer/;
|
|
});
|
|
|
|
Or more advanced extensions to add monitoring to your application.
|
|
|
|
# Forward exceptions to a web service
|
|
$app->hook(after_dispatch => sub ($c) {
|
|
return unless my $e = $c->stash('exception');
|
|
$c->ua->post('https://example.com/bugs' => form => {exception => $e});
|
|
});
|
|
|
|
You can even extend much of the core functionality.
|
|
|
|
# Make controller object available to actions as $_
|
|
$app->hook(around_action => sub ($next, $c, $action, $last) {
|
|
local $_ = $c;
|
|
return $next->();
|
|
});
|
|
|
|
# Pass route name as argument to actions
|
|
$app->hook(around_action => sub ($next, $c, $action, $last) {
|
|
return $c->$action($c->current_route);
|
|
});
|
|
|
|
For a full list of available hooks see L<Mojolicious/"HOOKS">.
|
|
|
|
=head1 ADVANCED
|
|
|
|
Less commonly used and more powerful features.
|
|
|
|
=head2 Shortcuts
|
|
|
|
To make route generation more expressive, you can also add your own shortcuts with
|
|
L<Mojolicious::Routes/"add_shortcut">.
|
|
|
|
# Simple "resource" shortcut
|
|
$r->add_shortcut(resource => sub ($r, $name) {
|
|
|
|
# Prefix for resource
|
|
my $resource = $r->any("/$name")->to("$name#");
|
|
|
|
# Render a list of resources
|
|
$resource->get('/')->to('#index')->name($name);
|
|
|
|
# Render a form to create a new resource (submitted to "store")
|
|
$resource->get('/create')->to('#create')->name("create_$name");
|
|
|
|
# Store newly created resource (submitted by "create")
|
|
$resource->post->to('#store')->name("store_$name");
|
|
|
|
# Render a specific resource
|
|
$resource->get('/:id')->to('#show')->name("show_$name");
|
|
|
|
# Render a form to edit a resource (submitted to "update")
|
|
$resource->get('/:id/edit')->to('#edit')->name("edit_$name");
|
|
|
|
# Store updated resource (submitted by "edit")
|
|
$resource->put('/:id')->to('#update')->name("update_$name");
|
|
|
|
# Remove a resource
|
|
$resource->delete('/:id')->to('#remove')->name("remove_$name");
|
|
|
|
return $resource;
|
|
});
|
|
|
|
# GET /users -> {controller => 'users', action => 'index'}
|
|
# GET /users/create -> {controller => 'users', action => 'create'}
|
|
# POST /users -> {controller => 'users', action => 'store'}
|
|
# GET /users/23 -> {controller => 'users', action => 'show', id => 23}
|
|
# GET /users/23/edit -> {controller => 'users', action => 'edit', id => 23}
|
|
# PUT /users/23 -> {controller => 'users', action => 'update', id => 23}
|
|
# DELETE /users/23 -> {controller => 'users', action => 'remove', id => 23}
|
|
$r->resource('users');
|
|
|
|
=head2 Rearranging routes
|
|
|
|
From application startup until the first request has arrived, all routes can still be moved around or even removed with
|
|
methods like L<Mojolicious::Routes::Route/"add_child"> and L<Mojolicious::Routes::Route/"remove">.
|
|
|
|
# GET /example/show -> {controller => 'example', action => 'show'}
|
|
my $show = $r->get('/show')->to('example#show');
|
|
$r->any('/example')->add_child($show);
|
|
|
|
# Nothing
|
|
$r->get('/secrets/show')->to('secrets#show')->name('show_secrets');
|
|
$r->find('show_secrets')->remove;
|
|
|
|
Especially for rearranging routes created by plugins this can be very useful, to find routes by their name you can use
|
|
L<Mojolicious::Routes::Route/"find">.
|
|
|
|
# GET /example/test -> {controller => 'example', action => 'test'}
|
|
$r->get('/something/else')->to('something#else')->name('test');
|
|
my $test = $r->find('test');
|
|
$test->pattern->parse('/example/test');
|
|
$test->pattern->defaults({controller => 'example', action => 'test'});
|
|
|
|
Even the route pattern and destination can still be changed with L<Mojolicious::Routes::Pattern/"parse"> and
|
|
L<Mojolicious::Routes::Pattern/"defaults">.
|
|
|
|
=head2 Adding conditions
|
|
|
|
You can also add your own conditions with the method L<Mojolicious::Routes/"add_condition">. All conditions are
|
|
basically router plugins that run every time a new request arrives, and which need to return a true value for the route
|
|
to match.
|
|
|
|
# A condition that randomly allows a route to match
|
|
$r->add_condition(random => sub ($route, $c, $captures, $num) {
|
|
|
|
# Loser
|
|
return undef if int rand $num;
|
|
|
|
# Winner
|
|
return 1;
|
|
});
|
|
|
|
# /maybe (25% chance)
|
|
$r->get('/maybe')->requires(random => 4)->to('foo#bar');
|
|
|
|
Use whatever request information you need.
|
|
|
|
# A condition to check query parameters (useful for mock web services)
|
|
$r->add_condition(query => sub ($route, $c, $captures, $hash) {
|
|
|
|
for my $key (keys %$hash) {
|
|
my $param = $c->req->url->query->param($key);
|
|
return undef unless defined $param && $param eq $hash->{$key};
|
|
}
|
|
|
|
return 1;
|
|
});
|
|
|
|
# /hello?to=world&test=1
|
|
$r->get('/hello')->requires(query => {test => 1, to => 'world'})->to('foo#bar');
|
|
|
|
=head2 Condition plugins
|
|
|
|
You can also package your conditions as reusable plugins.
|
|
|
|
# Plugin
|
|
package Mojolicious::Plugin::WerewolfCondition;
|
|
use Mojo::Base 'Mojolicious::Plugin', -signatures;
|
|
|
|
use Astro::MoonPhase;
|
|
|
|
sub register ($self, $app, $conf) {
|
|
|
|
# Add "werewolf" condition
|
|
$app->routes->add_condition(werewolf => sub ($route, $c, $captures, $days) {
|
|
|
|
# Keep the werewolves out!
|
|
return undef if abs(14 - (phase(time))[2]) > ($days / 2);
|
|
|
|
# It's ok, no werewolf
|
|
return 1;
|
|
});
|
|
}
|
|
|
|
1;
|
|
|
|
Now just load the plugin and you are ready to use the condition in all your applications.
|
|
|
|
# Application
|
|
package MyApp;
|
|
use Mojo::Base 'Mojolicious', -signatures;
|
|
|
|
sub startup ($self) {
|
|
|
|
# Plugin
|
|
$self->plugin('WerewolfCondition');
|
|
|
|
# /hideout (keep them out for 4 days after full moon)
|
|
$self->routes->get('/hideout')->requires(werewolf => 4)->to(controller => 'foo', action => 'bar');
|
|
}
|
|
|
|
1;
|
|
|
|
=head2 Mount applications
|
|
|
|
The easiest way to embed one application into another is L<Mojolicious::Plugin::Mount>, which allows you to mount whole
|
|
self-contained applications under a domain and/or prefix.
|
|
|
|
use Mojolicious::Lite -signatures;
|
|
|
|
# Whole application mounted under "/prefix"
|
|
plugin Mount => {'/prefix' => '/home/sri/myapp/script/myapp'};
|
|
|
|
# Mount application with subdomain
|
|
plugin Mount => {'test.example.com' => '/home/sri/myapp2.pl'};
|
|
|
|
# Normal route
|
|
get '/' => sub ($c) {
|
|
$c->render(text => 'Hello World!');
|
|
};
|
|
|
|
app->start;
|
|
|
|
=head2 Embed applications
|
|
|
|
For a little more power you can also embed applications by using them instead of a controller. This allows for example,
|
|
the use of the L<Mojolicious::Lite> domain specific language in normal L<Mojolicious> controllers.
|
|
|
|
# Controller
|
|
package MyApp::Controller::Bar;
|
|
use Mojolicious::Lite -signatures;
|
|
|
|
# /hello
|
|
get '/hello' => sub ($c) {
|
|
my $name = $c->param('name');
|
|
$c->render(text => "Hello $name.");
|
|
};
|
|
|
|
1;
|
|
|
|
With the attribute L<Mojolicious::Routes::Route/"partial">, you can allow the route to partially match and use only the
|
|
remaining path in the embedded application, the base path will be passed along in the C<path> stash value.
|
|
|
|
# /foo/*
|
|
$r->any('/foo')->partial(1)->to('bar#', name => 'Mojo');
|
|
|
|
A minimal embeddable application is nothing more than a subclass of L<Mojolicious>, containing a C<handler> method
|
|
accepting L<Mojolicious::Controller> objects.
|
|
|
|
package MyApp::Controller::Bar;
|
|
use Mojo::Base 'Mojolicious', -signatures;
|
|
|
|
sub handler ($self, $c) {
|
|
$c->res->code(200);
|
|
my $name = $c->param('name');
|
|
$c->res->body("Hello $name.");
|
|
}
|
|
|
|
1;
|
|
|
|
The host application will only share very little information with the embedded application through the stash. So you
|
|
cannot currently use route placeholders in routes leading to embedded applications, since that would cause problems
|
|
with L<Mojolicious::Controller/"url_for">.
|
|
|
|
=head2 Application plugins
|
|
|
|
You can even package applications as self-contained reusable plugins.
|
|
|
|
# Plugin
|
|
package Mojolicious::Plugin::MyEmbeddedApp;
|
|
use Mojo::Base 'Mojolicious::Plugin', -signatures;
|
|
|
|
sub register ($self, $app, $conf) {
|
|
|
|
# Automatically add route
|
|
$app->routes->any('/foo')->partial(1)->to(app => EmbeddedApp::app());
|
|
}
|
|
|
|
package EmbeddedApp;
|
|
use Mojolicious::Lite;
|
|
|
|
get '/bar' => 'bar';
|
|
|
|
1;
|
|
__DATA__
|
|
@@ bar.html.ep
|
|
Hello World!
|
|
|
|
The C<app> stash value, which won't be inherited by nested routes, can be used for already instantiated applications.
|
|
Now just load the plugin and you're done.
|
|
|
|
# Application
|
|
package MyApp;
|
|
use Mojo::Base 'Mojolicious', -signatures;
|
|
|
|
sub startup ($self) {
|
|
|
|
# Plugin
|
|
$self->plugin('MyEmbeddedApp');
|
|
}
|
|
|
|
1;
|
|
|
|
=head1 MORE
|
|
|
|
You can continue with L<Mojolicious::Guides> now or take a look at the L<Mojolicious
|
|
wiki|https://github.com/mojolicious/mojo/wiki>, which contains a lot more documentation and examples by many different
|
|
authors.
|
|
|
|
=head1 SUPPORT
|
|
|
|
If you have any questions the documentation might not yet answer, don't hesitate to ask in the
|
|
L<Forum|https://forum.mojolicious.org> or the official IRC channel C<#mojo> on C<chat.freenode.net>
|
|
(L<chat now!|https://webchat.freenode.net/#mojo>).
|
|
|
|
=cut
|