use Carp qw/croak carp shortmess/;
use Try::Tiny;
use Safe::Isa;
+use Moose::Util 'find_meta';
use Plack::Middleware::Conditional;
use Plack::Middleware::ReverseProxy;
use Plack::Middleware::IIS6ScriptNameFix;
use Plack::Middleware::IIS7KeepAliveFix;
use Plack::Middleware::LighttpdScriptNameFix;
+use Plack::Middleware::ContentLength;
+use Plack::Middleware::Head;
+use Plack::Middleware::HTTPExceptions;
+use Plack::Middleware::FixMissingBodyInRedirect;
+use Plack::Middleware::MethodOverride;
+use Plack::Middleware::RemoveRedundantBody;
use Plack::Util;
-use Class::Load;
+use Class::Load 'load_class';
BEGIN { require 5.008003; }
my %p = ( _log => $self->log );
$p{_uploadtmp} = $self->_uploadtmp if $self->_has_uploadtmp;
$p{data_handlers} = {$self->registered_data_handlers};
+ $p{_use_hash_multivalue} = $self->config->{use_hash_multivalue_in_request}
+ if $self->config->{use_hash_multivalue_in_request};
\%p;
}
# Remember to update this in Catalyst::Runtime as well!
-our $VERSION = '5.90049_002';
+our $VERSION = '5.90065';
sub import {
my ( $class, @arguments ) = @_;
=head2 $c->forward( $class, $method, [, \@arguments ] )
-Forwards processing to another action, by its private name. If you give a
+This is one way of calling another action (method) in the same or
+a different controller. You can also use C<< $self->my_method($c, @args) >>
+in the same controller or C<< $c->controller('MyController')->my_method($c, @args) >>
+in a different controller.
+The main difference is that 'forward' uses some of the Catalyst request
+cycle overhead, including debugging, which may be useful to you. On the
+other hand, there are some complications to using 'forward', restrictions
+on values returned from 'forward', and it may not handle errors as you prefer.
+Whether you use 'forward' or not is up to you; it is not considered superior to
+the other ways to call a method.
+
+'forward' calls another action, by its private name. If you give a
class name but no method, C<process()> is called. You may also optionally
pass arguments in an arrayref. The action will receive the arguments in
C<@_> and C<< $c->req->args >>. Upon returning from the function,
$class->setup_log( delete $flags->{log} );
$class->setup_plugins( delete $flags->{plugins} );
- $class->setup_middleware();
+
$class->setup_data_handlers();
$class->setup_dispatcher( delete $flags->{dispatcher} );
if (my $engine = delete $flags->{engine}) {
EOF
}
+ # Call plugins setup, this is stupid and evil.
+ # Also screws C3 badly on 5.10, hack to avoid.
+ {
+ no warnings qw/redefine/;
+ local *setup = sub { };
+ $class->setup unless $Catalyst::__AM_RESTARTING;
+ }
+
+ $class->setup_middleware();
+
+ # Initialize our data structure
+ $class->components( {} );
+
+ $class->setup_components;
+
if ( $class->debug ) {
my @plugins = map { "$_ " . ( $_->VERSION || '' ) } $class->registered_plugins;
? $class->log->debug(qq/Found home "$home"/)
: $class->log->debug(qq/Home "$home" doesn't exist/)
: $class->log->debug(q/Couldn't find home/);
- }
-
- # Call plugins setup, this is stupid and evil.
- # Also screws C3 badly on 5.10, hack to avoid.
- {
- no warnings qw/redefine/;
- local *setup = sub { };
- $class->setup unless $Catalyst::__AM_RESTARTING;
- }
-
- # Initialize our data structure
- $class->components( {} );
-
- $class->setup_components;
- if ( $class->debug ) {
my $column_width = Catalyst::Utils::term_width() - 8 - 9;
my $t = Text::SimpleTable->new( [ $column_width, 'Class' ], [ 8, 'Type' ] );
for my $comp ( sort keys %{ $class->components } ) {
}
$class->setup_finalize;
- # Should be the last thing we do so that user things hooking
- # setup_finalize can log..
+
+ # Flush the log for good measure (in case something turned off 'autoflush' early)
$class->log->_flush() if $class->log->can('_flush');
- return 1; # Explicit return true as people have __PACKAGE__->setup as the last thing in their class. HATE.
+
+ return $class || 1; # Just in case someone named their Application 0...
}
=head2 $app->setup_finalize
my $last = pop( @{ $c->stack } );
if ( my $error = $@ ) {
+ #rethow if this can be handled by middleware
+ if(blessed $error && ($error->can('as_psgi') || $error->can('code'))) {
+ $error->can('rethrow') ? $error->rethrow : croak $error;
+ }
if ( blessed($error) and $error->isa('Catalyst::Exception::Detach') ) {
$error->rethrow if $c->depth > 1;
}
# Support skipping finalize for psgix.io style 'jailbreak'. Used to support
# stuff like cometd and websockets
- if($c->request->has_io_fh) {
+ if($c->request->_has_io_fh) {
$c->log_response;
return;
}
$c->finalize_headers unless $c->response->finalized_headers;
- # HEAD request
- if ( $c->request->method eq 'HEAD' ) {
- $c->response->body('');
- }
-
$c->finalize_body;
}
=head2 $c->finalize_error
-Finalizes error.
+Finalizes error. If there is only one error in L</error> and it is an object that
+does C<as_psgi> or C<code> we rethrow the error and presume it caught by middleware
+up the ladder. Otherwise we return the debugging error page (in debug mode) or we
+return the default error page (production mode).
=cut
-sub finalize_error { my $c = shift; $c->engine->finalize_error( $c, @_ ) }
+sub finalize_error {
+ my $c = shift;
+ if($#{$c->error} > 0) {
+ $c->engine->finalize_error( $c, @_ );
+ } else {
+ my ($error) = @{$c->error};
+ if(
+ blessed $error &&
+ ($error->can('as_psgi') || $error->can('code'))
+ ) {
+ # In the case where the error 'knows what it wants', becauses its PSGI
+ # aware, just rethow and let middleware catch it
+ $error->can('rethrow') ? $error->rethrow : croak $error;
+ croak $error;
+ } else {
+ $c->engine->finalize_error( $c, @_ )
+ }
+ }
+}
=head2 $c->finalize_headers
if ( my $location = $response->redirect ) {
$c->log->debug(qq/Redirecting to "$location"/) if $c->debug;
$response->header( Location => $location );
-
- if ( !$response->has_body ) {
- # Add a default body if none is already present
- my $encoded_location = encode_entities($location);
- $response->body(<<"EOF");
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml">
- <head>
- <title>Moved</title>
- </head>
- <body>
- <p>This item has moved <a href="$encoded_location">here</a>.</p>
- </body>
-</html>
-EOF
- $response->content_type('text/html; charset=utf-8');
- }
}
- # Content-Length
- if ( defined $response->body && length $response->body && !$response->content_length ) {
-
- # get the length from a filehandle
- if ( blessed( $response->body ) && $response->body->can('read') || ref( $response->body ) eq 'GLOB' )
- {
- my $size = -s $response->body;
- if ( $size ) {
- $response->content_length( $size );
- }
- else {
- $c->log->warn('Serving filehandle without a content-length');
- }
- }
- else {
- # everything should be bytes at this point, but just in case
- $response->content_length( length( $response->body ) );
- }
- }
-
- # Errors
- if ( $response->status =~ /^(1\d\d|[23]04)$/ ) {
- $response->headers->remove_header("Content-Length");
- $response->body('');
- }
+ # Remove incorrectly added body and content related meta data when returning
+ # an information response, or a response the is required to not include a body
$c->finalize_cookies;
my $c = $class->prepare(@arguments);
$c->dispatch;
$status = $c->finalize;
- }
- catch {
+ } catch {
+ #rethow if this can be handled by middleware
+ if(blessed $_ && ($_->can('as_psgi') || $_->can('code'))) {
+ $_->can('rethrow') ? $_->rethrow : croak $_;
+ }
chomp(my $error = $_);
$class->log->error(qq/Caught exception in engine "$error"/);
};
sub _make_immutable_if_needed {
my $class = shift;
- my $meta = Class::MOP::get_metaclass_by_name($class);
+ my $meta = find_meta($class);
my $isa_ca = $class->isa('Class::Accessor::Fast') || $class->isa('Class::Accessor');
if (
$meta->is_immutable
$dispatcher = $class->dispatcher_class;
}
- Class::MOP::load_class($dispatcher);
+ load_class($dispatcher);
# dispatcher instance
$class->dispatcher( $dispatcher->new );
# Don't really setup_engine -- see _setup_psgi_app for explanation.
return if $class->loading_psgi_file;
- Class::MOP::load_class($engine);
+ load_class($engine);
if ($ENV{MOD_PERL}) {
my $apache = $class->engine_loader->auto;
my ( $proto, $plugin, $instant ) = @_;
my $class = ref $proto || $proto;
- Class::MOP::load_class( $plugin );
+ load_class( $plugin );
$class->log->warn( "$plugin inherits from 'Catalyst::Component' - this is deprecated and will not work in 5.81" )
if $plugin->isa( 'Catalyst::Component' );
my $plugin_meta = Moose::Meta::Class->create($plugin);
} @{ $plugins };
for my $plugin ( reverse @plugins ) {
- Class::MOP::load_class($plugin->[0], $plugin->[1]);
+ load_class($plugin->[0], $plugin->[1]);
my $meta = find_meta($plugin->[0]);
next if $meta && $meta->isa('Moose::Meta::Role');
to use it to enable L<Plack::Middleware>
This method is automatically called during 'setup' of your application, so
-you really don't need to invoke it.
+you really don't need to invoke it. However you may do so if you find the idea
+of loading middleware via configuration weird :). For example:
+
+ package MyApp;
+
+ use Catalyst;
+
+ __PACKAGE__->setup_middleware('Head');
+ __PACKAGE__->setup;
When we read middleware definitions from configuration, we reverse the list
which sounds odd but is likely how you expect it to work if you have prior
experience with L<Plack::Builder> or if you previously used the plugin
L<Catalyst::Plugin::EnableMiddleware> (which is now considered deprecated)
+So basically your middleware handles an incoming request from the first
+registered middleware, down and handles the response from the last middleware
+up.
+
=cut
sub registered_middlewares {
my $class = shift;
if(my $middleware = $class->_psgi_middleware) {
- return @$middleware;
+ return (
+ Plack::Middleware::HTTPExceptions->new,
+ Plack::Middleware::RemoveRedundantBody->new,
+ Plack::Middleware::FixMissingBodyInRedirect->new,
+ Plack::Middleware::ContentLength->new,
+ Plack::Middleware::MethodOverride->new,
+ Plack::Middleware::Head->new,
+ @$middleware);
} else {
die "You cannot call ->registered_middlewares until middleware has been setup";
}
}
sub setup_middleware {
- my ($class, @middleware_definitions) = @_;
- push @middleware_definitions, reverse(
- @{$class->config->{'psgi_middleware'}||[]});
+ my $class = shift;
+ my @middleware_definitions = @_ ?
+ reverse(@_) : reverse(@{$class->config->{'psgi_middleware'}||[]});
my @middleware = ();
while(my $next = shift(@middleware_definitions)) {
}
}
- $class->_psgi_middleware(\@middleware);
+ my @existing = @{$class->_psgi_middleware || []};
+ $class->_psgi_middleware([@middleware,@existing,]);
}
=head2 registered_data_handlers
=head2 default_data_handlers
-Default Data Handler that come bundled with L<Catalyst>. Currently there is
-only one default data handler, for 'application/json'. This uses L<JSON::MaybeXS>
-which uses the dependency free L<JSON::PP> OR L<Cpanel::JSON::XS> if you have
-installed it. If you don't mind the XS dependency, you should add the faster
-L<Cpanel::JSON::XS> to you dependency list (in your Makefile.PL or dist.ini, or
-cpanfile, etc.)
+Default Data Handlers that come bundled with L<Catalyst>. Currently there are
+only two default data handlers, for 'application/json' and an alternative to
+'application/x-www-form-urlencoded' which supposed nested form parameters via
+L<CGI::Struct> or via L<CGI::Struct::XS> IF you've installed it.
+
+The 'application/json' data handler is used to parse incoming JSON into a Perl
+data structure. It used either L<JSON::MaybeXS> or L<JSON>, depending on which
+is installed. This allows you to fail back to L<JSON:PP>, which is a Pure Perl
+JSON decoder, and has the smallest dependency impact.
-L<JSON::MaybeXS> is loaded the first time you ask for it (so if you never ask
-for it, its never used).
+Because we don't wish to add more dependencies to L<Catalyst>, if you wish to
+use this new feature we recommend installing L<JSON> or L<JSON::MaybeXS> in
+order to get the best performance. You should add either to your dependency
+list (Makefile.PL, dist.ini, cpanfile, etc.)
=cut
sub default_data_handlers {
my ($class) = @_;
return +{
+ 'application/x-www-form-urlencoded' => sub {
+ my ($fh, $req) = @_;
+ my $params = $req->_use_hash_multivalue ? $req->body_parameters->mixed : $req->body_parameters;
+ Class::Load::load_first_existing_class('CGI::Struct::XS', 'CGI::Struct')
+ ->can('build_cgi_struct')->($params);
+ },
'application/json' => sub {
- local $/;
- Class::Load::load_class("JSON::MaybeXS");
- JSON::MaybeXS::decode_json $_->getline },
+ Class::Load::load_first_existing_class('JSON::MaybeXS', 'JSON')
+ ->can('decode_json')->(do { local $/; $_->getline });
+ },
};
}
=item *
+C<use_hash_multivalue_in_request>
+
+In L<Catalyst::Request> the methods C<query_parameters>, C<body_parametes>
+and C<parameters> return a hashref where values might be scalar or an arrayref
+depending on the incoming data. In many cases this can be undesirable as it
+leads one to writing defensive code like the following:
+
+ my ($val) = ref($c->req->parameters->{a}) ?
+ @{$c->req->parameters->{a}} :
+ $c->req->parameters->{a};
+
+Setting this configuration item to true will make L<Catalyst> populate the
+attributes underlying these methods with an instance of L<Hash::MultiValue>
+which is used by L<Plack::Request> and others to solve this very issue. You
+may prefer this behavior to the default, if so enable this option (be warned
+if you enable it in a legacy application we are not sure if it is completely
+backwardly compatible).
+
+=item *
+
C<psgi_middleware> - See L<PSGI MIDDLEWARE>.
=item *
=back
+=head1 EXCEPTIONS
+
+Generally when you throw an exception inside an Action (or somewhere in
+your stack, such as in a model that an Action is calling) that exception
+is caught by Catalyst and unless you either catch it yourself (via eval
+or something like L<Try::Tiny> or by reviewing the L</error> stack, it
+will eventually reach L</finalize_errors> and return either the debugging
+error stack page, or the default error page. However, if your exception
+can be caught by L<Plack::Middleware::HTTPExceptions>, L<Catalyst> will
+instead rethrow it so that it can be handled by that middleware (which
+is part of the default middleware). For example this would allow
+
+ use HTTP::Throwable::Factory 'http_throw';
+
+ sub throws_exception :Local {
+ my ($self, $c) = @_;
+
+ http_throw(SeeOther => { location =>
+ $c->uri_for($self->action_for('redirect')) });
+
+ }
+
=head1 INTERNAL ACTIONS
Catalyst uses internal actions like C<_DISPATCH>, C<_BEGIN>, C<_AUTO>,
it installed (if you want the faster XS parser, add it to you project Makefile.PL
or dist.ini, cpanfile, etc.)
-The C<data_handlers> configuation is a hashref whose keys are HTTP Content-Types
+The C<data_handlers> configuration is a hashref whose keys are HTTP Content-Types
(matched against the incoming request type using a regexp such as to be case
insensitive) and whose values are coderefs that receive a localized version of
C<$_> which is a filehandle object pointing to received body.
Where C<@middleware> is one or more of the following, applied in the REVERSE of
the order listed (to make it function similarly to L<Plack::Builder>:
+
+Alternatively, you may also define middleware by calling the L</setup_middleware>
+package method:
+
+ package MyApp::Web;
+
+ use Catalyst;
+
+ __PACKAGE__->setup_middleware( \@middleware_definitions);
+ __PACKAGE__->setup;
+
+In the case where you do both (use 'setup_middleware' and configuration) the
+package call to setup_middleware will be applied earlier (in other words its
+middleware will wrap closer to the application). Keep this in mind since in
+some cases the order of middleware is important.
+
+The two approaches are not exclusive.
=over 4
Ulf Edvinsson
+vanstyn: Henry Van Styn <vanstyn@cpan.org>
+
Viljo Marrandi C<vilts@yahoo.com>
Will Hawes C<info@whawes.co.uk>
dd070: Dhaval Dhanani <dhaval070@gmail.com>
+Upasana <me@upasana.me>
+
=head1 COPYRIGHT
-Copyright (c) 2005, the above named PROJECT FOUNDER and CONTRIBUTORS.
+Copyright (c) 2005-2014, the above named PROJECT FOUNDER and CONTRIBUTORS.
=head1 LICENSE