# This file documents the revision history for Perl extension Catalyst.
+5.90115 - 2017-05-01
+ - fixes for silent bad behavior in Catalyst::ScriptRole and 'ensure_class_loaded'
+ (hobbs++)
+ - do not require MXRWO if Moose is new enough to have cored it (ether++)
+ - documentation improvements (ether++)
+ - Encoding documentation improvements (colinnewell++)
+ - Improve documentation and test cases for 'abort_chain_on_error_fix' configuration
+ option (melmothx++)
+ - Better debug output when using Hash::MultiValue (tremor69++)
+ - Fixes for detecting debug terminal size (simonamor++)
+
5.90114 - 2016-12-19
- Fixed regression introduced in the last version (5.90113) which caused
application to hang when the action private name contained a string
authority('cpan:MSTROUT');
all_from 'lib/Catalyst/Runtime.pm';
-requires 'List::MoreUtils';
+requires 'List::Util' => '1.45';
requires 'namespace::autoclean' => '0.28';
requires 'namespace::clean' => '0.23';
requires 'MooseX::Emulate::Class::Accessor::Fast' => '0.00903';
requires 'Data::OptList';
requires 'Moose' => '1.03';
requires 'MooseX::MethodAttributes::Role::AttrContainer::Inheritable' => '0.24';
-requires 'MooseX::Role::WithOverloading' => '0.09';
+requires 'MooseX::Role::WithOverloading' => '0.09' unless can_use('Moose', '2.1300');
requires 'Carp' => '1.25';
requires 'Class::C3::Adopt::NEXT' => '0.07';
requires 'CGI::Simple::Cookie' => '1.109';
@author_requires,
map {; $_ => 0 } qw(
File::Copy::Recursive
- Test::Without::Module
Starman
MooseX::Daemonize
Test::NoTabs
'license', => 'http://dev.perl.org/licenses/',
'homepage', => 'http://dev.catalyst.perl.org/',
# r/w: catagits@git.shadowcat.co.uk:Catalyst-Runtime.git
+ # web: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?p=catagits/Catalyst-Runtime.git;a=summary
'repository', => 'git://git.shadowcat.co.uk/catagits/Catalyst-Runtime.git',
);
Catalyst - The Elegant MVC Web Application Framework
-<div>
-
- <a href="https://badge.fury.io/pl/Catalyst-Runtime"><img src="https://badge.fury.io/pl/Catalyst-Runtime.svg" alt="CPAN version" height="18"></a>
- <a href="https://travis-ci.org/perl-catalyst/catalyst-runtime/"><img src="https://api.travis-ci.org/perl-catalyst/catalyst-runtime.png" alt="Catalyst></a>
- <a href="http://cpants.cpanauthors.org/dist/Catalyst-Runtime"><img src="http://cpants.cpanauthors.org/dist/Catalyst-Runtime.png" alt='Kwalitee Score' /></a>
-</div>
-
# SYNOPSIS
See the [Catalyst::Manual](https://metacpan.org/pod/Catalyst::Manual) distribution for comprehensive
documentation and tutorials.
- # Building Catalyst for development
- cpanm --local-lib=~/perl5 local::lib && eval $(perl -I ~/perl5/lib/perl5/ -Mlocal::lib)
- cpanm --installdeps --with-develop .
- perl Makefile.PL
-
# Install Catalyst::Devel for helpers and other development tools
# use the helper to create a new application
catalyst.pl MyApp
`$c->go` will perform a full dispatch on the specified action or method,
with localized `$c->action` and `$c->namespace`. Like `detach`,
`go` escapes the processing of the current request chain on completion, and
-does not return to its cunless blessed $cunless blessed $caller.
+does not return to its caller.
@arguments are arguments to the final destination of $action. @captures are
arguments to the intermediate steps, if any, on the way to the final sub of
Note that << $c->state >> operates in a scalar context which means that all
values it returns are scalar.
+Please note that if an action throws an exception, the value of state
+should no longer be considered the return if the last action. It is generally
+going to be 0, which indicates an error state. Examine $c->error for error
+details.
+
## $c->clear\_errors
Clear errors. You probably don't want to clear the errors unless you are
## $c->last\_error
Returns the most recent error in the stack (the one most recently added...)
-or nothing if there are no errors.
+or nothing if there are no errors. This does not modify the contents of the
+error stack.
## shift\_errors
-shifts the most recently added error off the error stack and returns if. Returns
+shifts the most recently added error off the error stack and returns it. Returns
+nothing if there are no more errors.
+
+## pop\_errors
+
+pops the most recently added error off the error stack and returns it. Returns
nothing if there are no more errors.
## COMPONENT ACCESSORS
## do stuff here..
};
-## $c->uri\_for( $path?, @args?, \\%query\_values? )
+## $c->uri\_for( $path?, @args?, \\%query\_values?, \\$fragment? )
-## $c->uri\_for( $action, \\@captures?, @args?, \\%query\_values? )
+## $c->uri\_for( $action, \\@captures?, @args?, \\%query\_values?, \\$fragment? )
-## $c->uri\_for( $action, \[@captures, @args\], \\%query\_values? )
+## $c->uri\_for( $action, \[@captures, @args\], \\%query\_values?, \\$fragment? )
Constructs an absolute [URI](https://metacpan.org/pod/URI) object based on the application root, the
provided path, and the additional arguments and query parameters provided.
`$c->request->base`; any `@args` are appended as additional path
components; and any `%query_values` are appended as `?foo=bar` parameters.
+**NOTE** If you are using this 'stringy' first argument, we skip encoding and
+allow you to declare something like:
+
+ $c->uri_for('/foo/bar#baz')
+
+Where 'baz' is a URI fragment. We consider this first argument string to be
+'expert' mode where you are expected to create a valid URL and we for the most
+part just pass it through without a lot of internal effort to escape and encode.
+
If the first argument is a [Catalyst::Action](https://metacpan.org/pod/Catalyst::Action) it represents an action which
will have its path resolved using `$c->dispatcher->uri_for_action`. The
optional `\@captures` argument (an arrayref) allows passing the captured
## $app->request\_class\_traits
-An arrayref of [Moose::Role](https://metacpan.org/pod/Moose::Role)s which are applied to the request class.
+An arrayref of [Moose::Role](https://metacpan.org/pod/Moose::Role)s which are applied to the request class. You can
+name the full namespace of the role, or a namespace suffix, which will then
+be tried against the following standard namespace prefixes.
+
+ $MyApp::TraitFor::Request::$trait_suffix
+ Catalyst::TraitFor::Request::$trait_suffix
+
+So for example if you set:
+
+ MyApp->request_class_traits(['Foo']);
+
+We try each possible role in turn (and throw an error if none load)
+
+ Foo
+ MyApp::TraitFor::Request::Foo
+ Catalyst::TraitFor::Request::Foo
+
+The namespace part 'TraitFor::Request' was chosen to assist in backwards
+compatibility with [CatalystX::RoleApplicator](https://metacpan.org/pod/CatalystX::RoleApplicator) which previously provided
+these features in a stand alone package.
## $app->composed\_request\_class
## $app->response\_class\_traits
-An arrayref of [Moose::Role](https://metacpan.org/pod/Moose::Role)s which are applied to the response class.
+An arrayref of [Moose::Role](https://metacpan.org/pod/Moose::Role)s which are applied to the response class. You can
+name the full namespace of the role, or a namespace suffix, which will then
+be tried against the following standard namespace prefixes.
+
+ $MyApp::TraitFor::Response::$trait_suffix
+ Catalyst::TraitFor::Response::$trait_suffix
+
+So for example if you set:
+
+ MyApp->response_class_traits(['Foo']);
+
+We try each possible role in turn (and throw an error if none load)
+
+ Foo
+ MyApp::TraitFor::Response::Foo
+ Catalyst::TraitFor::Responset::Foo
+
+The namespace part 'TraitFor::Response' was chosen to assist in backwards
+compatibility with [CatalystX::RoleApplicator](https://metacpan.org/pod/CatalystX::RoleApplicator) which previously provided
+these features in a stand alone package.
## $app->composed\_response\_class
## handle\_unicode\_encoding\_exception
-Hook to let you customize how encoding errors are handled. By default
-we just throw an exception. Receives a hashref of debug information.
-Example:
+Hook to let you customize how encoding errors are handled. By default
+we just throw an exception and the default error page will pick it up.
+Receives a hashref of debug information. Example of call (from the
+Catalyst internals):
- $c->handle_unicode_encoding_exception({
- param_value => $value,
- error_msg => $_,
- encoding_step => 'params',
- });
+ my $decoded_after_fail = $c->handle_unicode_encoding_exception({
+ param_value => $value,
+ error_msg => $_,
+ encoding_step => 'params',
+ });
+
+The calling code expects to receive a decoded string or an exception.
+
+You can override this for custom handling of unicode errors. By
+default we just die. If you want a custom response here, one approach
+is to throw an HTTP style exception, instead of returning a decoded
+string or throwing a generic exception.
+
+ sub handle_unicode_encoding_exception {
+ my ($c, $params) = @_;
+ HTTP::Exception::BAD_REQUEST->throw(status_message=>$params->{error_msg});
+ }
+
+Alternatively you can 'catch' the error, stash it and write handling code later
+in your application:
+
+ sub handle_unicode_encoding_exception {
+ my ($c, $params) = @_;
+ $c->stash(BAD_UNICODE_DATA=>$params);
+ # return a dummy string.
+ return 1;
+ }
+
+<B>NOTE:</b> Please keep in mind that once an error like this occurs,
+the request setup is still ongoing, which means the state of `$c` and
+related context parts like the request and response may not be setup
+up correctly (since we haven't finished the setup yet). If you throw
+an exception the setup is aborted.
## $c->setup\_log
## $app->composed\_stats\_class
-this is the stats\_class composed with any 'stats\_class\_traits'.
+this is the stats\_class composed with any 'stats\_class\_traits'. You can
+name the full namespace of the role, or a namespace suffix, which will then
+be tried against the following standard namespace prefixes.
+
+ $MyApp::TraitFor::Stats::$trait_suffix
+ Catalyst::TraitFor::Stats::$trait_suffix
+
+So for example if you set:
+
+ MyApp->stats_class_traits(['Foo']);
+
+We try each possible role in turn (and throw an error if none load)
+
+ Foo
+ MyApp::TraitFor::Stats::Foo
+ Catalyst::TraitFor::Stats::Foo
+
+The namespace part 'TraitFor::Stats' was chosen to assist in backwards
+compatibility with [CatalystX::RoleApplicator](https://metacpan.org/pod/CatalystX::RoleApplicator) which previously provided
+these features in a stand alone package.
## $c->use\_stats
decoded, this means that applications using this mode can correctly handle URIs including the %2F character
(i.e. with `AllowEncodedSlashes` set to `On` in Apache).
- Given that this method of path resolution is probably more correct, it is recommended that you use
+ Given that this method of path resolution is provably more correct, it is recommended that you use
this unless you have a specific need to deploy your application in a non-standard environment, and you are
aware of the implications of not being able to handle encoded URI paths correctly.
- `using_frontend_proxy_path` - Enabled [Plack::Middleware::ReverseProxyPath](https://metacpan.org/pod/Plack::Middleware::ReverseProxyPath) on your application (if
installed, otherwise log an error). This is useful if your application is not running on the
'root' (or /) of your host server. **NOTE** if you use this feature you should add the required
-middleware to your project dependency list since it's not automatically a dependency of [Catalyst](https://metacpan.org/pod/Catalyst).
+middleware to your project dependency list since its not automatically a dependency of [Catalyst](https://metacpan.org/pod/Catalyst).
This has been done since not all people need this feature and we wish to restrict the growth of
[Catalyst](https://metacpan.org/pod/Catalyst) dependencies.
- `encoding` - See ["ENCODING"](#encoding)
- `abort_chain_on_error_fix`
- When there is an error in an action chain, the default behavior is to continue
- processing the remaining actions and then catch the error upon chain end. This
- can lead to running actions when the application is in an unexpected state. If
- you have this issue, setting this config value to true will promptly exit a
- chain when there is an error raised in any action (thus terminating the chain
- early.)
+ Defaults to true.
- use like:
+ When there is an error in an action chain, the default behavior is to
+ abort the processing of the remaining actions to avoid running them
+ when the application is in an unexpected state.
- __PACKAGE__->config(abort_chain_on_error_fix => 1);
+ Before version 5.90070, the default used to be false. To keep the old
+ behaviour, you can explicitly set the value to false. E.g.
- In the future this might become the default behavior.
+ __PACKAGE__->config(abort_chain_on_error_fix => 0);
+
+ If this setting is set to false, then the remaining actions are
+ performed and the error is caught at the end of the chain.
- `use_hash_multivalue_in_request`
in Catalyst version 5.90080+ break some of your query code, you may disable
the UTF-8 decoding globally using this configuration.
- This setting takes precedence over `default_query_encoding` and
- `decode_query_using_global_encoding`
+ This setting takes precedence over `default_query_encoding`
+
+- `do_not_check_query_encoding`
+
+ Catalyst versions 5.90080 - 5.90106 would decode query parts of an incoming
+ request but would not raise an exception when the decoding failed due to
+ incorrect unicode. It now does, but if this change is giving you trouble
+ you may disable it by setting this configuration to true.
- `default_query_encoding`
specify a fixed value for how to decode your query. You might need this if
you are doing a lot of custom encoding of your URLs and not using UTF-8.
- This setting take precedence over `decode_query_using_global_encoding`.
-
-- `decode_query_using_global_encoding`
-
- Setting this to true will default your query decoding to whatever your
- general global encoding is (the default is UTF-8).
-
- `use_chained_args_0_special_case`
In older versions of Catalyst, when more than one action matched the same path
This is recommended for temporary backwards compatibility only.
+To turn it off for a single request use the [clear\_encoding](https://metacpan.org/pod/clear_encoding)
+method to turn off encoding for this request. This can be useful
+when you are setting the body to be an arbitrary block of bytes,
+especially if that block happens to be a block of UTF8 text.
+
Encoding is automatically applied when the content-type is set to
a type that can be encoded. Currently we encode when the content type
matches the following regular expression:
chansen: Christian Hansen
-Chase Venters <chase.venters@gmail.com>
+Chase Venters `chase.venters@gmail.com`
chicks: Christopher Hicks
marcus: Marcus Ramberg <mramberg@cpan.org>
-Mischa Spiegelmock <revmischa@cpan.org>
-
miyagawa: Tatsuhiko Miyagawa <miyagawa@bulknews.net>
mgrimes: Mark Grimes <mgrimes@cpan.org>
use Tree::Simple qw/use_weak_refs/;
use Tree::Simple::Visitor::FindByUID;
use Class::C3::Adopt::NEXT;
-use List::MoreUtils qw/uniq/;
use attributes;
use String::RewritePrefix;
use Catalyst::EngineLoader;
__PACKAGE__->_encode_check(Encode::FB_CROAK | Encode::LEAVE_SRC);
# Remember to update this in Catalyst::Runtime as well!
-our $VERSION = '5.90114';
+our $VERSION = '5.90115';
$VERSION = eval $VERSION if $VERSION =~ /_/; # numify for warning-free dev releases
sub import {
Catalyst - The Elegant MVC Web Application Framework
-=for html
-<a href="https://badge.fury.io/pl/Catalyst-Runtime"><img src="https://badge.fury.io/pl/Catalyst-Runtime.svg" alt="CPAN version" height="18"></a>
-<a href="https://travis-ci.org/perl-catalyst/catalyst-runtime/"><img src="https://api.travis-ci.org/perl-catalyst/catalyst-runtime.png" alt="Catalyst></a>
-<a href="http://cpants.cpanauthors.org/dist/Catalyst-Runtime"><img src="http://cpants.cpanauthors.org/dist/Catalyst-Runtime.png" alt='Kwalitee Score' /></a>
-
=head1 SYNOPSIS
See the L<Catalyst::Manual> distribution for comprehensive
and access it from the stash.
-Keep in mind that the C<end> method used is that of the caller action. So a C<$c-E<gt>detach> inside a forwarded action would run the C<end> method from the original action requested.
+Keep in mind that the C<end> method used is that of the caller action. So a C<< $c->detach >> inside a forwarded action would run the C<end> method from the original action requested.
=cut
# somewhat lifted from URI::_query's query_form
$query = '?'.join('&', map {
my $val = $params->{$_};
- #s/([;\/?:@&=+,\$\[\]%])/$URI::Escape::escapes{$1}/go; ## Commented out because seems to lead to double encoding - JNAP
- s/ /+/g;
- my $key = $_;
+ my $key = encode_utf8($_);
+ # using the URI::Escape pattern here so utf8 chars survive
+ $key =~ s/([^A-Za-z0-9\-_.!~*'() ])/$URI::Escape::escapes{$1}/go;
+ $key =~ s/ /+/g;
+
$val = '' unless defined $val;
(map {
- my $param = "$_";
- $param = encode_utf8($param);
+ my $param = encode_utf8($_);
# using the URI::Escape pattern here so utf8 chars survive
$param =~ s/([^A-Za-z0-9\-_.!~*'() ])/$URI::Escape::escapes{$1}/go;
$param =~ s/ /+/g;
- $key = encode_utf8($key);
- # using the URI::Escape pattern here so utf8 chars survive
- $key =~ s/([^A-Za-z0-9\-_.!~*'() ])/$URI::Escape::escapes{$1}/go;
- $key =~ s/ /+/g;
-
- "${key}=$param"; } ( ref $val eq 'ARRAY' ? @$val : $val ));
+ "${key}=$param";
+ } ( ref $val eq 'ARRAY' ? @$val : $val ));
} @keys);
}
(defined($res->body)) and
(ref(\$res->body) eq 'SCALAR')
) {
+ # if you are finding yourself here and your body is already encoded correctly
+ # and you want to turn this off, use $c->clear_encoding to prevent encoding
+ # at this step, or set encoding to undef in the config to do so for the whole
+ # application. See the ENCODING documentaiton for better notes.
$c->res->body( $c->encoding->encode( $c->res->body, $c->_encode_check ) );
# Set the charset if necessary. This might be a bit bonkers since encodable response
next if ! keys %$params;
my $t = Text::SimpleTable->new( [ 35, 'Parameter' ], [ $column_width, 'Value' ] );
for my $key ( sort keys %$params ) {
- my $param = $params->{$key};
- my $value = defined($param) ? $param : '';
- $t->row( $key, ref $value eq 'ARRAY' ? ( join ', ', @$value ) : $value );
+ my @values = ();
+ if(ref $params eq 'Hash::MultiValue') {
+ @values = $params->get_all($key);
+ } else {
+ my $param = $params->{$key};
+ if( defined($param) ) {
+ @values = ref $param eq 'ARRAY' ? @$param : $param;
+ }
+ }
+ $t->row( $key.( scalar @values > 1 ? ' [multiple]' : ''), join(', ', @values) );
}
$c->log->debug( ucfirst($type) . " Parameters are:\n" . $t->draw );
}
C<abort_chain_on_error_fix>
-When there is an error in an action chain, the default behavior is to continue
-processing the remaining actions and then catch the error upon chain end. This
-can lead to running actions when the application is in an unexpected state. If
-you have this issue, setting this config value to true will promptly exit a
-chain when there is an error raised in any action (thus terminating the chain
-early.)
+Defaults to true.
+
+When there is an error in an action chain, the default behavior is to
+abort the processing of the remaining actions to avoid running them
+when the application is in an unexpected state.
+
+Before version 5.90070, the default used to be false. To keep the old
+behaviour, you can explicitly set the value to false. E.g.
-use like:
+ __PACKAGE__->config(abort_chain_on_error_fix => 0);
- __PACKAGE__->config(abort_chain_on_error_fix => 1);
+If this setting is set to false, then the remaining actions are
+performed and the error is caught at the end of the chain.
-In the future this might become the default behavior.
=item *
This is recommended for temporary backwards compatibility only.
+To turn it off for a single request use the L<clear_encoding>
+method to turn off encoding for this request. This can be useful
+when you are setting the body to be an arbitrary block of bytes,
+especially if that block happens to be a block of UTF8 text.
+
Encoding is automatically applied when the content-type is set to
a type that can be encoded. Currently we encode when the content type
matches the following regular expression:
Andrew Bramble
-Andrew Ford E<lt>A.Ford@ford-mason.co.ukE<gt>
+Andrew Ford <A.Ford@ford-mason.co.uk>
Andrew Ruthven
chansen: Christian Hansen
-Chase Venters C<chase.venters@gmail.com>
+Chase Venters <chase.venters@gmail.com>
chicks: Christopher Hicks
-Chisel Wright C<pause@herlpacker.co.uk>
+Chisel Wright <pause@herlpacker.co.uk>
-Danijel Milicevic C<me@danijel.de>
+Danijel Milicevic <me@danijel.de>
davewood: David Schmidt <davewood@cpan.org>
-David Kamholz E<lt>dkamholz@cpan.orgE<gt>
+David Kamholz <dkamholz@cpan.org>
-David Naughton, C<naughton@umn.edu>
+David Naughton <naughton@umn.edu>
David E. Wheeler
Gary Ashton Jones
-Gavin Henry C<ghenry@perl.me.uk>
+Gavin Henry <ghenry@perl.me.uk>
Geoff Richards
jcamacho: Juan Camacho
-jester: Jesse Sheidlower C<jester@panix.com>
+jester: Jesse Sheidlower <jester@panix.com>
jhannah: Jay Hannah <jay@jays.net>
jon: Jon Schutz <jjschutz@cpan.org>
-Jonathan Rockway C<< <jrockway@cpan.org> >>
+Jonathan Rockway <jrockway@cpan.org>
-Kieren Diment C<kd@totaldatasolution.com>
+Kieren Diment <kd@totaldatasolution.com>
konobi: Scott McWhirter <konobi@cpan.org>
random: Roland Lammel <lammel@cpan.org>
-Robert Sedlacek C<< <rs@474.at> >>
+revmischa: Mischa Spiegelmock <revmischa@cpan.org>
+
+Robert Sedlacek <rs@474.at>
SpiceMan: Marcel Montes
vanstyn: Henry Van Styn <vanstyn@cpan.org>
-Viljo Marrandi C<vilts@yahoo.com>
+Viljo Marrandi <vilts@yahoo.com>
-Will Hawes C<info@whawes.co.uk>
+Will Hawes <info@whawes.co.uk>
willert: Sebastian Willert <willert@cpan.org>
wreis: Wallace Reis <wreis@cpan.org>
-Yuval Kogman, C<nothingmuch@woobling.org>
+Yuval Kogman <nothingmuch@woobling.org>
-rainboxx: Matthias Dietrich, C<perl@rainboxx.de>
+rainboxx: Matthias Dietrich <perl@rainboxx.de>
dd070: Dhaval Dhanani <dhaval070@gmail.com>
=head1 AUTHOR
-Florian Ragwitz E<lt>rafl@debian.orgE<gt>
+Florian Ragwitz <rafl@debian.org>
=end stopwords
use Class::Load ':all';
use String::RewritePrefix;
use Moose::Util qw/find_meta/;
-use List::Util qw/first/;
-use List::MoreUtils qw/uniq/;
+use List::Util qw/first uniq/;
use namespace::clean -except => 'meta';
BEGIN {
package MyApp::Controller::Zoo;
- sub foo : Local Does('Moo') { ... } # Catalyst::ActionRole::
- sub bar : Local Does('~Moo') { ... } # MyApp::ActionRole::Moo
- sub baz : Local Does('+MyApp::ActionRole::Moo') { ... }
+ sub foo : Local Does('Buzz') { ... } # Catalyst::ActionRole::
+ sub bar : Local Does('~Buzz') { ... } # MyApp::ActionRole::Buzz
+ sub baz : Local Does('+MyApp::ActionRole::Buzz') { ... }
=head2 GET
# in MyApp::Controller::Foo
sub bar : Chained CaptureArgs(1) { ... }
- # in MyApp::Controller::Foo::Moo
+ # in MyApp::Controller::Foo::Bar
sub bar : ChainedParent Args(1) { ... }
This builds a chain like C</bar/*/bar/*>.
its arguments. If it doesn't expect any, just specify
C<:CaptureArgs(0)>. The captures get passed to the action's C<@_> right
after the context, but you can also find them as array references in
-C<$c-E<gt>request-E<gt>captures-E<gt>[$level]>. The C<$level> is the
+C<< $c->request->captures->[$level] >>. The C<$level> is the
level of the action in the chain that captured the parts of the path.
An action that is part of a chain (that is, one that has a C<:Chained>
Just as with C<:CaptureArgs>, the arguments get passed to the action in
C<@_> after the context object. They can also be reached through
-C<$c-E<gt>request-E<gt>arguments>.
+C<< $c->request->arguments >>.
You should see 'Args' in L<Catalyst::Controller> for more details on using
type constraints in your Args declarations.
? $env->{QUERY_STRING}
: '';
- # Check for keywords (no = signs)
- # (yes, index() is faster than a regex :))
- if ( index( $query_string, '=' ) < 0 ) {
- my $keywords = $self->unescape_uri($query_string);
- $keywords = $decoder->($keywords);
- $c->request->query_keywords($keywords);
- return;
- }
-
$query_string =~ s/\A[&;]+//;
- my $p = Hash::MultiValue->new(
- map { defined $_ ? $decoder->($self->unescape_uri($_)) : $_ }
- map { ( split /=/, $_, 2 )[0,1] } # slice forces two elements
- split /[&;]+/, $query_string
- );
+ my @unsplit_pairs = split /[&;]+/, $query_string;
+ my $p = Hash::MultiValue->new();
+
+ my $is_first_pair = 1;
+ for my $pair (@unsplit_pairs) {
+ my ($name, $value)
+ = map { defined $_ ? $decoder->($self->unescape_uri($_)) : $_ }
+ ( split /=/, $pair, 2 )[0,1]; # slice forces two elements
+
+ if ($is_first_pair) {
+ # If the first pair has no equal sign, then it means the isindex
+ # flag is set.
+ $c->request->query_keywords($name) unless defined $value;
+
+ $is_first_pair = 0;
+ }
+
+ $p->add( $name => $value );
+ }
+
$c->encoding($old_encoding) if $old_encoding;
$c->request->query_parameters( $c->request->_use_hash_multivalue ? $p : $p->mixed );
package Catalyst::Exception::Basic;
-use MooseX::Role::WithOverloading;
+use Moose::Role;
+use if !eval { require Moose; Moose->VERSION('2.1300') },
+ 'MooseX::Role::WithOverloading';
use Carp;
use namespace::clean -except => 'meta';
package Catalyst::Exception::Interface;
-use MooseX::Role::WithOverloading;
+use Moose::Role;
+use if !eval { require Moose; Moose->VERSION('2.1300') },
+ 'MooseX::Role::WithOverloading';
use namespace::clean -except => 'meta';
use overload
# Remember to update this in Catalyst as well!
-our $VERSION = '5.90114';
+our $VERSION = '5.90115';
$VERSION = eval $VERSION if $VERSION =~ /_/; # numify for warning-free dev releases
=head1 NAME
use MooseX::Getopt;
use Catalyst::EngineLoader;
use Moose::Util::TypeConstraints;
-use Catalyst::Utils qw/ ensure_class_loaded /;
-use Class::Load 'load_class';
+use Catalyst::Utils;
use namespace::autoclean;
subtype 'Catalyst::ScriptRole::LoadableClass',
as 'ClassName';
coerce 'Catalyst::ScriptRole::LoadableClass',
from 'Str',
- via { ensure_class_loaded($_); 1 };
+ via { Catalyst::Utils::ensure_class_loaded($_); $_ };
with 'MooseX::Getopt' => {
-version => 0.48,
sub _run_application {
my $self = shift;
my $app = $self->application_name;
- load_class($app);
+ Catalyst::Utils::ensure_class_loaded($app);
my $server;
if (my $e = $self->_plack_engine_name ) {
$server = $self->load_engine($e, $self->_plack_loader_args);
my $res = request('foo/bar?test=1');
my $virtual_res = request('foo/bar?test=1', {host => 'virtualhost.com'});
+Alternately, you can pass in an L<HTTP::Request::Common> object to set arbitrary
+request headers.
+
+ my $res = request(GET '/foo/bar',
+ X-Foo => 'Bar',
+ Authorization => 'Bearer JWT_HERE',
+ ...
+ );
+
=head2 ($res, $c) = ctx_request( ... );
Works exactly like L<request|/"$res = request( ... );">, except it also returns the Catalyst context object,
} else {
warn "There was an error trying to detect your terminal size: $@\n";
}
+ };
+
+ unless ($width) {
warn 'Trouble trying to detect your terminal size, looking at $ENV{COLUMNS}'."\n";
$width = $ENV{COLUMNS}
if exists($ENV{COLUMNS})
&& $ENV{COLUMNS} =~ m/^\d+$/;
- };
+ }
do {
warn "Cannot determine desired terminal width, using default of 80 columns\n";
--- /dev/null
+#!perl
+
+use strict;
+use warnings;
+use Test::More tests => 1;
+use HTTP::Request::Common;
+
+BEGIN {
+ package TestApp::Controller::Root;
+ $INC{'TestApp/Controller/Root.pm'} = __FILE__;
+ use Moose;
+ use MooseX::MethodAttributes;
+ extends 'Catalyst::Controller';
+
+ has counter => (is => 'rw', isa => 'Int', default => sub { 0 });
+ sub increment {
+ my $self = shift;
+ $self->counter($self->counter + 1);
+ }
+ sub root :Chained('/') :PathPart('') :CaptureArgs(0) {
+ my ($self, $c, $arg) = @_;
+ die "Died in root";
+ }
+ sub main :Chained('root') :PathPart('') :Args(0) {
+ my ($self, $c, $arg) = @_;
+ $self->increment;
+ die "Died in main";
+ }
+ sub hits :Path('hits') :Args(0) {
+ my ($self, $c, $arg) = @_;
+ $c->response->body($self->counter);
+ }
+ __PACKAGE__->config(namespace => '');
+}
+{
+ package TestApp;
+ $INC{'TestApp.pm'} = __FILE__;
+ use Catalyst;
+ __PACKAGE__->setup;
+}
+
+use Catalyst::Test 'TestApp';
+
+{
+ my $res = request('/');
+}
+{
+ my $res = request('/hits');
+ is $res->content, 0, "main action not touched on crash with no explicit setting";
+}
--- /dev/null
+#!perl
+
+use strict;
+use warnings;
+use Test::More tests => 1;
+use HTTP::Request::Common;
+
+BEGIN {
+ package TestApp::Controller::Root;
+ $INC{'TestApp/Controller/Root.pm'} = __FILE__;
+ use Moose;
+ use MooseX::MethodAttributes;
+ extends 'Catalyst::Controller';
+
+ has counter => (is => 'rw', isa => 'Int', default => sub { 0 });
+ sub increment {
+ my $self = shift;
+ $self->counter($self->counter + 1);
+ }
+ sub root :Chained('/') :PathPart('') :CaptureArgs(0) {
+ my ($self, $c, $arg) = @_;
+ die "Died in root";
+ }
+ sub main :Chained('root') :PathPart('') :Args(0) {
+ my ($self, $c, $arg) = @_;
+ $self->increment;
+ die "Died in main";
+ }
+ sub hits :Path('hits') :Args(0) {
+ my ($self, $c, $arg) = @_;
+ $c->response->body($self->counter);
+ }
+ __PACKAGE__->config(namespace => '');
+}
+{
+ package TestApp;
+ $INC{'TestApp.pm'} = __FILE__;
+ use Catalyst;
+ __PACKAGE__->config(abort_chain_on_error_fix => 1);
+ __PACKAGE__->setup;
+}
+
+use Catalyst::Test 'TestApp';
+
+{
+ my $res = request('/');
+}
+{
+ my $res = request('/hits');
+ is $res->content, 0, "main action not touched on crash with explicit setting to true";
+}
--- /dev/null
+#!perl
+
+use strict;
+use warnings;
+use Test::More tests => 1;
+use HTTP::Request::Common;
+
+BEGIN {
+ package TestApp::Controller::Root;
+ $INC{'TestApp/Controller/Root.pm'} = __FILE__;
+ use Moose;
+ use MooseX::MethodAttributes;
+ extends 'Catalyst::Controller';
+
+ has counter => (is => 'rw', isa => 'Int', default => sub { 0 });
+ sub increment {
+ my $self = shift;
+ $self->counter($self->counter + 1);
+ }
+ sub root :Chained('/') :PathPart('') :CaptureArgs(0) {
+ my ($self, $c, $arg) = @_;
+ die "Died in root";
+ }
+ sub main :Chained('root') :PathPart('') :Args(0) {
+ my ($self, $c, $arg) = @_;
+ $self->increment;
+ die "Died in main";
+ }
+ sub hits :Path('hits') :Args(0) {
+ my ($self, $c, $arg) = @_;
+ $c->response->body($self->counter);
+ }
+ __PACKAGE__->config(namespace => '');
+}
+{
+ package TestApp;
+ $INC{'TestApp.pm'} = __FILE__;
+ use Catalyst;
+ __PACKAGE__->config(abort_chain_on_error_fix => 0);
+ __PACKAGE__->setup;
+}
+
+use Catalyst::Test 'TestApp';
+
+{
+ my $res = request('/');
+}
+{
+ my $res = request('/hits');
+ is $res->content, 1, "main action performed on crash with explicit setting to false";
+}
use Catalyst::Test 'TestApp';
my %roles = (
- foo => 'TestApp::ActionRole::Moo',
- bar => 'TestApp::ActionRole::Moo',
- baz => 'Moo',
+ foo => 'TestApp::ActionRole::Guff',
+ bar => 'TestApp::ActionRole::Guff',
+ baz => 'Guff',
quux => 'Catalyst::ActionRole::Zoo',
);
{
my $resp = request("/actionroles/corge");
ok($resp->is_success);
- is($resp->content, 'TestApp::ActionRole::Moo');
+ is($resp->content, 'TestApp::ActionRole::Guff');
is($resp->header('X-Affe'), 'Tiger');
- is($resp->header('X-Action-After'), 'moo');
+ is($resp->header('X-Action-After'), 'moo');
}
{
my $resp = request("/actionroles/frew");
use FindBin;
use lib "$FindBin::Bin/../lib";
-use Test::More tests => 54;
+use Test::More tests => 56;
use Catalyst::Test 'TestApp';
use Catalyst::Request;
{
my $creq;
- my $parameters = {
+ my $body_parameters = {
a => 1,
blank => '',
};
+ my $query_parameters = {
+ 'query string' => undef
+ };
+ my $parameters = {
+ %$body_parameters,
+ %$query_parameters
+ };
my $request = POST(
'http://localhost/dump/request/a/b?query+string',
- 'Content' => $parameters,
+ 'Content' => $body_parameters,
'Content-Type' => 'application/x-www-form-urlencoded'
);
ok( eval '$creq = ' . $response->content, 'Unserialize Catalyst::Request' );
is( $creq->uri->query, 'query+string', 'Catalyst::Request POST query_string' );
is( $creq->query_keywords, 'query string', 'Catalyst::Request query_keywords' );
+ is_deeply( $creq->query_parameters, $query_parameters, 'Catalyst::Request query_parameters' );
+ is_deeply( $creq->body_parameters, $body_parameters, 'Catalyst::Request body_parameters' );
is_deeply( $creq->parameters, $parameters, 'Catalyst::Request parameters' );
ok( $response = request('http://localhost/dump/request/a/b?x=1&y=1&z=1'), 'Request' );
{
- package Catalyst::Log::Subclass;
+ package Catalyst::Log::SubclassAutoflush;
use base qw/Catalyst::Log/;
sub _send_to_log {
@MESSAGES = (); # clear the message log
-my $SUBCLASS = 'Catalyst::Log::Subclass';
+my $SUBCLASS = 'Catalyst::Log::SubclassAutoflush';
can_ok $SUBCLASS, 'new';
-ok $log = Catalyst::Log::Subclass->new,
+ok $log = $SUBCLASS->new,
'... and the log subclass constructor should return a new object';
isa_ok $log, $SUBCLASS, '... and the object it returns';
isa_ok $log, $LOG, '... and it also';
use Test::More;
use Try::Tiny;
-plan skip_all => "Need Test::Without::Module for this test"
- unless try { require Test::Without::Module; 1 };
-
-Test::Without::Module->import(qw(
+my %hidden = map { (my $m = "$_.pm") =~ s{::}{/}g; $m => 1 } qw(
Starman::Server
Plack::Handler::Starman
MooseX::Daemonize
MooseX::Daemonize::Pid::File
MooseX::Daemonize::Core
-));
-
-require "$Bin/../aggregate/unit_core_script_server.t";
+);
+local @INC = (sub {
+ return unless exists $hidden{$_[1]};
+ die "Can't locate $_[1] in \@INC (hidden)\n";
+}, @INC);
-Test::Without::Module->unimport(qw(
- Starman::Server
- Plack::Handler::Starman
- MooseX::Daemonize
- MooseX::Daemonize::Pid::File
- MooseX::Daemonize::Core
-));
+do "$Bin/../aggregate/unit_core_script_server.t"
+ or die $@ || 'test returned false';
1;
-
);
is(
+ Catalyst::uri_for( $context, '/bar', 'with space', { 'also with' => 'space here' })->as_string,
+ 'http://127.0.0.1/foo/bar/with%20space?also+with=space+here',
+ 'Spaces encoded correctly'
+);
+
+is(
Catalyst::uri_for( $context, '/bar#fragment', { param1 => 'value1' } )->as_string,
'http://127.0.0.1/foo/bar?param1=value1#fragment',
'URI for path with fragment and query params 1'
'Plus is not encoded, called with only class name'
);
+is(
+ Catalyst::uri_for( 'TestApp', '/bar', 'with space', { 'also with' => 'space here' })->as_string,
+ '/bar/with%20space?also+with=space+here',
+ 'Spaces encoded correctly, called with only class name'
+);
+
TODO: {
local $TODO = 'broken by 5.7008';
is(
Marrandi
McWhirter
Milicevic
+ Mischa
Miyagawa
Montes
+ Napiorkowski
Naughton
Oleg
Ragwitz
Sedlacek
Sheidlower
SpiceMan
+ Spiegelmock
Styn
Szilakszi
Tatsuhiko
Ulf
Upasana
+ Venters
Vilain
Viljo
Wardley
miyagawa
mst
multipart
- Napiorkowski
naughton
ningu
nothingmuch
phaylon
rafl
rainboxx
+ revmischa
sri
szbalint
uploadtmp
use Class::MOP;
BEGIN {
+ my %hidden = map { (my $m = "$_.pm") =~ s{::}{/}g; $m => 1 } qw(
+ Foo
+ Bar
+ );
+ unshift @INC, sub {
+ return unless exists $hidden{$_[1]};
+ die "Can't locate $_[1] in \@INC (hidden)\n";
+ };
+}
+
+BEGIN {
package TestRole;
$INC{'TestRole'} = __FILE__;
use Moose::Role;
sub c { 'c' }
- package TestApp::TraitFor::Response::Bar;
+ package TestApp::TraitFor::Response::Bar;
$INC{'TestApp/TraitFor/Response/Bar.pm'} = __FILE__;
use Moose::Role;
}
BEGIN {
+ my %hidden = map { (my $m = "$_.pm") =~ s{::}{/}g; $m => 1 } qw(
+ Foo
+ Bar
+ );
+ unshift @INC, sub {
+ return unless exists $hidden{$_[1]};
+ die "Can't locate $_[1] in \@INC (hidden)\n";
+ };
+}
+
+BEGIN {
package TestRole;
$INC{'TestRole'} = __FILE__;
use Moose::Role;
-package Catalyst::ActionRole::Moo;
+package Catalyst::ActionRole::Guff;
use Moose::Role;
-package Moo;
+package Guff;
use Moose::Role;
-package TestApp::ActionRole::Moo;
+package TestApp::ActionRole::Guff;
use Moose::Role;
},
);
-sub foo : Local Does('Moo') {}
-sub bar : Local Does('~Moo') {}
-sub baz : Local Does('+Moo') {}
+sub foo : Local Does('Guff') {}
+sub bar : Local Does('~Guff') {}
+sub baz : Local Does('+Guff') {}
sub quux : Local Does('Zoo') {}
-sub corge : Local Does('Moo') ActionClass('TestAfter') {
+sub corge : Local Does('Guff') ActionClass('TestAfter') {
my ($self, $ctx) = @_;
$ctx->stash(after_message => 'moo');
}
--- /dev/null
+use warnings;
+use strict;
+use Test::More;
+
+# Test case for reported issue when an action consumes JSON but a
+# POST sends nothing we get a hard error
+
+{
+ package MyApp::Controller::Root;
+ $INC{'MyApp/Controller/Root.pm'} = __FILE__;
+
+ use base 'Catalyst::Controller';
+
+ sub bar :Local Args(0) GET {
+ my( $self, $c ) = @_;
+ }
+
+ package MyApp;
+ use Catalyst;
+ MyApp->setup;
+}
+
+use HTTP::Request::Common;
+use Catalyst::Test 'MyApp';
+
+# These tests assume that the decoding that occurs for the query string follows
+# the payload decoding algorithm described here:
+# https://www.w3.org/TR/html5/forms.html#url-encoded-form-data
+
+{
+ ok my $req = GET 'root/bar';
+
+ my ($res, $c) = ctx_request($req);
+
+ ok !defined($c->req->query_keywords), 'query_keywords is not defined when no ?';
+ is_deeply $c->req->query_parameters, {}, 'query_parameters defined, but empty for no ?';
+}
+
+
+{
+ ok my $req = GET 'root/bar?';
+
+ my ($res, $c) = ctx_request($req);
+
+ ok !defined $c->req->query_keywords, 'query_keywords is not defined when ? with empty query string';
+ is_deeply $c->req->query_parameters, {}, 'query_parameters defined, but empty with empty query string';
+}
+
+
+{
+ ok my $req = GET 'root/bar?a=b';
+
+ my ($res, $c) = ctx_request($req);
+
+ ok !defined($c->req->query_keywords), 'query_keywords undefined when isindex not set';
+ is_deeply $c->req->query_parameters, { a => 'b' }, 'query_parameters defined for ?a=b';
+}
+
+
+{
+ ok my $req = GET 'root/bar?x';
+
+ my ($res, $c) = ctx_request($req);
+
+ is $c->req->query_keywords, 'x', 'query_keywords defined for ?x';
+ # The algorithm reads like 'x' should be treated as a value, not a name.
+ # Perl does not support undef as a hash key. I feel this would be the best
+ # alternative as isindex is moving towards complete deprecation.
+ is_deeply $c->req->query_parameters, { 'x' => undef }, 'query_parameters defined for ?x';
+}
+
+
+{
+ ok my $req = GET 'root/bar?x&a=b';
+
+ my ($res, $c) = ctx_request($req);
+
+ is $c->req->query_keywords, 'x', 'query_keywords defined for ?x&a=b';
+ # See comment above about the 'query_parameters defined for ?x' test case.
+ is_deeply $c->req->query_parameters, { 'x' => undef, a => 'b' }, 'query_parameters defined for ?x&a=b';
+}
+
+
+done_testing();