NAME

   Mojolicious::Plugin::CanonicalURL - Ensures canonical URLs via
   redirection

STATUS

SYNOPSIS

     # Redirects all URLs that have a slash to their no-slash equivalent with a 301 status code.
     $app->plugin('CanonicalURL');

     # Redirects all requests whose paths have no slash to their slash equivalent with a 301 status code.
     $app->plugin('CanonicalURL', { end_with_slash => 1 });

     # Canonicalize any requests whose paths match the regex qr/foo/.
     $app->plugin('CanonicalURL', { should_canonicalize_request => qr/foo/ });

     # Canonicalize only requests with path /foo EXACTLY.
     $app->plugin('CanonicalURL', { should_canonicalize_request => '/foo' });

     # Same as above, but using a subroutine. $_ contains the Mojolicious::Controller for the request.
     use Mojolicious::Plugin::CanonicalURL 'remove_trailing_slashes';
     $app->plugin('CanonicalURL', { should_canonicalize_request => sub { remove_trailing_slashes($_->req->url->path) eq '/foo' } });

     # Same as above, but faster. Code is inlined into the subroutine. This is equally as fast as "should_canonicalize_request => '/foo'" above
     # 'return $next->() unless ' added to the beginning of the string, and ';' added to the end automatically.
     $app->plugin('CanonicalURL', { should_canonicalize_request => \q{remove_trailing_slashes($c->req->url->path) eq '/foo'} } });

     # Same as above, with explicit 'return $next->() unless' at the beginning and ';' at the end.
     $app->plugin('CanonicalURL', { should_canonicalize_request => \q{return $next->() unless remove_trailing_slashes($c->req->url->path) eq '/foo';} } });

     # for multiline code to be inlined, inline_code is recommended instead
     # of should_canonicalize_request
     $app->plugin('CanonicalURL', {
       inline_code => q{
         my $path_no_slashes = remove_trailing_slashes($c->req->url->path);
         return $next->() if $path_no_slashes eq $path1;
         return $next->() if $path_no_slashes eq $path2;
         return $next->() if $path_no_slashes eq $path3;
       },
       # you may pass your own variables for use in inline_code
       captures => {
         '$path1' => \$path1,
         '$path2' => \$path2,
         '$path3' => \$path3,
       },
     });

     # canoncalize all requests that start with /foo
     $app->plugin('CanonicalURL', { should_canonicalize_request => qr{^/foo} });

     # Same as above, but faster because it uses index()
     $app->plugin('CanonicalURL', { should_canonicalize_request => {starts_with => '/foo'} });

     # canoncalize all requests that start with /foo or /bar
     $app->plugin('CanonicalURL', { should_canonicalize_request => [qr{^/foo}, qr{^/bar}] });

     # Same as above, but faster than qr{^/foo} or qr{^/bar} because it uses index()
     $app->plugin('CanonicalURL', { should_canonicalize_request => [{starts_with => '/foo'}, {starts_with => '/bar'}] });

     # Canonicalize all requests except the one with the path /foo
     $app->plugin('CanonicalURL', { should_not_canonicalize_request => '/foo' });

     # All options available to should_canonicalize_request are available to should_not_canonicalize_request.
     # Canonicalize all requests except the one with the path /foo, any request matching qr/bar/, any request
     # starting with /baz, any request with the path /qux/, or any request with the host example.com
     $app->plugin('CanonicalURL', { should_not_canonicalize_request => [
         '/foo',
         qr/bar/,
         {starts_with => '/baz'}
         sub { $_->req->url->path eq '/qux/' },
         \q{$c->req->url->to_abs->host eq 'example.com'},
       ],
     });

     # should_canonicalize_request and should_not_canonicalize_request can be used together
     # All request must start with /foo and must NOT match qr/bar/ to be canonicalized
     # /foo/baz matches
     # /foo/bar does not match
     $app->plugin('CanonicalURL', {
         should_canonicalize_request => {starts_with => '/foo'},
         should_not_canonicalize_request => qr/bar/,
     });

DESCRIPTION

   Mojolicious::Plugin::CanonicalURL is a flexible and fast
   Mojlicious::Plugin to give you control over canonicalizing your URLs.
   Mojolicious::Plugin::CanonicalURL uses Sub::Quote to build the
   subroutine used as an "around_action" in Mojolicious hook based on the
   "OPTIONS" you pass in to make it as fast as possible.
   Mojolicious::Plugin::CanonicalURL by default redirects URLs ending with
   a slash in their path to their non-slash equivalent. All redirected
   URLs will have a status code of 301
   <https://en.wikipedia.org/wiki/HTTP_301>.

   "end_with_slash" can be set to 1 to instead require that canonicalized
   URLs end with a slash.

   "should_canonicalize_request" and/or "should_not_canonicalize_request"
   can be set to override the default that all URLs will be canonicalized.

   When redirecting to the canonicalized path, all other attributes of the
   Mojo::URL for the request will remain the same (such as query
   parameters); only "path" in Mojo::URL will change to the canonicalized
   form.

   Mojolicious::Plugin::CanonicalURL will remove multiple trailing slashes
   and replace them with one slash if "end_with_slash" is a true value, or
   no slashes if "end_with_slash" is a false value.

   By default, Mojolicious::Plugin::CanonicalURL only works for dynamic
   actions that have methods that are called. This means that, by default,
   this plugin will not work for Mojolicious::Lite routes like this:

     get '/' => {text => 'I ♥ Mojolicious!'};

   Or routes in a full app like this:

     sub startup {
       my ($self) = @_;

       my $routes = $self->routes;
       $routes->get('/' => {text => 'I ♥ Mojolicious!'});
     }

   See "canonicalize_before_render" to canonicalize non-dynamic actions.

OPTIONS

end_with_slash

     # Require all canonicalized URLs do not end with a slash. This is the default.
     $app->plugin('CanonicalURL', { end_with_slash => undef });

     # Require all canonicalized URLs end with a slash.
     $app->plugin('CanonicalURL', { end_with_slash => 1 });

   Sets whether canonicalized URLs should end with a slash or not. Default
   is undef (canonicalzed URLs will not end with a slash). This matches up
   with how Mojolicious generates "Named-routes" in
   Mojolicious::Guides::Routing, which have no slash at the end. Make sure
   that if you set end_with_slash to 1 and you used named routes that you
   keep this in mind so that you do not redirect anytime a named route URL
   is used. A role using "after-methods" in Class::Method::Modifiers may
   be the correct way to add trailing slashes by setting the
   "trailing_slash" in Mojo::Path to 1 on the Mojo::URL.

   If "end_with_slash" is false, an exception is made for the root path,
   /, according to RFC 2616
   <https://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html>:

     Note that the absolute path cannot be empty; if none is present in the original URI, it MUST be given as "/" (the server root).

   Mojolicious::Plugin::CanonicalURL will replace multiple trailing
   slashes with one if "end_with_slash" is true, or zero if
   "end_with_slash" is false.

should_canonicalize_request

   "should_canonicalize_request" is responsible for determining if a given
   request should be canonicalized or not. "should_canonicalize_request"
   can be several different types of values.

   "should_canonicalize_request" may be combined with
   "should_not_canonicalize_request" to specify conditions that must be
   met and conditions that must not be met for a request to be
   canonicalized.

 SCALAR

   If should_canonicalize_request is passed a scalar value, it will only
   canonicalize a reqeust if its path ("path" in Mojo::URL) matches the
   provided path. Note that all paths are compared without any trailing
   slashes, regardless of the value of "end_with_slash". All scalar values
   should be valid requests paths that start with a /.

     # will only canonicalize paths where $c->req->url->path eq '/foo'
     $app->plugin('CanonicalURL', { should_canonicalize_request => '/foo' });

 REGEXP

   If "should_canonicalize_request" is passed regex, the regex will be
   compared to the path of the request. If the regex does not match
   against the path, the request will not be canonicalized.

     # $c->req->url->path =~ qr/foo/ must be true for a request to be canonicalized
     $app->plugin('CanonicalURL', { should_canonicalize_request => qr/foo/ });

 CODE

   If "should_canonicalize_request" is passed a subroutine, $_ will
   contain the Mojolicious::Controller for the current request, and the
   truth value returned by the subroutine will determine if the request is
   canonicalized. If a true value is returned, the request will be
   canonicalized. If a false value is returned, the request will not be
   canonicalized.

     # will only canonicalize request if path eq '/foo'
     $app->plugin('CanonicalURL', {
       should_canonicalize_request => sub {
         return remove_trailing_slashes($_->req->url->path) ne '/foo';
       },
     });

 SCALAR REFERENCE

   "SCALAR REFERENCE" is similar to "CODE", except that the code will be
   inlined into the generated method, avoiding the extra subroutine call.

     # 'return $next->() unless ' is added if there is no return at the beginning, and ';' added at the end if it does not exist
     $app->plugin('CanonicalURL', {
       should_canonicalize_request => \q{remove_trailing_slashes($c->req->url->path) eq '/foo'},
     });

   $next and $c from "around_action" in Mojolicious are available. "return
   $next->() unless " is added if the string does not begin with return,
   and ; is added to the end if it does not exist. If manually returning
   without performing canonicalization, make sure to return "$next->()".

     return $next->();

   You also can have your own variables through "captures":

     my $my_path = '/foo';
     $app->plugin('CanonicalURL', {
       captures => { '$my_path' => \$my_path },
       should_canonicalize_request => \q{remove_trailing_slashes($c->req->url->path) eq $my_path},
     });

   But be careful not to use any "RESERVED VARIABLE NAMES".

   This is really meant for small one-liners that evaluate to a true/false
   value. For longer, more custom code, see "inline_code".

   When comparing paths manually, be sure to handle the case where a path
   may or may not have a slash at the end.
   Mojolicious::Plugin::CanonicalURL provides "remove_trailing_slashes",
   which can be used in inlined code or exported.

 HASH

   "should_canonicalize_request" may be provided a hash reference to
   specify what path a request must start with to be canonicalized. The
   key must be starts_with, and the value should be a valid path starting
   with a slash.

     # all reqeusts must start with '/foo' to be canonicalized
     $app->plugin('CanonicalURL', { should_canonicalize_request => { starts_with => '/foo' } });

   In the future, this may allow other keys, like contains, which could be
   a faster form of something like qr/bar/.

 ARRAY

   "should_canonicalize_request" can be passed an array reference with any
   number of elements as defined by "SCALAR", "REGEXP", "CODE", "SCALAR
   REFERENCE" and "HASH", and these conditions will be or'ed together, so
   that a request will be canonicalized if any of the conditions is true.
   Note that a SCALAR REFERENCE should just be a condition, without a
   return or trailing ;.

   "captures" may be used if a scalar reference is provided in the array.

     # Canonicalize any request whose path equals '/foo', any request whose path matches qr/bar/, any request
     # whose path starts with '/baz', any request with the path '/qux/', or any request that has the host example.com
     $app->plugin('CanonicalURL', { should_canonicalize_request => [
         '/foo',
         qr/bar/,
         {starts_with => '/baz'},
         sub { $_->req->url->path eq '/qux/' },
         \q{$c->req->url->to_abs->host eq 'example.com'},
       ],
     });

should_not_canonicalize_request

   "should_not_canonicalize_request" is responsible for determining if a
   given request should be canonicalized or not.
   "should_not_canonicalize_request" accepts the same types of values as
   "should_canonicalize_request", but handles them oppositely.

   "should_not_canonicalize_request" may be combined with
   "should_canonicalize_request" to specify conditions that must not be
   met and conditions that must be met for a request to be canonicalized.

 SCALAR

   If should_not_canonicalize_request is passed a scalar value, it will
   only canonicalize a reqeust if its path ("path" in Mojo::URL) does not
   match the provided path. Note that all paths are compared without any
   trailing slashes, regardless of the value of "end_with_slash". All
   scalar values should be valid requests paths that start with a /.

     # will only canonicalize paths where $c->req->url->path ne '/foo'
     $app->plugin('CanonicalURL', { should_not_canonicalize_request => '/foo' });

 REGEXP

   If "should_not_canonicalize_request" is passed regex, the regex will be
   compared to the path of the request. If the regex matches against the
   path, the request will not be canonicalized.

     # $c->req->url->path !~ qr/foo/ must be true for a request to be canonicalized
     $app->plugin('CanonicalURL', { should_not_canonicalize_request => qr/foo/ });

 CODE

   If "should_not_canonicalize_request" is passed a subroutine, $_ will
   contain the Mojolicious::Controller for the current request, and the
   truth value returned by the subroutine will determine if the request is
   canonicalized. If a true value is returned, the request will not be
   canonicalized. If a false value is returned, the request will be
   canonicalized.

     # will not canonicalize request if path eq '/foo'
     $app->plugin('CanonicalURL', {
       should_not_canonicalize_request => sub {
         return remove_trailing_slashes($_->req->url->path) eq '/foo';
       },
     });

 SCALAR REFERENCE

   SCALAR REFERECE is similar to CODE, except that the code will be
   inlined into the generated method, avoiding the extra subroutine call.

     # 'return $next->() if ' is added if there is no return at the beginning, and ';' added at the end if it does not exist
     $app->plugin('CanonicalURL', {
       should_not_canonicalize_request => \q{remove_trailing_slashes($c->req->url->path) eq '/foo'},
     });

   $next and $c from "around_action" in Mojolicious are available. "return
   $next->() if " is added if the string does not begin with return, and ;
   is added to the end if it does not exist. If manually returning without
   performing canonicalization, make sure to return "$next->()".

     return $next->();

   You also can have your own variables through "captures":

     my $my_path = '/foo';
     $app->plugin('CanonicalURL', {
       captures => { '$my_path' => \$my_path },
       should_not_canonicalize_request => \q{remove_trailing_slashes($c->req->url->path) eq $my_path},
     });

   But be careful not to use any "RESERVED VARIABLE NAMES".

   This is really meant for small one-liners that evaluate to a true/false
   value. For longer, more custom code, see "inline_code".

   When comparing paths manually, be sure to handle the case where a path
   may or may not have a slash at the end.
   Mojolicious::Plugin::CanonicalURL provides "remove_trailing_slashes",
   which can be used in inlined code or exported.

 HASH

   "should_not_canonicalize_request" may be provided a hash reference to
   specify what paths a request must start with to not be canonicalized.
   The key must be starts_with, and the value should be a valid path
   starting with a slash.

     # all reqeusts must not start with '/foo' to be canonicalized
     $app->plugin('CanonicalURL', { should_not_canonicalize_request => { starts_with => '/foo' } });

   In the future, this may allow other keys, like contains, which could be
   a faster form of something like qr/bar/.

 ARRAY

   "should_not_canonicalize_request" can be passed an array reference with
   any number of elements as defined by SCALAR, REGEXP, CODE, SCALAR
   REFERENCE and HASH, and these conditions will be or'ed together, so
   that a request will not be canonicalized if any of the conditions is
   true. Note that a SCALAR REFERENCE should just be a condition, without
   a return or trailing ;.

   "captures" may be used if a scalar reference is provided in the array.

     # Do not canonicalize any request whose path equals '/foo', any request whose path matches qr/bar/, any request
     # whose path starts with '/baz', any request with the path '/qux/', or any request that has the host example.com
     $app->plugin('CanonicalURL', { should_not_canonicalize_request => [
         '/foo',
         qr/bar/,
         {starts_with => '/baz'},
         sub { $_->req->url->path eq '/qux/' },
         \q{$c->req->url->to_abs->host eq 'example.com'},
       ],
     });

inline_code

   "inline_code" allows you to pass in code that will be run right before
   the code to canonicalize a request runs, but after any code generated
   by "should_canonicalize_request" or "should_not_canonicalize_request".
   This is meant for larger, more custom code than the scalar inline code
   allowed for "should_canonicalize_request" and
   "should_not_canonicalize_request".

     $app->plugin('CanonicalURL', {
       inline_code => q{
         # custom code
         if (...) {
           $c->app->log("Path is " . $c->req->url->path);
           return $next->() if remove_trailing_slashes($c->req->url->path) eq '/foo';
         }
       },
     });

   If "inline_code" is set, "captures" may be used to access your
   variables inside of the code:

     $app->plugin('CanonicalURL', {
       captures => { '$my_var' => \$my_var },
       inline_code => q{
         if (...) {
           # custom code
           $c->app->log("My var is $my_var");
           return $next->() if $c->req->url->path eq $my_var;
         }
       },
     });

   When comparing paths manually, be sure to handle the case where a path
   may or may not have a slash at the end.
   Mojolicious::Plugin::CanonicalURL provides "remove_trailing_slashes",
   which can be used in inlined code or exported.

captures

     my $skip = {
       '/path_one' => 1,
       '/path_two' => 1,
     };

     $app->plugin('CanonicalURL', {
       captures => { '$skip' => \$skip },
       should_canonicalize_request => \'exists $skip->{remove_trailing_slashes($c->req->url->path)}',
     });

     # same as above using inline_code
     $app->plugin('CanonicalURL', {
       captures => { '$skip' => \$skip },
       inline_code => q{
         if (exists $skip->{$c->req->url->path}) {
           return $next->();
         }
       },
     });

   "captures" corresponds to captures in Sub::Quote:

     \%captures is a hashref of variables that will be made available to the code.
     The keys should be the full name of the variable to be made available, including the sigil.
     The values should be references to the values. The variables will contain copies of the values.

   "captures" can only be used when "should_canonicalize_request" or
   "should_not_canonicalize_request" use "SCALAR REFERENCE" or SCALAR
   REFERENCE, or when a scalar reference is provided in "ARRAY" or ARRAY
   for "should_canonicalize_request" or "should_not_canonicalize_request",
   or when "inline_code" is set.

   See "SYNOPSIS" in Sub::Quote's Silly::dragon for an example using
   captures.

canonicalize_before_render

     # Now the before_render hook will be used to canonicalize requests in addition to the around_action hook.
     $app->plugin('CanonicalURL', {
       canonicalize_before_render => 1,
     });

     # useful for actions defined like this in Mojolicious::Lite which will not trigger the around_action hook
     get '/' => {text => 'I ♥ Mojolicious!'};

     # or actions like this in the full app
     sub startup {
       my ($self) = @_;

       my $routes = $self->routes;
       $routes->get('/' => {text => 'I ♥ Mojolicious!'});
     }

   For text routes as shown above, no action is actually called, because
   the text is stored with the route and rendered from the string without
   being wrapped in a subroutine. This means that requests for these
   routes cannot be canonicalized using the "around_action" in Mojolicious
   hook. When using routes of this format, "canonicalize_before_render"
   must be set to 1 so that the "before_render" in Mojolicious hook will
   be used instead to canonicalize these requests. If a request was
   already successfully canonicalized (or redirected by other means) in
   the "around_action" in Mojolicious hook, then the "before_render" in
   Mojolicious hook will return early. However, both the "around_action"
   in Mojolicious and "before_render" in Mojolicious hooks will be called
   when a request does not need to be canonicalized, performing the same
   work twice (the performance of this shouldn't be a problem, especially
   for lite apps).

   By default, "canonicalize_before_render" is undef, meaning that the
   "before_render" in Mojolicious hook is not used to canonicalize
   requests, only the "around_action" in Mojolicious hook is used.

EXPORTED

remove_trailing_slashes

   Removes all trailing slashes from a path. Accepts a string or a blessed
   reference that overloads "", such as Mojo::Path. This is useful for
   examining paths.

     # export to use in your code
     use Mojolicious::Plugin::CanonicalURL 'remove_trailing_slashes';
     $app->plugin('CanonicalURL', { should_canonicalize_request => sub { remove_trailing_slashes($_->req->url->path) eq '/foo' } });

     # or use in code that is inlined
     $app->plugin('CanonicalURL', { should_canonicalize_request => \q{remove_trailing_slashes($_->req->url->path) eq '/foo'} });

   "remove_trailing_slashes" can be exported or used by inlined code, such
   as when "should_canonicalize_request" or
   "should_not_canonicalize_request" use "SCALAR REFERENCE" or SCALAR
   REFERENCE, or when "inline_code" is set.

RESERVED VARIABLE NAMES

   These are the variable names that Mojolicious::Plugin::CanonicalURL
   uses and you should avoid using when declaring your own variables via
   "inline_code", "SCALAR REFERENCE", SCALAR REFERENCE, or "captures":

     * $c

     * $next

     * $_mpcu_path

     * $_mpcu_path_length

     * $_mpcu_path_with_no_slashes_at_the_end

     * $_mpcu_*

AUTHOR

   Adam Hopkins <[email protected]>

COPYRIGHT

   Copyright 2019- Adam Hopkins

LICENSE

   This library is free software; you can redistribute it and/or modify it
   under the same terms as Perl itself.

SEE ALSO

     * Mojolicious

     * Mojolicious::Plugin